分治法


最大子數組問題

方法一:暴力求解方法

我們可以很容易地設計出一個暴力方法來求解本問題:簡單地嘗試沒對可能的子數組,共有O(n2)種

#include<iostream>
using namespace std;
#define INT_MIN 0x80000000
int main()
{
     int arr[10]={9,8,-3,-5,7,-39,79,-37,8,9};
     int i,j;
     int sum=0,maxsum=INT_MIN;
     int imax;
     for (i=0;i<10;i++)
     {
         sum=0;
          for (j=i;j<10;j++)
          {
             sum+=arr[j];
             if (maxsum<sum)
             {
                 maxsum=sum;
                 imax=j;
             }
          }
     }
 
     cout<< "maxsum: " <<maxsum<< " imax:" <<imax<<endl;
}
 

方法二:使用分治策略的求解方法(O(nlgn))

我們來思考如何用分治技術來求解最大子數組問題。假定我們要尋找子數組A[low,high]的最大子數組。使用分治技術意味着我們要將子數組划分為兩個規模盡量相等的子數組。也就是說,找到子數組的中央位置,比如mid,然后考慮求解兩個子數組A[low,mid]和A[mid+1,high]。則A[low,high]的任何連續子數組A[i..j]所處的位置必定是以下3種情況之一:

  • 完全位於子數組A[low..mid]中,因此low<=i<=j<=mid
  • 完全位於子數組A[mid+1..high]中,因此mid<i<=j<=high;
  • 跨越了中點,因此low<=i<=mid<j<=high;

因此,A[low..high]的一個最大子數組所處的位置必然是這三種情況之一。實際上,A[low,high]的一個最大子數組必然是完全位於A[low..mid]中,完全位於A[mid+1..high]中或者跨越中點的所有子數組中和最大者。我們可以遞歸地求解A[low,mid]和A[mid+1,high]的最大子數組,因為這兩個子問題仍然是最大子數組的問題,只是規模更小。因此,剩下的全部工作就是尋找跨越中點的最大子數組,然后在三種情況中選取和最大者。

算法如下:

#include<iostream>
#include<tuple>
using namespace std;
#define INT_MIN 0x80000000
tuple< int , int , int > FindMaxCrossingSubarry( int arr[], int low, int mid, int high)
{
     int leftsum=INT_MIN,rightsum=INT_MIN;
     int sum=0;
     int maxleft=low,maxright=mid+1;
     int i;
     for (i=mid;i!=low;i--)
     {
         sum+=arr[i];
         if (leftsum<sum)
         {
             leftsum=sum;
             maxleft=i;
         }
     }
     sum=0;
     for (i=mid+1;i!=high;i++)
     {
         sum+=arr[i];
         if (rightsum<sum)
         {
             rightsum=sum;
             maxright=i;
         }
     }
     cout<< "maxleft: " <<maxleft<< "  maxright: " <<maxright<< "  leftsum+rightsum: " <<leftsum+rightsum<<endl;
     return tuple< int , int , int >(maxleft,maxright,leftsum+rightsum);
}
 
tuple< int , int , int > FindMaximumSubarry( int arr[], int low, int high)
{
     if (low==high)
         return tuple< int , int , int >(low,high,arr[low]);
     else
     {
         int mid=(low+high)/2;
         tuple< int , int , int > left=FindMaximumSubarry(arr,low,mid);
         tuple< int , int , int > right=FindMaximumSubarry(arr,mid+1,high);
         tuple< int , int , int > middle=FindMaxCrossingSubarry(arr,low,mid,high);
         if (get<2>(left)>=get<2>(right)&&get<2>(left)>=get<2>(middle))
             return left;
         else if (get<2>(right)>=get<2>(left)&&get<2>(right)>=get<2>(middle))
             return right;
         else
             return middle;
     }
}
 
int main()
{
     int arr[10]={9,8,-3,-5,7,-39,79,-37,8,9};
     tuple< int , int , int > result=FindMaximumSubarry(arr,0,9);
     cout<<get<0>(result)<< " " <<get<1>(result)<< " " <<get<2>(result)<<endl;
}
 

方法三:使用動態規划的算法(O(n))

#include<iostream>
using namespace std;
 
