動態規划:最大子矩陣


  在DP問題中有一種叫最大子矩陣問題,剛好碰到了這一題,於是學習分享之。

  讓我們先來看一下題目:ZOJ Problem Set - 1074

  http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1074

  題目分類:動態規划

  題目大意:就是輸入一個N*N的矩陣,找出在矩陣中,所有元素加起來之和最大的子矩陣。

  例如在    0 -2 -7 0 這樣一個4*4的矩陣中,元素之和最大的子矩陣為   9 2  ,它們之和為15。 

       9  2 -6 2                        -4 1
      -4 1 -4 1                         -1 8
      -1 8 0 -2

 

  這是一個最大子矩陣問題,我們怎么來解決這個問題呢?任何問題都會有它的簡化的問題,這是二維的數組,與之對應的,我們可以先嘗試一下一維數組。

  如果有一個一維數組a[n],如何找出連續的一段,使其元素之和最大呢?

  例如有 1 2 -3 4 -2 5 -3 -1 7 4 -6 這樣一個數組,那么顯然 4 -2 5 -3 -1 7 4 這個子數組元素之和最大,為4+(-2)+5+(-3)+(-3)+7+4=14。為找到一維數組的最大子數組,我們可以有以下方法。

  1、窮舉法

 

 1 for(i=0;i<n;i++)
 2 {
 3     for(j=0;j<=i;j++)
 4     {
 5         sum = 0;
 6         for(k=j;k<=i;k++)
 7             sum += a[k];
 8         if(sum > max)    max = sum;
 9     }
10 }

 

  窮舉法在n很大的情況下,需要運行的次數非常的多,有三層循環,所以n很大時不能使用這種方法。

  2、帶記憶的遞推法

 1 record[0] = 0;
 2 for(i=1;i<=n;i++)                    //用下標1~n來儲存n個數
 3     record[i] = record[i-1] + a[i];  //用record記錄a[i]前i個的和
 4 max = 0;
 5 for(i=1;i<=n;i++)
 6 {
 7     for(j=0;j<i;j++)
 8     {
 9         sum = record[i] - record[j];
10         if(sum > max)    max = sum;
11     }
12 }

  這種方法的時間復雜度明顯比上一種的低了很多,時間復雜度為O(n²)。這種方法其實我們再繼續優化一下,就變成了我們所需要的動態規划。

  3、動態規划

  我們來分析一下最優子結構,若想找到n個數的最大子段和,那么要找到n-1個數的最大子段和,這就出來了。我們用b[i]來表示a[0]...a[i]的最大子段和,b[i]無非有兩種情況
:(1)最大子段一直連續到a[i]  (2)以a[i]為首的新的子段 。由此我們可以得到b[i]的狀態轉移方程:b[i]=max{b[i-1]+a[i],a[i]}。最終我們得到的最大子段和為max{b[i], 0<=i<n}, 算法如下:

 

 1 int MaxSubArray(int a[],int n)
 2 {
 3     int i,b = 0,sum = 0;
 4     for(i = 0;i < n;i++)
 5     {
 6         if(b>0)                // 若a[i]+b[i-1]會減小
 7             b += a[i];        // 則以a[i]為首另起一個子段
 8         else    
 9             b = a[i];
10         if(b > sum)    
11             sum = b;
12     }
13     return sum;
14 }

 

  說了這么多,這跟最大子矩陣有什么關系呢?當然有關系學啦!二維就是一維的擴展,把二維壓扁不就變成一維了嗎?

  我們假設所求N*N的矩陣的最大子矩陣是從i列到j列,q行到p行,如下圖所示(假設下標從1開始)

  a[1][1]  a[1][2]  ······  a[1][i]  ······  a[1][j]   ······  a[1][n]

  a[2][1]  a[2][2]  ······  a[2][i]  ······  a[2][j]   ······  a[2][n]

                  ······

  a[q][1]  a[q][2]  ······  a[q][i]  ······  a[q][j]  ······  a[q][n]

                  ······

  a[p][1]  a[p][2]  ······  a[p][i]  ······  a[p][j]  ······  a[p][n]

                  ······

  a[n][1]  a[n][2]  ······  a[n][i]  ······  a[n][j]  ······  a[n][n]

  最大子矩陣就是圖示紅色部分,如果把最大子矩陣同列的加起來,我們可以得到一個一維數組{a[q][i]+······+a[p][i] , ······ ,a[q][j]+······+a[p][j]} ,現在我們可以看出,這其實就是一個一維數組的最大子段問題。如果把二維數組看成是縱向的一維數組和橫向的一維數組,那問題不就迎刃而解了嗎?把二維轉換成了我們剛剛解決了的問題。

  最終我們得到了ZOJ Problem Set - 1074的解法,代碼如下:http://paste.ubuntu.com/15272521/

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 
 5 int maxsub(int a[],int n)    
 6 {
 7     int i,max=0,b=0;
 8     for(i=0;i<n;i++)
 9     {
10         if(b > 0)
11             b += a[i];
12         else 
13             b = a[i];
14         if(b > max)
15             max = b;
16     }
17     return max;
18 }
19 
20 int main()
21 {
22     int n,i,j,k,maxsubrec,maxsubarr;
23     int dp[101][101],arr[101];
24     while(cin>>n)
25     {
26          for(i=0;i<n;i++)
27              for(j=0;j<n;j++)
28                  cin>>dp[i][j];
29          maxsubrec = 0;
30          for(i=0;i<n;i++)
31          {
32              memset(arr,0,sizeof(arr));
33              for(j=i;j<n;j++)
34              {
35                  for(k=0;k<n;k++)
36                      arr[k] += dp[j][k];
37                  maxsubarr = maxsub(arr,n);
38                  if(maxsubarr > maxsubrec) maxsubrec = maxsubarr;
39              }
40          }
41          cout<<maxsubrec<<endl;
42     }
43 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM