0-1背包的有趣應用

直接開始問題达箍,前端開發(fā)過程中我們經(jīng)常會遇到這樣的問題,我們需要在一個寬度固定的區(qū)域顯示一些長度不固定的標簽或者按鈕。
畫個圖說明一下

這是一種看著比較舒服的情況,當然因為每個標簽的長度不固定也有可能出現(xiàn)其他的情況

由于第一行已經(jīng)沒有足夠的空間用來填充接下來的標簽太颤,因此只能把這個位置空出來換一行接著填充苞俘,依次處理盹沈,右邊就會多出很多空白的區(qū)域不能利用。

在不考慮標簽顯示順序的情況下吃谣,有沒有一種辦法能讓整個區(qū)域填充的飽滿一點乞封,也就是右側空白區(qū)域盡可能的小。

我們把這個問題簡單抽象一下:定義整個顯示區(qū)域的寬度為W岗憋;每個Tag的寬度為wi肃晚;是否選取某個Tag為xi={0,1}
針對于每一行要求:

期望:

能夠達到最大

經(jīng)過抽象之后問題就變得明朗了,典型的一種組合優(yōu)化NP完全問題仔戈,再具體一點就是0-1背包問題
背包問題有很多種求解方式关串,這里選擇一種動態(tài)規(guī)劃的解法拧廊。
背包問題:給定 n 種物品和一個容量為C的背包,物品 i 的重量是 wi晋修,其價值為 vi 吧碾。
應該如何選擇裝入背包的物品,使得裝入背包中的物品的總價值最大墓卦?
面對每個物品倦春,我們只有選擇拿取或者不拿兩種選擇,不能選擇裝入某物品的一部分落剪,也不能裝入同一物品多次

聲明一個 大小為 m[n][c] 的二維數(shù)組睁本,m[ i ][ j ] 表示 在面對第 i 件物品,且背包容量為 j 時所能獲得的最大價值 忠怖,那么我們可以很容易分析得出 m[i][j] 的計算方法呢堰,

  • j < w[i] 的情況,這時候背包容量不足以放下第 i 件物品凡泣,只能選擇不拿
    m[ i ][ j ] = m[ i-1 ][ j ]
  • j>=w[i] 的情況暮胧,這時背包容量可以放下第 i 件物品,我們就要考慮拿這件物品是否能獲取更大的價值问麸。

如果拿取往衷,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 這里的 m[ i-1 ][ j-w[ i ] ]指的就是考慮了i-1件物品严卖,背包容量為j-w[i]時的最大價值席舍,也是相當于為第i件物品騰出了w[i]的空間。

如果不拿哮笆,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)

究竟是拿還是不拿来颤,自然是比較這兩種情況那種價值最大
由此可以得到狀態(tài)轉移方程:

if(j>=w[i])  
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);  
else  
    m[i][j]=m[i-1][j];  

針對于我們這里的問題vi=wi,我們期待的背包物品的價值就是物品的重量稠肘,具體算法實現(xiàn)

/**
 背包算法

 @param vws 價值&重量輸入(每個Tag的寬度)
 @param width 背包容量(每行Tag能占用的最大寬度)
 @return 背包內容物品選擇情況(Tag的選擇情況)
 */
