此篇博客爲轉載和修改,來源寫在參考資料。java
分治,"分而治之"。從字面上理解就是分---治,把大的問題分紅小問題,解決一個一個小問題,以後把問題的答案合併起來,就獲得大問題的結果。您確定會在想,這思想這麼簡單,你不說我也是知道。歷史上,秦國經過遠交近攻的策略,逐個擊破,最後統一六國不也是分治思想的體現嗎? 如下用一個二叉樹的前序遍歷爲例,對分治思想在代碼上的體現進行說明。node
public class PreoderTraversal {
public class TreeNode{
private int val;
public TreeNode left,right;
public TreeNode(int val){
this.val = val;
this.left = this.right = null;
}
}
public ArrayList<Integer> preodertraversal(TreeNode root){
ArrayList<Integer> result = new ArrayList<Integer>();
//退出的條件
if(root == null){
return null;
}
//分:左子樹與右子樹
ArrayList<Integer> left = preodertraversal(root.left);
ArrayList<Integer> right = preodertraversal(root.right);
//治:把獲得的結果合併起來
result.add(root.val);
result.addAll(left);
result.addAll(right);
return result;
}
}
上面的過程能夠經過一個遞推公式來表示
T(n) = 2T(2/n)+O(1)
2T(2/n) 表示 原來的大問題變成兩個原來一半的問題
O(1)表示 對二叉樹的每一個節點只操做一次。
上面的公式能夠推出 上面前序遍歷的時間複雜度是 O(n)
複製代碼
從以上代碼,能夠看出,分治算法在代碼實現上有如下兩點好處: 1.前序遍歷的結果可用經過一個函數內的ArrayList返回不須要建立全局變量,來存放結果。 2.對於拆分後的問題,運算量大,採用多併發,多核來處理,也是很容易的。具體結合上面代碼來講,對於left、right結果求解,能夠分別啓用一個線程。面試
對於分治的題目不少,爲何選擇下面這兩道題目呢?由於足夠典型,學會了這兩道題,咱們保證,您在與同事、面試官聊起分治算法的時候,他們會認爲您是懂分治算法。算法
分析: 既然咱們使用分治來解決,那就看看問題怎麼拆分呢? 這道題目中是求兩個節點的公共的祖先,很顯然,問題的拆分能夠依據:兩個節點在二叉樹的位置來拆分問題: 都在左子樹上、都在右子樹上、一個邊一個、有一個節點就是根節點bash
一個大的問題拆分四個問題,逐個解決,求出大問題,下面給出 實現代碼併發
public TreeNode getAncestor(TreeNode root,TreeNode node1,TreeNode node2){
if (root == null)
{
return null;
}
//若是有一個節點就是根節點
if(root == node1 || root == node2){
return root;
}
TreeNode left = getAncestor(root.left,node1,node2);
TreeNode right = getAncestor(root.right,node1,node2);
//節點一邊一個
if(left == null && right == null)
{
return root;
}
//節點都在左子樹
if (left != null) {
return left;
}
//節點都在右子樹
if (right != null) {
return right;
}
return null;
}
複製代碼
若是您還不太明白,不要緊,對着分析和代碼多看幾回,就會打通任督二脈的。函數
爲何說這道題有點難度呢?緣由在於二叉樹上有負值的存在。並且最關鍵的是題目只是說遍歷二叉樹,求最大和,並無說是從哪裏出發,若是從根出發就是求: root---->anyNode 根到任意節點的最大和 明顯這題目的意思是 anyNode---->anyNode 任意節點到任意節點的最大和。 採用分治,怎麼拆分呢? 爲三種狀況:左子樹、右子樹、左子樹-->根-->右子樹。不明白,不要緊,看下圖分析。 分析:ui
代碼實現this
public class returnType{
int root2any,any2any;
returnType(int root2any,int any2any){
this.root2any = root2any; //存放上面分析的root-->anyNode
this.any2any = any2any; // anyNode-->anyNode
}
}
public returnType maxSum(TreeNode root){
//若是二叉樹不存在,直接設置成最小值
if(root == null){
return new returnType(Integer.MIN_VALUE,Integer.MIN_VALUE);
}
returnType left = maxSum(root.left);
returnType right = maxSum(root.right);
//結合上面的圖就是求A+B大仍是A+C大呢,
和0作比較就是由於有負數的存在
int root2any =Math.max(0,Math.max(left.root2any,right.root2any))+root.val;
//R=Math.max(D,E)
int any2any = Math.max(left.any2any,right.any2any);
//Math.max(R,A+B+C)
any2any = Math.max(any2any,Math.max(0,left.root2any)+Math.max(0,right.root2any)+ root.val);
return new returnType(root2any,any2any);
}
複製代碼
分治算法其實在最初的快排和歸併排序都接觸過了,若是你上面兩道題目都理解,下面給出歸併排序和快排的代碼在重溫一下,看下感受是否是so easy!! 歸併排序spa
private static Comparable[] aux;
public static void sort(Comparable[] list){
aux = new Comparable[list.length];
sort(list,0,list.length-1);
}
public static void sort(Comparable[] list,int lo,int hi){
if(lo < hi){
return;
}
int mid = lo +(hi-lo)/2;
//分
sort(list,lo,mid);
sort(list,mid+1,hi);
//治
meger(list,lo,mid,hi); //這個是歸併的具體具體過程,咱們這篇介紹分治的重點,在此忽略了
}
複製代碼
快速排序
public static void sort(Comparable[] list){
Collections.shuffle(list); //消除輸入的影響
sort(list,0,list.length-1);
}
public static void sort(Comparable[] list,int lo,int hi){
if(lo < hi){
return;
}
int j = patition(list,lo,hi); //快排中重要的切分,典型有三取樣切分。找出大小爲中間的點
在此忽略了具體實現,有興趣看相關資料
//分
sort(list,lo,j-1);
sort(list,j+1,hi);
}
複製代碼
快排和歸併排序的能夠概括的遞推公式
T(n) = 2T(2/n) +O(n)
時間複雜度是 )O(NlogN)
複製代碼