1. 程式人生 > 實用技巧 >演算法與資料結構——樹

演算法與資料結構——樹

樹的解題方法總結

以前在演算法與資料結構的課上學習的時候,聽的迷迷糊糊的,理論學的多,實踐操作的少,這幾天刷了下leetcode裡樹相關的題目,有一些體會,遂記錄。

關於樹的題目一般是遍歷題目,而樹的遍歷又分為兩種:深度優先遍歷(DFS,Deep Firstly Search)和寬度優先遍歷(BFS,Breath Firstly Search)

深度優先

就是儘可能往下找到葉子節點,其遍歷方式按照對父親節點和兄弟節點們的訪問順序不同可以分為三種(前序,中序,後續),這一點以前學習的時候知識很散,沒有歸納起來,所以搞不清這些名詞之間的關係。由於dfs的特點(儘可能往下),所以解這類題目一般用棧(資料結構)+遞迴(演算法),前序和後序其實是兩種類似的,前序是:頭——>左——>右,後序是:左——>右——>頭,可以說是相反的一種進棧順序,程式碼如下:

class Solution {
    List<Integer> list=new LinkedList<>();
    public List<Integer> preorder(Node root) {
        if(root==null)  return list;
        list.add(root.val);
        for(Node c:root.children)
            preorder(c);
        return list;
    }
}
class Solution {
    List<Integer> list=new LinkedList();
    public List<Integer> postorder(Node root) {
        if(root==null)  return list;
        for(Node node:root.children){
            postorder(node);
        }
        list.add(root.val);
        return list;
    }
}
  • 看程式碼的話非常簡潔和相似,只是頭節點的入棧順序改變下。如果是二叉樹的話,判斷下子樹會不會為空,然後將for迴圈改為左右子樹遞迴即可
  • 接下來看中序遍歷,中序遍歷只針對二叉樹,因為其遍歷順序是左——>中——>右,對於n叉樹來說沒有這種方式,題目和程式碼如下。這裡為了程式碼寫的少點,將葉子節點的子節點(為空)也進行了一次遍歷,還是比較好理解的。
  • 二叉樹的中序遍歷
class Solution {
    List<Integer> list=new LinkedList<Integer>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root!=null){
            inorderTraversal(root.left);
            list.add(root.val);
            inorderTraversal(root.right);
        }
        return list;
    }
}

總的來說,用遞迴加棧解決樹的深度遍歷還是很清晰明瞭的。但是遞迴在一些極限情況下(遞迴層數過多)效能不那麼好,所以還有一種通用的迭代方式來解決,題目和上面的一樣,解法如下。

  • 前序
class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> list=new LinkedList<>();
        if(root==null)  return list;
        Stack<Node> stack=new Stack<>();
        stack.add(root);
        while(!stack.isEmpty()){
            Node x=stack.pop();           
            if(x!=null){
                list.add(x.val);
                Collections.reverse(x.children);
                for(Node n:x.children)
                    stack.add(n);
            }         
        }
        return list;
    }
}
  • 後序
class Solution {
    public List<Integer> postorder(Node root) {
        LinkedList<Integer> list=new LinkedList();
        Stack<Node> stack=new Stack();
        if(root==null) return list;
        stack.push(root);
        while(!stack.isEmpty()){
            Node x=stack.pop();
            list.addFirst(x.val);
            for(Node c:x.children)
                if(c!=null)
                    stack.push(c);
           }
        return list;
    }
}
  • 中序
class Solution {
    class Node{
        TreeNode treenode;
        int color;
        public Node(TreeNode a,int b){
            treenode=a;
            color=b;
        }
    }
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list=new ArrayList<>();
        if(root==null)
            return list;
        Stack<Node> stack=new Stack<>();
        Node node=new Node(root,0);
        stack.add(node);
        while(!stack.isEmpty()){
            Node x=stack.pop();
            if(x.color==1)
                list.add(x.treenode.val);
            else{
                x.color=1;
                if(x.treenode.right!=null)
                    stack.add(new Node(x.treenode.right,0));
                stack.add(x);
                if(x.treenode.left!=null)
                    stack.add(new Node(x.treenode.left,0));
            }
        }
        return list;
    }
}

可以看出,迭代的方法複雜一些,尤其是中序,但是方法上來說思想一致,僅需變動下順序,所以可以說是通用模板。

寬度優先

由於其特點為從左到右,從上到下的遍歷方式,因此其不像深度優先有三種遍歷方式,只有一種,即不分前中後序。根據其特點,一般採用佇列(資料結構)+迭代(演算法)的方式來解決。

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        LinkedList<Node> A = new LinkedList<>();
        LinkedList<List<Integer>> result=new LinkedList<>();
        if(root==null)  return result;
        A.add(root);
        while (!A.isEmpty()) {
            List<Integer> list=new LinkedList<>();
            int sl=A.size();
            for(int i=0;i<sl;i++){
                list.add(A.getFirst().val);
                A.addAll(A.getFirst().children);
                A.remove(0);
            }
            result.add(list);
        }
        return result;
    }
}

也可以採用遞迴的方式

class Solution {
    LinkedList<List<Integer>> result=new LinkedList<>();
    public List<List<Integer>> levelOrder(Node root) {
        //遞迴方式:1,建立層,新增層元素
        if(root!=null)
            dfs(root,0);
        return result;
    }
    void dfs(Node node, int level){
        if(result.size()<=level)
            result.add(new LinkedList<>());
        result.get(level).add(node.val);
        for(Node x:node.children){
            dfs(x,level+1);
        }
    }
}

以上就是對樹遍歷的四種方式總結,後續做圖方面演算法再進行擴充。