- (NSIndexSet *)backpack:(NSArray<NSNumber *> *)vws width:(NSInteger)width
{
    // 背包可選擇物品數(shù)量
    int n = (int)vws.count;
    
    // 背包容量
    int c = (int)width;
    
    // 物品價值數(shù)組
    int *v = malloc(sizeof(int) * (n + 1));
    
    // 物品重量數(shù)組
    int *w = malloc(sizeof(int) * (n + 1));
    
    // 初始化價值數(shù)組和重量數(shù)組
    *v = 0;
    *w = 0;
    int *vh = v;
    int *wh = w;
    for (int i = 0; i < n; i++) {
        *++vh = [vws[i] intValue];
        *++wh = [vws[i] intValue];
    }
    
    // 構造二維轉移矩陣福铅,并初始化為0
    int **m = (int **)malloc(sizeof(int *) * (n + 1));
    for(int i = 0; i < n + 1; i++) {
        *(m + i) = (int *)malloc(sizeof(int) * (c + 1));
        for (int j = 0; j < c + 1; j++) {
            *(*(m + i) + j) = 0;
        }
    }
    
    // 構造轉移矩陣
    for(int i = 1; i <= n; i++) {
        for (int j = 1; j <= c; j++) {
            if (j >= w[i]) {
                m[i][j] = MAX(m[i - 1][j], m[i - 1][j - w[i]] + v[i]);
            }else {
                m[i][j] = m[i - 1][j];
            }
        }
    }
    
    // 標記物品選擇情況
    int *x = malloc(sizeof(int) * (n + 1));
    for(int i = n; i > 1; i--) {
        if(m[i][c] == m[i-1][c]) {
            x[i] = 0;
        }else {
            x[i] = 1;
            c-= w[i];
        }
    }
    x[1] = (m[1][c] > 0) ? 1 : 0;
    
    // 返回物品選擇情況
    NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
    for (int i = 1; i <= n; i++) {
        if (x[i] == 1) {
            [indexSet addIndex:i - 1];
        }
    }
    
    // 回收資源
    free(v);
    free(w);
    free(x);
    for(int i = 0; i < n; i++) {
        free(*(m + i));
    }
    
    return indexSet;
}

在求得了轉移矩陣之后還需要知道具體是選擇了哪個物品,另起一個 x[ ] 數(shù)組项阴,x[i]=0表示不拿滑黔,x[i]=1表示拿。m[n][c]為最優(yōu)值环揽,如果m[n][c]=m[n-1][c] ,說明有沒有第n件物品都一樣略荡,則x[n]=0 ; 否則 x[n]=1。當x[n]=0時歉胶,由x[n-1][c]繼續(xù)構造最優(yōu)解汛兜;當x[n]=1時,則由x[n-1][c-w[i]]繼續(xù)構造最優(yōu)解通今。以此類推粥谬,可構造出所有的最優(yōu)解肛根。

 // 標記物品選擇情況
    int *x = malloc(sizeof(int) * (n + 1));
    for(int i = n; i > 1; i--) {
        if(m[i][c] == m[i-1][c]) {
            x[i] = 0;
        }else {
            x[i] = 1;
            c-= w[i];
        }
    }
    x[1] = (m[1][c] > 0) ? 1 : 0;
    
    // 返回物品選擇情況
    NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
    for (int i = 1; i <= n; i++) {
        if (x[i] == 1) {
            [indexSet addIndex:i - 1];
        }
    }

完整代碼參考:
https://github.com/fuxiaoghost/BackpackAlgorithm

我們看看最終完成的效果,雖然丑了點漏策,但是效果還不錯

背包砌墻
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末晶通,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子哟玷,更是在濱河造成了極大的恐慌狮辽,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巢寡,死亡現(xiàn)場離奇詭異喉脖,居然都是意外死亡,警方通過查閱死者的電腦和手機抑月,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門树叽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谦絮,你說我怎么就攤上這事题诵。” “怎么了层皱?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵性锭,是天一觀的道長。 經(jīng)常有香客問我叫胖,道長草冈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任瓮增,我火速辦了婚禮怎棱,結果婚禮上,老公的妹妹穿的比我還像新娘绷跑。我一直安慰自己拳恋,他們只是感情好,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布砸捏。 她就那樣靜靜地躺著谬运,像睡著了一般。 火紅的嫁衣襯著肌膚如雪带膜。 梳的紋絲不亂的頭發(fā)上吩谦,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音膝藕,去河邊找鬼。 笑死咐扭,一個胖子當著我的面吹牛芭挽,可吹牛的內容都是我干的滑废。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼袜爪,長吁一口氣:“原來是場噩夢啊……” “哼蠕趁!你這毒婦竟也來了?” 一聲冷哼從身側響起辛馆,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤俺陋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昙篙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腊状,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年苔可,在試婚紗的時候發(fā)現(xiàn)自己被綠了缴挖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡焚辅,死狀恐怖映屋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情同蜻,我是刑警寧澤棚点,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站湾蔓,受9級特大地震影響乙濒,放射性物質發(fā)生泄漏。R本人自食惡果不足惜卵蛉,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一颁股、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧傻丝,春花似錦甘有、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泛释,卻和暖如春滤愕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怜校。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工间影, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茄茁。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓魂贬,卻偏偏與公主長得像巩割,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子付燥,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內容