算法基礎(Java)--分治算法

前言

此篇博客爲轉載和修改,來源寫在參考資料。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)
複製代碼

參考資料

相關文章
相關標籤/搜索