int MaxArraySum( int arr[], int n)
{
     int sum,maxSum,maxi;
     int i;
     maxSum=0;
     maxi=0;
     sum=0;
     for (i=0;i<n;i++)
     {
         sum+=arr[i];
         if (sum<0)
         {
             sum=0;
             continue ;
         }
         if (maxSum<sum)
         {
             maxSum=sum;
             maxi=i;
         }
     }
     cout<< "max sum: " <<maxSum<< " index: " <<maxi<<endl;
     return maxSum;
}
 
int main()
{
     int arr[10]={9,8,-3,-5,7,-39,79,-37,8,9};
     cout<<MaxArraySum(arr,10);
}
 
 
 

分治法總結

一、基本概念
在計算機科學中,分治法是一種很重要的算法。字面上的解釋是“分而治之”,就是把一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最后子問題可以簡單的直接求解,原問題的解即子問題的解的合並。這個技巧是很多高效算法的基礎,如排序算法(快速排序,歸並排序),傅立葉變換(快速傅立葉變換)……

任何一個可以用計算機求解的問題所需的計算時間都與其規模有關。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。例如,對於n個元素的排序問題,當n=1時,不需任何計算。n=2時,只要作一次比較即可排好序。n=3時只要作3次比較即可,…。而當n較大時,問題就不那么容易處理了。要想直接解決一個規模較大的問題,有時是相當困難的。

--------------------------------------------------------------------------------

二、基本思想及策略
分治法的設計思想是:將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。
分治策略是:對於一個規模為n的問題,若該問題可以容易地解決(比如說規模n較小)則直接解決,否則將其分解為k個規模較小的子問題,這些子問題互相獨立且與原問題形式相同,遞歸地解這些子問題,然后將各子問題的解合並得到原問題的解。這種算法設計策略叫做分治法。

如果原問題可分割成k個子問題,1<k≤n,且這些子問題都可解並可利用這些子問題的解求出原問題的解,那么這種分治法就是可行的。由分治法產生的子問題往往是原問題的較小模式,這就為使用遞歸技術提供了方便。在這種情況下,反復應用分治手段,可以使子問題與原問題類型一致而其規模卻不斷縮小,最終使子問題縮小到很容易直接求出其解。這自然導致遞歸過程的產生。分治與遞歸像一對孿生兄弟,經常同時應用在算法設計之中,並由此產生許多高效算法。

--------------------------------------------------------------------------------

三、分治法適用的情況
分治法所能解決的問題一般具有以下幾個特征:
1) 該問題的規模縮小到一定的程度就可以容易地解決
2) 該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質。
3) 利用該問題分解出的子問題的解可以合並為該問題的解;

4) 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。

第一條特征是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨着問題規模的增加而增加;
第二條特征是應用分治法的前提它也是大多數問題可以滿足的,此特征反映了遞歸思想的應用;、
第三條特征是關鍵,能否利用分治法完全取決於問題是否具有第三條特征,如果具備了第一條和第二條特征,而不具備第三條特征,則可以考慮用貪心法或動態規划法。
第四條特征涉及到分治法的效率,如果各子問題是不獨立的則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規划法較好。

--------------------------------------------------------------------------------

四、分治法的基本步驟
分治法在每一層遞歸上都有三個步驟:
step1 分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;
step2 解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解各個子問題
step3 合並:將各個子問題的解合並為原問題的解。

它的一般的算法設計模式如下:
Divide-and-Conquer(P)
1. if |P|≤n0
2. then return(ADHOC(P))
3. 將P分解為較小的子問題 P1 ,P2 ,...,Pk
4. for i←1 to k
5. do yi ← Divide-and-Conquer(Pi) △ 遞歸解決Pi
6. T ← MERGE(y1,y2,...,yk) △ 合並子問題
7. return(T)
其中|P|表示問題P的規模;n0為一閾值,表示當問題P的規模不超過n0時,問題已容易直接解出,不必再繼續分解。ADHOC(P)是該分治法中的基本子算法,用於直接解小規模的問題P。因此,當P的規模不超過n0時直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是該分治法中的合並子算法,用於將P的子問題P1 ,P2 ,...,Pk的相應的解y1,y2,...,yk合並為P的解。

--------------------------------------------------------------------------------

