1. 程式人生 > 其它 >最小子陣列和——分治思想

最小子陣列和——分治思想

  首先什麼是最大和子陣列:

  即從陣列某一位置開始的連續陣列的所有元素的和要保證最大

  例如13,-25,20,-3,-16,-23,18,20,-7,12,最大子陣列即為18,20,-7,12,和為43,且是連續的

  

  在陣列中找到最大和子陣列有三種情況:

  1.最大子陣列在源陣列的中間位置的左側 2.最大子陣列在原陣列中間位置的右側 3.最大子陣列橫跨中間位置,既存在於左側又存在於右側

  

  首先來說第三種情況圖b

  因為子陣列橫跨中間位置mid,所以這證明i至mid與mid+1至j的所有元素相加為陣列中最大的。為了保證這個條件,i至mid就得是mid左側最大的子陣列,同理,mid+1至j也得是mid右側的最大子陣列,否則前面的條件無法成立。所以任務就是分別找到mid兩側的最大子陣列,然後把他們的和加到一起就是橫跨mid的情況的最大子陣列的和。

  

 1 //尋找橫跨middle的最大子陣列始末位置以及最大值
 2 tuple<int, int, int> findMaxCrossSubArray(vector<int>& nums, int left, int middle, int right) {
 3     int leftMaxSum = INT_MIN, rightMaxSum = INT_MIN;
 4     int sum = 0;
 5     int leftIndex = 0, rightIndex = 0;
 6 
 7     //兩個迴圈,分別去找middle左側和右側的最大和以及生成最大和時的索引位置
8 for (int i = middle; i >= left; i--) { 9 sum += nums[i]; 10 if (sum >= leftMaxSum) { 11 leftMaxSum = sum; 12 leftIndex = i; 13 } 14 } 15 sum = 0; 16 for (int i = middle + 1; i <= right; i++) { 17 sum += nums[i]; 18 if
(sum >= rightMaxSum) { 19 rightMaxSum = sum; 20 rightIndex = i; 21 } 22 } 23 //然後將兩個最大和相加,兩邊索引值也返回 24 tuple<int, int, int> t(leftIndex, rightIndex, leftMaxSum + rightMaxSum); 25 return t; 26 }

  函式用一個元組去儲存最大子陣列的始末位置和最大值,middle為mid,先從右往左從middle遍歷至left,每個元素累加,找到最大值leftSumMax和出現最大值時的索引leftIndex。右側同理,只不過範圍變成了middle + 1至right。最後將左右索引和兩端的最大值相加,存在元組裡返回。

  之後就是前兩個步驟,要判斷左右兩部分的子陣列首先就要遞迴去拆分,拆到只剩一個元素的時候停止,元素兩兩合併,合併的同時尋找三個累加中最大的一個作為自己的最大和。

  如上圖所示,當陣列遞迴到只有一個元素的時候,開始層層返回。返回的過程中要判斷出三個資料(我寫在了每個部分的下面)來計算每個部分的和。因為只存在於一個元素的時候,子陣列的最大和就是本身,所以底層只需要返回自己的索引和值。合併之後,左側的最大值就是之前遞迴返回的,右側的最大值就是右側返回的,之後要計算的就是橫跨mid的最大值,將這三者比較,返回最大值以及它的始末位置。

  首先看最左下角,13和-25,左側最大值是13,右側則是-25,計算橫跨mid最大值則是-12,所以13,-25這部分的最大子陣列就是13本身,返回0,0,13。

  接著來到13,-25,20,右側因為只有20,所以右側是20,左側已經算出來是上面的13,cross(mid)則是8(-12+20),經過比較返回右側,即2,2,20。

  然後是13,-25,20,-3,-16,左側最大值經由上面計算為20,右邊則是別的遞迴層,經過計算(和上面步驟相同)最大值是-3,所以整個陣列左半部分最大子陣列和就是2,2,20。

  右側和左側計算方法相同,得出結果是橫跨了一次mid的子陣列6,9,43。

  計算源陣列的Cross,經過計算得出21,43 > 21 > 20,所以返回右側元組6,9,43,結果就出來了

  

 1 tuple<int, int, int> findMaxSubArray(vector<int>& nums, int left, int right) {
 2     //base case
 3     if (left >= right) {
 4         tuple<int, int, int> ans(left, right, nums[left]);        //就一個元素,返回本身
 5         return ans;
 6     }
 7     else {
 8         int middle = (left + right) / 2;        //分割
 9         int leftLeft, leftRight, rightLeft, rightRight, crossLeft, crossRight, leftSum, rightSum, crossSum;
10         tuple<int, int, int> tl = findMaxSubArray(nums, left, middle);                //middle左側的最大子陣列
11         tuple<int, int, int> tr = findMaxSubArray(nums, middle + 1, right);            //middle右側的最大子陣列
12         leftLeft = get<0>(tl), leftRight = get<1>(tl), leftSum = get<2>(tl);
13         rightLeft = get<0>(tr), rightRight = get<1>(tr), rightSum = get<2>(tr);
14         tuple<int, int, int> tc = findMaxCrossSubArray(nums, left, middle, right);    //橫跨middle的最大子陣列
15         crossLeft = get<0>(tc), crossRight = get<1>(tc), crossSum = get<2>(tc);
16 
17         //三個子陣列誰和大返回誰的始末位置和最大值
18         if ((leftSum >= rightSum) && (leftSum >= crossSum)) {
19             return tl;
20         }else if ((rightSum >= leftSum) && (rightSum >= crossSum)) {
21             return tr;
22         }else {
23             return tc;
24         }
25     }
26 }

  最終程式碼:

 1 vector<int> findMax(vector<int>& nums) {
 2     //tuple三位:0起始索引,1結束索引,2最大值
 3     tuple<int, int, int> t = findMaxSubArray(nums, 0, nums.size() - 1);
 4     //根據元組前兩位去找開始到結束的索引
 5     vector<int> ans(nums.begin() + get<0>(t), nums.begin() + get<1>(t) + 1);
 6     return ans;
 7 }
 8 
 9 tuple<int, int, int> findMaxSubArray(vector<int>& nums, int left, int right) {
10     //base case
11     if (left >= right) {
12         tuple<int, int, int> ans(left, right, nums[left]);        //就一個元素,返回本身
13         return ans;
14     }
15     else {
16         int middle = (left + right) / 2;        //分割
17         int leftLeft, leftRight, rightLeft, rightRight, crossLeft, crossRight, leftSum, rightSum, crossSum;
18         tuple<int, int, int> tl = findMaxSubArray(nums, left, middle);                //middle左側的最大子陣列
19         tuple<int, int, int> tr = findMaxSubArray(nums, middle + 1, right);            //middle右側的最大子陣列
20         leftLeft = get<0>(tl), leftRight = get<1>(tl), leftSum = get<2>(tl);
21         rightLeft = get<0>(tr), rightRight = get<1>(tr), rightSum = get<2>(tr);
22         tuple<int, int, int> tc = findMaxCrossSubArray(nums, left, middle, right);    //橫跨middle的最大子陣列
23         crossLeft = get<0>(tc), crossRight = get<1>(tc), crossSum = get<2>(tc);
24 
25         //三個子陣列誰和大返回誰的始末位置和最大值
26         if ((leftSum >= rightSum) && (leftSum >= crossSum)) {
27             return tl;
28         }else if ((rightSum >= leftSum) && (rightSum >= crossSum)) {
29             return tr;
30         }else {
31             return tc;
32         }
33     }
34 }
35 //尋找橫跨middle的最大子陣列始末位置以及最大值
36 tuple<int, int, int> findMaxCrossSubArray(vector<int>& nums, int left, int middle, int right) {
37     int leftMaxSum = INT_MIN, rightMaxSum = INT_MIN;
38     int sum = 0;
39     int leftIndex = 0, rightIndex = 0;
40 
41     //兩個迴圈,分別去找middle左側和右側的最大和以及生成最大和時的索引位置
42     for (int i = middle; i >= left; i--) {
43         sum += nums[i];
44         if (sum >= leftMaxSum) {
45             leftMaxSum = sum;
46             leftIndex = i;
47         }
48     }
49     sum = 0;
50     for (int i = middle + 1; i <= right; i++) {
51         sum += nums[i];
52         if (sum >= rightMaxSum) {
53             rightMaxSum = sum;
54             rightIndex = i;
55         }
56     }
57     //然後將兩個最大和相加,兩邊索引值也返回
58     tuple<int, int, int> t(leftIndex, rightIndex, leftMaxSum + rightMaxSum);
59     return t;
60 }
C++

  採用元組是因為涉及到要返回出最大子陣列的始末位置,如果只需要求得最大子陣列的最大和,則只需要一個int去儲存結果就可以了。