淺談DP演算法(一)之一維數組解決01背包問題

来源:http://www.cnblogs.com/wust-owen/archive/2016/04/11/5377126.html
-Advertisement-
Play Games

淺談DP演算法(一) ——如何用一維數組解決01背包問題 DP演算法(Dynamic Programming,俗稱動態規劃)是最經典演算法之一.本筆記以耳熟能詳的數塔問題為引子,深入討論01背包的解決方法. 首先,如下圖所示,要求從頂層走到底層,若每一步只能走到相鄰的結點,則經過的結點的數字之和最大是多少 ...


淺談DP演算法(一)

                  ——如何用一維數組解決01背包問題

 

  DP演算法(Dynamic Programming,俗稱動態規劃)是最經典演算法之一.本筆記以耳熟能詳的數塔問題為引子,深入討論01背包的解決方法.

  首先,如下圖所示,要求從頂層走到底層,若每一步只能走到相鄰的結點,則經過的結點的數字之和最大是多少?

  這個問題,對於任意一個結點,直接選擇數字大的子結點顯然是不行的.以9為例,如果選擇15,當前和24>21,但是15的兩個子結點太小,24+6+18<21+10+18.由此可見,這樣求出每階段的部分最優解並不是全局最優解.另外,如果用蠻力演算法,每條路徑都遍歷一次,那麼層數為n時,路徑總數呈指數級增長:

  顯然這種方法的計算量太大,也不可取.那麼此時用DP演算法是行之有效的.具體思想為:從倒數第二層開始,一層一層向上遍歷.倒數第二層第一個結點是2,如果路徑經過2,那麼肯定會選擇數值較大左子結點19. 便用19+2=21代替原先的2. 同理18改為18+10=28,9改為19,5改為21. 這樣倒數第二層就變成21 28 19 21四個結點,再將最後一層捨棄.這樣一層層向上,直到第一層,選擇第二層較大的那個結點加到9上面去,就得出了全局最優解.

  代碼實現:如果數字塔為n層,開闢一個n*n的二維數組即可,非常簡單,此處省略.

  對動態規劃的思想有一個基本瞭解後,現總結出動態規劃基本概念.不過在此之前,首先解釋一下什麼是多階段決策問題,什麼是狀態.

  多階段決策問題:一個問題可以分為若幹個階段,每個階段都要做出決策;

  狀態:每個階段開始面臨的自然狀況或客觀條件.在上例中每個階段的狀態就是到達當前結點的兩個子結點的選擇.

  動態規劃是一個多階段決策問題中,各個階段採取的決策,依賴於當前狀態,又引起狀態的轉移,一個決策序列就是在變化的狀態中產生出來的,故有“動態”的含義,稱這種解決多階段決策最優化問題的方法為動態規劃方法.

   適用DP演算法的問題一般具備以下特點:

    (1)最優化原理(最優子結構性質)

    一個最優化策略的子策略也是最優的.舉個例子就清楚了,如數字塔問題中,第二層到底層數值和最大的路徑一定是從頂層到底層數值和最大的路徑的子集;

    (2)無後向性(無後效性)

    通俗講,某階段狀態一旦確定,就不受這個狀態之後的決策的影響;

    (3)子問題重疊

    即子問題之間不獨立.與前兩個不同的是,這個特點不是必要的,如果不滿足相比之下DP演算法不具備優勢.如果獨立,分治演算法策略將更簡單方便.

  下麵來看經典的01背包問題:

    把N個物品放入容量為V的背包里,第i個物品所需要的空間為need[i],同時它的價值為value[i],該如何放才能達到背包里的物品價值最大?

  分析:因為對於任何一個物品,都只有放或不放的選擇,因而稱之為01背包.用best(N,V)*表示N個物品放入容量為V的背包里的最大價值.則對於第N個物品,除非need[N]>V,都有放或不放兩個選擇:

  (1)如果將第N個物品放入背包中

best(N,V)=best(N-1,V-need[N])+value[N];  //即等於第N個物品的價值加上將N-1個物品放入容量為V-need[N]的背包里的最大價值;

  (2)如果不放

best(N,V)=best(N-1,V-need[N])+value[N];  //即等於第N個物品的價值加上將N-1個物品放入容量為V-need[N]的背包里的最大價值;

  這樣我們重覆上述步驟,直至N和V減小到0,可以得到對於其中任何一個物品i (1<=i<=N),當前狀態背包剩餘容量為j (0<=j<=V),都有

if(j<need[i])

    best(i,j)=best(i-1,j);

else

    best(i,j)=MAX{best(i-1,j-need[i])+value[i],best(i-1,j)};

  這就是從第i階段到第i-1階段的狀態轉移規律,程式員為了把逼格提高,起了一個術語——狀態轉移方程.

  而當i=0時**,只需設置一下邊界值best(0,j)=0.這樣,求解best(N,V)這個看起來很複雜又無從下手的問題,就變成了從i=0時的best(0,j)=0逐漸到i=N時的best(N,j).

 

  *best(N,V)只是物品、背包容量和價值三者之間的關係表示,千萬不要糾結它為什麼這麼表示,到底什麼意思,裡面又是如何根據N,V來得到價值的;

  **本來i=0是沒有意義的,因為是從第N個物品逐漸推導到第1個物品,設置i=0時的best(i,j)只是為了滿足數學上的計算;

 

  代碼實現:舉一個實例助於理解,現有4個物品,編號1、2、3、4;他們所需空間分別為2、3、4、1,而各自價值為2、5、3、2,背包總空間為5,該怎樣放才能使得背包內物品價值最大呢?根據狀態轉移方程,列表如下所示:

 

i need value j
1 2 3 4 5
0     0 0 0 0 0
1 2 2          
2 3 5          
3 4 3          
4 1 2          

 

  j從1遞增到5,best(1,1)=best(0,1)=0,依次求best(i,j)並填入表中,得: 

 

i need value j
1 2 3 4 5
1 2 2 0 2 2 2 2

 

  i從1到4,填完整個表格: 

 

i need value j
1 2 3 4 5
0     0 0 0 0 0
1 2 2 0 2 2 2 2
2 3 5 0 2 5 5 7
3 4 3 0 2 5 5 7
4 1 2 2 2 5 7 7

 

  觀察表格,同樣是一個二維的數組,直觀上看數塔是從下往上階段遞推,01背包是一行一行向數組最右下角遞推.定義二維數組best[N+1][V],best[N+1][V]就是全局最優解.核心代碼如下:

 

for(j=1;j<=V;j++)

    best[0][j]=0;

for(i=1;i<=N+1;i++)

    for(j=1;j<=V;j++)

    {

        if(j<need[i])

            best[i][j]=best[i-1][j];

        else

            best[i][j]=MAX{best[i-1][j-need[i]]+value[i],best[i-1][j]};

    }        

   

  代碼優化:如果覺得問題就這麼解決,那就沒意思了.在上述表格中,其實可以發現,為了得到最後的一個元素,申請一個龐大的(N+1)*V的數組過於鋪張浪費了,用ACMer的話來說就是所需存儲空間過大.不難發現,求解第i階段其實只需要第i-1階段的值,也就是說,無論N有多大,只需要一個2*V的數組就可以解決問題了,這樣確實可以省下不少空間.i為偶數存入best[0][],i為奇數存入best[1][].

 

best(i-1,j-x)

...

best(i-1,j)

 

...

best(i,j)

 

for(j=1;j<=V;j++)

    best[0][j]=0;

for(i=1;i<=N+1;i++)