五、分治法的復雜性分析
一個分治法將規模為n的問題分成k個規模為n/m的子問題去解。設分解閥值n0=1,且adhoc解規模為1的問題耗費1個單位時間。再設將原問題分解為k個子問題以及用merge將k個子問題的解合並為原問題的解需用f(n)個單位時間。用T(n)表示該分治法解規模為|P|=n的問題所需的計算時間,則有:T(n)= k T(n/m)+f(n)

通過迭代法求得方程的解:
遞歸方程及其解只給出n等於m的方冪時T(n)的值,但是如果認為T(n)足夠平滑,那么由n等於m的方冪時T(n)的值可以估計T(n)的增長速度。通常假定T(n)是單調上升的,從而當 mi≤n<mi+1時,T(mi)≤T(n)<T(mi+1)。

--------------------------------------------------------------------------------

六、可使用分治法求解的一些經典問題
(1)二分搜索
(2)大整數乘法
(3)Strassen矩陣乘法
(4)棋盤覆蓋
1,快速排序

快排是C.R.A.Hoare於1962年提出的一種划分交換排序。它采用了一種分治的策略,該方法的基本思想是:
1).先從數列末尾取出一個數作為基准元。
2).分區過程,將比這個數大的數全放到它的右邊,小於或等於它的數全放到它的左邊。
3).再對左右區間重復第二步,直到各區間只有一個數。

  1. //手動快速默寫快排:先划界,再分治....   int *arr,  low,  high)  
  2.  pos = rand() % (high - low + 1) + low;  
  3.   
  4.  key = arr[high];  
  5.  i = low - 1;  
  6.  ( j = low; j <= high - 1; j++)  
  7.  (arr[j] <= key)  
  8.  i;  
  9. void *arr,  low,  high)  
  10.  (low < high)  
  11.  mid = quickPartion(arr, low, high);  
  12. }  

2,歸並排序
歸並排序是把序列遞歸地分成短序列,遞歸出口是短序列只有1個元素(認為直接有序)或者2個序列(1次比較和交換),然后把各個有序的段序列合並成一個有序的長序列,不斷合並直到原序列全部排好序。可以發現,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也沒有人故意交換,這不會破壞穩定性。那么,在短的有序序列合並的過程中,穩定是是否受到破壞?沒有,合並過程中我們可以保證如果兩個當前元素相等時,我們把處在前面的序列的元素保存在結果序列的前面,這樣就保證了穩定性。所以,歸並排序也是穩定的排序算法。

核心函數:

  1. //分治法的合並函數   //arr[low...mid]與arr[mid+1...high]相合並 void *arr,  low,  mid,  high)  
  2.  leftlen = mid - low + 1;  
  3.  rightlen = high - mid;  
  4.  *L =  [leftlen + 1];  
  5.  *R =  [rightlen + 1];  
  6.   
  7.   
  8.  i = 0;  
  9.  (; i < leftlen; i++)  
  10.  j = 0;  
  11.  (; j < rightlen; j++)  
  12.   
  13.  ( k = low; k <= high; k++)  
  14.  (L[i] <= R[j])  
  15.   
  16. [] L; L = NULL;  
  17. [] R; R = NULL;  
  18. //合並排序法(分治法)   void *arr,  low,  high)  
  19.  (low < high)  
  20.  mid = (low + high) / 2;  
  21. }  

九,Leetcode實例

題目:

在一個未排序的數組中找到第k大的元素,注意此言的第k大就是排序后的第k大的數,
注意:給定k總是安全有效的。

分析:

設數組下表從low開始,至high結束。
1、 總是取要划界的數組末尾元素為划界元x,開始划界:

a) 用j從low遍歷到high-1(最后一個暫不處理),i=low-1,如果nums[j]比x小就將nums[++i]與nums[j]交換位置

b) 遍歷完后再次將nums[i+1]與nums[high]交換位置(處理最后一個元素);

c) 返回划界元的位置i+1,下文稱其為midpos

這時的midpos位置的元素,此時就是整個數組中第N-midpos大的元素,我們所要做的就像二分法一樣找到K=N-midpos的“中間位置”,即midpos=N-K

2、 如果midpos==n-k,那么返回該值,這就是第k大的數。
3、 如果midpos>n-k,那么第k大的數在左半數組
4、 如果midpos<n-k,那么第k大的數在右半數組

  1. //思路首先:    
  2. //快排划界,如果划界過程中當前划界元的中間位置就是k則找到了    
  3. //time,o(n*lg(k)),space,o(1)    
  4. class Solution {    
  5. public:    
  6.     //對數組vec,low到high的元素進行划界,並獲取vec[high]的“中間位置”    
  7.     int quickPartion(vector<int> &vec, int low,int high)    
  8.     {      
  9.         int x = vec[high];    
  10.         int i = low - 1;    
  11.         for (int j = low; j <= high - 1; j++)    
  12.         {    
  13.             if (vec[j] <= x)//小於x的划到左邊    
  14.                 swap(vec,++i,j);    
  15.         }    
  16.         swap(vec,++i,high);//找到划界元的位置    
  17.         return i;//返回位置    
  18.     }     
  19.     //交換數組元素i和j的位置    
  20.     void swap(vector<int>& nums, int i, int j){      
  21.         int temp = nums[i];      
  22.         nums[i]=nums[j];      
  23.         nums[j]=temp;      
  24.     }     
  25.     int getQuickSortK(vector<int> &vec, int low,int high, int k)      
  26.     {      
  27.         if(low >= high) return vec[low];    
  28.         int  midpos = quickPartion(vec, low,high);   //對原數組vec[low]到vec[high]的元素進行划界      
  29.         if (midpos == vec.size() - k)      //如果midpos==n-k,那么返回該值,這就是第k大的數      
  30.             return vec[midpos];    
  31.         else if (midpos < vec.size() - k)  //如果midpos<n-k,那么第k大的數在右半數組     
  32.             return getQuickSortK(vec, midpos+1, high, k);    
  33.         else                               //如果midpos>n-k,那么第k大的數在左半數組     
  34.             return getQuickSortK(vec, low, midpos-1, k);    
  35.     }     
  36.     int findKthLargest(vector<int>& nums, int k) {    
  37.         return getQuickSortK(nums,0,nums.size()-1,k);    
  38.     }    
  39. };   

 

 

 

 

分治法

這篇文章將討論:
1)  分治策略的思想和理論
2)  幾個分治策略的例子:合並排序,快速排序,折半查找,二叉遍歷樹及其相關特性。

說明:這幾個例子在前面都寫過了,這里又拿出來,從算法設計的策略的角度把它們放在一起來比較,看看分治是如何實現滴。
由於內容太多,我將再花一篇文章來寫4個之前沒有寫過的分治算法:1,大整數乘法   2,矩陣乘法的分治策略   3,最近點對  4,凸包問題,請見下一篇。
好了,切入正題。

---------------------------------------------------------------------------------------------------------------------------------------------------

分治算法是按照下列方案來工作的:
1)將問題的實例划分為幾個較小的實例,最好具有相等的規模(事實上,一般來說就是這樣來分的,而且分為2個實例的居多,注意是遞歸的分!!!)
2)對這些較小的實例求解(一般使用遞歸的方法,但在問題規模足夠小的時候也可以采用采用另一個算法(停止遞歸))
3)如果有必要的話,合並這些較小問題的解,以得到原始問題的解(事實上,一個分治算法的精華就在於合並解的過程)
不要忽視這三句話!!!它是許多分治算法經驗的總結,有助於在分析問題中考慮如何去使用分治算法,提請注意括號里我的注釋!!!
形象的表示一下,截張圖:


大多數都是規模為n的問題,被划分為2個規模為n/2的問題,更一般的情況下,從理論上分析一下:
一個規模為n的實例可以划分為b個規模為n/b的實例,其中a個實例是需要求解的,為了簡化分析,我們假設n是b的冪(每次都可以整的划分),對算法的運行時間,下面的遞推關系式是顯然的:

其中,a,b的含義已經說過了,f(n)表示將求解得到的a個子問題的解合並起來所需要的時間復雜度。
如何根據a,b以及f的階來確定這個算法的時間復雜度呢?有下列主定理:(證明參見算法導論)

看起來似乎是有一點復雜,證明又比較復雜,這樣的式子又不好記。看到這個我也覺得茫然。好吧,根據自己的理解,我來做一點解析,挖掘一下這個式子:
1)b,a,f(n)的含義,這個要弄清楚,要不然恐怕你是無法記住這個結論的。
2)這個式子是一個通用的數學表達式,在計算機的常用算法策略中,它太概括了,我們往往用到的只是它范圍很小的一部分。
分析1:a和b的關系,其實a絕大多數時候都是等於b的吧(因為規模n划分為了b個子規模,需要處理的是a個,參見a,b的含義),a,b的含義告訴我們a <= b(當這是最基本要滿足的條件)。常常要么是 a==b(分成的子規模都要處理,然后去合並),要么是a==1(實際上這是減治的思想,分成了b個子規模,但最終卻可以排除其他的,只在其中一個子規模中去處理)。一般來說就是這樣,所以a,b並不是隨意的。

