二叉樹基礎---你必須會的二叉樹知識

二叉樹作爲面試中高頻出現的數據結構,本文將介紹二叉樹基礎。
對於二叉樹,最常見的面試問題就是:請描述一棵二叉樹,二叉樹的XX遍歷的遞歸算法與非遞歸算法。這些都是基礎,也就是說是必須要掌握的內容。
本篇博文就致力於梳理清楚,這些基礎問題。希望有所收穫。

很多人會問,二叉樹在我們平時開發中用的很少啊,爲什麼面試官那麼愛問,以及算法題中出現的那麼平凡,很多人甚至沒有辦法立刻想到二叉樹的應用有哪些?那麼就從這個問題開始本文吧。

1. 實際開發中哪裏用到二叉樹了呢?

  • 用的最多的應該是平衡二叉樹,有種特殊的平衡二叉樹紅黑樹,查找、插入、刪除的時間複雜度最壞爲O(logn)
  • 堆排序,堆排序是利用這種數據結構而設計的一種排序算法。
    • 堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。
  • Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虛擬內存的管理,都是通過紅黑樹去實現的。
  • 還有哈夫曼樹編碼方面的應用,他是一種壓縮算法。
  • B-Tree,B±Tree在文件系統中的應用。

2.二叉樹

花花醬說過,樹是用遞歸定義的數據結構,凡是樹的題目都可以用遞歸做。當然遞歸本質上還是利用了程序執行時候的入棧與出棧操作。我們也可以在代碼中模擬這種操作,因此樹的題目同樣可以使用非遞歸的方式進行解答

用語言描述一棵二叉樹?

二叉樹是遞歸定義的數據結構,其結點最多隻能有2個子節點,他們被稱爲左右子節點。
寫到這裏,我發現了一篇寫的非常好的遍歷二叉樹文章(見參考文獻二),既然前人已經寫了那麼好的文章了,那我就直接參考。下面記錄二叉樹的三種遍歷方式。
定義二叉樹

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    public TreeNode(int val) {
        this.val = val;
    }
}

前序遍歷(遞歸)

public void preOrder(TreeNode root) {
   if (root == null) {
        return;
    }
    System.out.println(root.val);
    preOrder(root.left);
    preOrder(root.right);
}

中序遍歷(遞歸)

public void inOrder(TreeNode root) {
    if (root==null) {
        return;
    }
    inOrder(root.left);
    System.out.println(root.val);
    inOrder(root.right);
}

後序遍歷(遞歸)

public void postOrder(TreeNode root) {
    if (root==null) {
        return;
    }
    postOrder(root.left);
    postOrder(root.right);
    System.out.println(root.val);
}

前序遍歷(非遞歸)

public void preOrder(TreeNode root) {
	if(root==null) {
		return;
	}
	Stack<TreeNode> stack = new Stack();
	while(root!=null) {
		System.out.println(root.val);
		stack.push(root);
		root = root.left;
	}
	while(!stack.isEmpty()) {
		TreeNode top = stack.peek();
		TreeNode node = top.right;
		stack.pop();
		while(node!=null) {
			System.out.println(node.val);
			stack.push(node);
			node = node.left;
		}
	}
}

中序遍歷(非遞歸)

public void inOrder(TreeNode root) {
	if(root==null) {
		return;
	}
	Stack<TreeNode> stack = new Stack();
	while(root!=null) {
		stack.push(root);
		root = root.left;
	}
	while(!stack.isEmpty()) {
		TreeNode top = stack.peek();
		TreeNode node = top.right;
		System.out.println(top.val);
		stack.pop();
		while(node!=null) {
			stack.push(node);
			node = node.left;
		}
	}
}

後序遍歷(非遞歸)

後續遍歷相對複雜,原因就在於出棧的情況不一樣了。

在先序和中序遍歷過程中,只要左子樹處理完畢實際上棧頂元素就可以出棧了,但是後續遍歷情況不同,什麼是後續遍歷?只有左子樹和右子樹都遍歷完畢纔可以處理當前節點,這是後續遍歷,那麼我們該如何知道當前節點的左子樹和右子樹都處理完了呢?

顯然我們需要某種方法記錄下遍歷的過程,實際上我們只需要記錄下遍歷的前一個節點就足夠了。

如果我們知道了遍歷過程中的前一個節點,那麼我們就可以做如下判斷了:

  • 如果前一個節點是當前節點的右子樹,那麼說明右子樹遍歷完畢可以pop了
  • 如果前一個節點是當前節點的左子樹而且當前節點右子樹爲空,那麼說明可以pop了
  • 如果當前節點的左子樹和右子樹都爲空,也就是葉子節點那麼說明可以pop了
public void postOrder(TreeNode root) {
    if(root!=null) {
        return;
    }
    Stack<TreeNode> stack = new Stack();
    TreeNode before = null;
    while(root!=null) {
        stack.push(root);
        root = root.left;
    }
    while(!stack.isEmpty()) {
        TreeNode top = stack.peek();
        if(top.left==null&&top.right==null ||
            before==top.right ||
            before==top.left&&top.right==null) {
            System.out.println(top.val);
            before = top;
            stack.pop();
        } else {
            TreeNode node = top.right;
            while(node!=null) {
                stack.push(node);
                node = node.left;
            }
        }
    }
}

下一篇文章,主要討論的是堆排序。

參考文獻

  1. 二叉樹實際應用場景有哪些?
  2. 徹底理解二叉樹的遍歷
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章