{

    if(i%2){

        if(j<need[i])

            best[1][j]=best[0][j];

        else

            best[1][j]=MAX{best[0,j-need[i]]+value[i],best[0,j]};

    }

    else{

        if(j<need[i])

            best[0][j]=best[1][j];

        else
    
            best[0][j]=MAX{best[1][j-need[i]]+value[i],best[1][j]};
    }
}    

   

  繼續優化:看到標題就會猜到,問題還可以繼續簡化成一維數組.至於如何實現呢,看上述表格繼續思考(畫個表格不是用來占空間的.

 

...

best(j-2)

best(j-1)

best(j)

...

  初始設置一維數組best[]={0},此時要留心兩個細節:

  (1)必須從best[V],best[V-1]開始計算並覆蓋原先數據;

  (2)只需要覆蓋到j=need[i].

  至於原因不難理解,這裡直接給出核心代碼:

 

__int64 best[]={0};        //long long

for(i=1; i<=n; i++)

    for(j=m; j>=needed[i]; j--)

        best[j]=MAX{best[j],best[j-need[i]]+value[i]};

  

  優化到現在,也就是說整個01背包問題,只需要三行代碼!無論是時間上,還是空間上,還是代碼的簡潔性,都達到最優!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 先拿出我半前年前平臺的設計初稿,經過半年的努力我已經完成了該設計稿的所有功能。並且理念已經遠遠超出該設計稿。 下麵是一些博友對我貼子的評價: 1、樓主,想法很美好,現實很骨感,我們公司就有一套你說的這樣的平臺,界面都是用XML配置出來的,雖然開發效率很高,但只能做固定版式的系統,有任何版式的修改,幾 ...
  • 這幾個月一直忙APP的項目,沒來得及更新項目,想想該抽出時間整理一下開發思路,跟大家分享,同時也希望得到寶貴的建議。 先說一下我們的許可權管理的的設計思路,首先一個企業信息化管理系統一定會用到許可權管理, 那麼一個動態的菜單在企業信息化管理系統占有一定的分量。 下麵介紹我的一些思路。 由於原聲的winf ...
  • SignalR支持多種伺服器和客戶端配置。此外,每種傳輸方式都有自身的要求限制;如果某種傳輸方式不被系統支持,SignalR能夠優雅地將故障轉移到其他類型的傳輸方式。關於SignalR所支持的傳輸方式的詳細信息,參見: Transports and Fallbacks。 系統要求 SignalR服務 ...
  • 當我們運行java程式時,發現程式不動,但又不知道是哪裡出問題時,可以使用JDK自帶的jstack工具去定位; 廢話不說,直接上例子吧,在window平臺上的; 死迴圈 寫個死迴圈的程式如下: 先運行以上程式,程式進入死迴圈; 打開cmd,輸入tasklist,找到javaw.exe的PID,如下為 ...
  • 這個寫的很不錯 http://www.cnblogs.com/Mr-xu/archive/2012/08/07/2626973.html 什麼叫引用? 引用就是對某對象的另一個名字。 主要用途: 為了描述函數的參數和返回值,特別是運算符的重載。 用法: X var; X& r = var; 那麼r是 ...
  • Spring這類的框架給我們開髮帶來非常大的好處,讓我們更加快速、有效的開發。 所以我們在開發中通常都會用到各種框架,每個框架都有很多jar包,每個jar都有各自不同的功能。開發不同的功能用到的jar也不盡相同,所以當我們用到相關框架的時候,並不是把它所有的jar都引入系統。那麼怎麼確定自己將會用到 ...
  • 準備先利用之前整理的python自帶的unittest框架 整合excel 實現介面自動化測試功能 先看看excel表格設置: 下來是對excel獲取的代碼: 之後是unittest框架 利用迴圈執行所有用例 現在只要在excel里添加介面測試用例 運行腳本 即可 ...
  • PHP及網頁使用UTF-8編碼,資料庫是sql server2008,使用預設編碼(936,即GBK編碼) 當讀取資料庫數據時,使用php自帶的json_encode()返回到前端,結果中文不顯示。 解決辦法: <?php header("Content-Type: text/html;charse ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...