分析2:其實b要么為2(幾乎所有情況)要么為3(極少數情況)吧,這個分治思想里面就說啦,一般來說都是分為2個規模相等的子規模(當然誰都想分的越多越好,這樣算法就更快,但是現實是問題往往沒有那么高效的算法,找到一個3的分治就已經很不錯了)

分析3:由上面2條可知,a,b的值幾乎就那么幾個(當然我說的是幾乎所有書上可以看到的常見算法案例),所以不用那么擔心。

分析4:f(n)是線性的的情況很多(即d=1的情況是最多的)。再來看看a和b^d比較大小的關系說明了什么:f(n)代表的是合並的復雜度,1<=a <= b,定性的分析,可以知道:

第一種情況:a < b^d
因為1<=a <= b,所以只要d>1,不管a,b是什么(不管怎么划分規模,也不關需要處理幾個規模),總是第一種情況,時間復雜度是n^d。
如果d=1呢,只要a < b(處理的比划分的少),那么還是第一種情況,時間復雜度也是n^d = n

第二種情況:a =  b^d
因為1<=a <= b,所以如果a=b(划分多少處理多少),那么d只能為1才能是這種情況。-----常見的歸並排序都是這樣
而如果a < b,那b就只能<1才能是這種情況,一般很少見。

第三種情況:a  >  b^d
非常少見,我還木有見過這樣的算法,一開始我認為這種情況不可能,但在理論上它是存在的。因為1<=a <= b,所以要滿足這個式子d必須<1 。
從這也可以看出這三個參數之間的關系,事實上是划分的復雜度和合並的復雜度在爭搶復雜度的控制權。

說了這么多,感覺越分析越復雜了嗎,其實不是,把這些分析想清楚,對遞推式的理解就更進一步了,有了上述分析,其實下面幾個常見的遞推式就包含了大多數的算法:
T(n)  =   a * T(n/b)  +  f(n)的常見式子:
1)  T(n)  =  2 * T(n/2) + O(n)      時間復雜度n*log(n)
一般來說分治算法就是這樣,分成2個子規模的問題,需要處理的也是2個,對這兩個子規模合並又是線性的
a = b = 2,    d = 1;        a == b^d 由主定理得n*log(n)
只要a=b,d=1,就都是這個復雜度

2)  T(n)  =  T(n/2) + O(n)        時間復雜度為n,線性的
a = 1,b = 2,d=1(分為2個子規模,但只對一個子規模處理,合並也是線性的)
a < b^d,      時間復雜度是n^d = n
其實質押a < b,d=1,都是這個。

感覺對一個定理解讀了這么多,確實讓它變得更加復雜了,但如果你做了上述思考,相信對這個式子認識也更加深刻了一點。當然,如果你覺得直接記住上面這個公式就可以了,可以無視以上解讀。

----------------------------------------------------------------------------------------------------------------------------------------------------

4.1  合並排序
數據結構里面寫過了,再從分治實現策略的角度想一想:

合並排序遞歸的將規模n分為2個子規模,對2個子規模分別排好序后(划分並處理),再來合並這2個子規模為有序(合並解)。
其遞推關系式是這樣的:

合並解的過程是線性的,即:

由主定理得,時間復雜度是n * logn的。


幾道習題的思考:
7,合並排序穩定嗎?yes
10,也可以不用遞歸的方法來實現合並排序,合並排序是采用遞歸從上往下構造,我們也可以合並2個相鄰序列從底向上構造。這樣就需要不斷地申請一個大的數組,把當前相鄰的2個部分合並起來,直到整個合並。

--------------------------------------------------------------------------------------------------------------------------------------------------

4.2  快速排序
前面數據結構里寫過了,從分治實現策略的解讀來看看:
歸並排序是按照元素在數組里的位置來對它們進行划分的,而快速排序是按照元素的值對它們進行划分。

遞推關系式:

根據主定理得知,時間復雜度和歸並排序一樣。
1)其關鍵在於尋找分裂位置!!!!(2個指針左右掃描,交換。注意這種方法
2)學會快排采用的分區這種思想。

幾道習題的思考:
3,快排穩定嗎? ---- no
8,將一個亂序數組排成所有負數在正數之前。-----借用快排2個指針左右掃描交換的思想,線性時間內完成
9,荷蘭國旗問題------分區思想

---------------------------------------------------------------------------------------------------------------------------------------------------

4.3  折半查找
前面數據結構寫過了,從分治策略的角度看看:
對有序數組采用折半查找:遞歸和非遞歸實現都比較簡單。
分治的遞推式:

根據主定理,它的復雜度是logn。
實際上分治算法一般是分成了幾個子規模就對這幾個子規模處理然后合並結果,然而折半查找分成了2個子規模,每次卻總能排除一個子規模,只對一個子規模進行處理,准確的說,它是一種減治策略。或者說可以把它看做是分治算法的退化。

--------------------------------------------------------------------------------------------------------------------------------------------------

4.4  二叉樹遍歷及相關特性
  很多算法也在前面寫過了,還是從分治策略的角度再來看看它的一些特性:
  二叉樹的定義本身(參見一般的數據結構書),把一顆二叉樹遞歸的定義為了左右子樹構成的樹。這意味着,分治法是典型可以用於解決二叉樹相關問題的策略。
  來看看可以做些什么:
  1)計算二叉樹的高度
  
  注意初始條件是判斷是否為空集返回一個 -1,后面有一個習題說是否可以寫成返回0,然后else里面不加1。這是不行的!!這樣沒有考慮單節點的情況。
  它的遞推關系式:
  
  你能由這個遞歸關系給出它的時間復雜度嗎?想想~
  事實上,可以簡化它為A(n)  =  2 *  A(n / 2) +  1  -------把左右的處理當做一個數量級,不會影響復雜度,根據主定理,它是O(n)的。
  來看看怎么證明它吧----》
  參見第  2)點

  2)應當指出,加法並不是上述算法中執行最多的次數,每得到一個左子樹和右子樹,才執行一次加法(先求max,再執行+1),而比較次數(指檢查樹是否為空,即if語句)顯然不止一次(要判斷根節點是否為空,還要判斷左右子樹是否為空)。
  這里引入內部節點和外部結點的概念(定義略)來分析問題!!!:
  
  顯然對於每個內部節點都要計算一次加法,對於內部節點和外部結點,都要執行一次比較(判斷是否為空)
  可以證明,有n個結點的二叉樹的外部結點個數為  x = n + 1;(如上圖,用數學歸納法或者圖論相關知識都很好證明,證明略)
  根據上述分析,對一個n個頂點的二叉樹求高度,
  加法的操作次數為內頂點個數:n
  比較判斷是否為空的操作次數為內部點加外部點的個數:n + (n + 1) = 2n + 1;
  無論怎么說,該算法是線性的!

  3)其他一些如三種經典的遍歷算法,都可以遞歸的來定義,這些都是數據結構課程的標准組成部分,前面已寫,不再詳述。
  最后指出一點,並不是所有的二叉樹算法都要遍歷左右子樹,例如二叉查找樹的插入查找刪除只需要遍歷一棵(每次總能排除一棵),這種策略應該歸於減可變規模的策略(參見下一章減治策略)。

幾道習題的思考:
1)設計一個分治算法來計算二叉樹的層數。-----------遞歸的返回左右子樹層數較大值+1,注意空樹和單節點時的情況
最后,擴展一下本節最開始的計算二叉樹高度的算法:
一道騰訊面試題:怎么求二叉樹上最遠的2個節點的距離?-------左子樹高度+右子樹高度+2,注意特殊情況(只有一邊右子樹)就行了。

--------------------------------------------------------------------------------------------------------------------------------------------------

總結:
1)分治的思想:一般遞歸來實現,划分子問題,合並子問題的解。
2)主定理,要很熟悉,常見的遞推式應該一眼判斷其復雜度。
3)合並排序,快速排序,折半查找,二叉遍歷樹相關特性,這些都是數據結構的經典內容,之前也都寫過了,代碼參見前面的相關文章。
這里再次復習,從不同的視角來看它們都是如何用用到了分治的策略。這些內容應當非常熟練。


免責聲明!

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



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