今天咱們來種一棵"樹"

Image by pangziai on Pixabayjava

今天是一年一度的植樹節,說到樹,我就想到了《西遊記》中的古樹精,今年下半年。。。好了好了,不皮了,咱們直接開花。今天就趁着植樹節來種一棵咱們程序員的「樹」吧。node

什麼是「樹」?

在種樹以前,咱們先來了解下什麼是樹?看個例子:程序員

不對不對,放錯了,應該是下面這個:算法

維基百科對於樹的定義是:數據庫

在計算機科學中,(英語:tree)是一種抽象數據類型(ADT)或是實現這種抽象數據類型的數據結構,用來模擬具備樹狀結構性質的數據集合。它是由 n(n>0)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。數組

說白了,只要是形如上圖的數據結構就叫樹。bash

二叉樹

天然界中有「迎客鬆」、「軒轅柏」這樣出名的樹,咱們今天要種的樹也是咱們IT圈裏面的扛把子——「二叉樹」。數據結構

二叉樹是數據結構中一種重要的數據結構,也是樹表家族最爲基礎的結構之一。post

二叉樹的特色是每一個結點最多有兩個子樹,左邊的叫作左子樹,右邊的叫作右子樹,二叉樹要麼爲空,要麼由根結點、左子樹和右子樹組成,而左子樹和右子樹分別是一棵二叉樹。 下面這棵樹就是一棵二叉樹。學習

常見術語

  • 結點(node):上圖中的 A、B、C。。。就是一個結點
  • 結點的度:一個結點含有的子樹的個數稱爲該結點的度
  • 樹的度:一棵樹中,最大的結點的度稱爲樹的度
  • 深度:對於任意節點 n , n 的深度爲從根到 n 的惟一路徑長,根的深度爲 0;
  • 高度:對於任意節點 n , n 的高度爲從 n 到一片樹葉的最長路徑長,全部樹葉的高度爲 0;

種樹

接下來的代碼均用 Java 展示,完整代碼可在公衆號「01二進制」後臺回覆「二叉樹」查看。

如今咱們就開始種一棵如上圖的樹吧。根據定義,咱們瞭解到,結點是一棵二叉樹最重要的元素,而做爲一個結點,必須知足如下條件:

  1. 根結點
  2. 左子樹和右子樹

所以咱們能夠建立一個結點類(TreeNode):

class TreeNode {
    String data;
    TreeNode left;
    TreeNode right;

    TreeNode(String data) {
        this.data = data;
    }
}
複製代碼

有了這個結點類以後咱們就能夠建立出一個如上圖的樹了:

// 建立二叉樹
private TreeNode createTree() {
    TreeNode root = new TreeNode("A");
    root.left = new TreeNode("B");
    root.right = new TreeNode("C");
    root.left.left = new TreeNode("D");
    root.left.right = new TreeNode("E");
    root.right.left = new TreeNode("F");
    root.right.right = new TreeNode("G");
    return root;
}
複製代碼

你看,一棵樹不就種好了嗎?

樹的特徵

做爲一個新時代的好青年,咱們不能把樹種好了就無論不顧了,得負起責任啊。最起碼你要知道本身的樹長啥樣吧。因此接下來咱們就來看看如何獲取樹的特徵

咱們描述一我的的特徵每每都是從他的外形、長相、身材入手的,描述一顆樹也是如此,咱們接下來將會從下面幾個角度去獲取樹的特徵:

  • 判斷是否爲空
  • 獲取樹的高度
  • 獲取樹中的結點個數

判斷是否爲空

// 判斷是否爲空
public boolean isEmpty(TreeNode root) {
    return root == null;
}
複製代碼

獲取樹的高度

這裏咱們採用遞歸的方式,由於樹的高度是由其子樹決定的,因此咱們只須要比較左、右子樹的高度而後取最大值便可,代碼以下:

// 獲取樹的高度
private int height(TreeNode root) {
    if (root == null)
        return 0;//遞歸結束:空樹高度爲0
    else {
        int i = height(root.left);
        int j = height(root.right);
        return (i < j) ? (j + 1) : (i + 1);
    }
}
複製代碼

獲取樹的結點大小(個數)

一個樹的結點個數一定爲其左子樹的結點個數 + 右子樹的結點個數 +1,所以咱們一樣能夠用遞歸很是簡單的將其表示出來:

// 獲取結點大小
private int size(TreeNode root) {
    if (root == null) {
        return 0;
    } else {
        return 1 + size(root.left) + size(root.right);
    }
}
複製代碼

遍歷二叉樹

上一節中咱們獲取到了樹的特徵,但這遠遠不夠,特徵只能粗略的描述一個二叉樹,想要詳細的瞭解一個二叉樹,咱們必須對其進行「遍歷」。

遍歷二叉樹是指以必定的次序訪問二叉樹中的每一個結點。所謂訪問結點是指對結點進行各類操做的簡稱(最簡單的就是訪問該結點的值)。

而訪問結點無非就 3 個操做:

  • 訪問結點自己(N)
  • 訪問左結點(L)
  • 訪問右結點(R)

咱們以根結點爲核心,若是先訪問根節點在訪問左右結點成爲前序遍歷;若是先訪問左結點而後根結點最後右結點則爲中序遍歷;若最後訪問根節點則爲後序遍歷

所以上圖的遍歷結果爲:

前序:A B D E C F G
中序:D B E A F C G
後序:D E B F G C A
複製代碼

固然這只是咱們本身根據定義手寫出來的,該如何用代碼表示出來呢?

前序遍歷

根據定義咱們知道,前序遍歷就是先訪問根結點,而後是左結點和右結點,所以用遞歸能夠很簡單的展示這一過程:

// 前序遍歷二叉樹
private void preOrder(TreeNode root) {
    if (root != null) {
        System.out.print(root.data + " ");
        preOrder(root.left);
        preOrder(root.right);
    }
}
複製代碼

中序遍歷

同理咱們也能夠很快的知道中序和後序遍歷了:

// 中序遍歷二叉樹
private void inOrder(TreeNode root) {
    if (root != null) {
        inOrder(root.left);
        System.out.print(root.data + " ");
        inOrder(root.right);
    }
}
複製代碼

後序遍歷

// 後序遍歷二叉樹
private void postOrder(TreeNode root) {
    if (root != null) {
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.data + " ");
    }
}

複製代碼

這裏遍歷二叉樹的代碼均是以遞歸方法完成的,非遞歸遍歷二叉樹的過程較爲麻煩,因爲篇幅限制,這裏就不放出來了。若想查看非遞歸版本的代碼可在公衆號「01二進制」後臺回覆「二叉樹」查看。

層序遍歷

事實上,以人來看一個樹的話大多都是一層一層的看,這種遍歷方式稱爲層序遍歷。具體思路:用隊列實現,先將根節點入隊列,只要隊列不爲空,而後出隊列,並訪問,接着講訪問節點的左右子樹依次入隊列。

// 層序遍歷
private void levelTravel(TreeNode root) {
    if (root == null) return;
    Queue<TreeNode> q = new LinkedList<TreeNode>();
    q.add(root);
    while (!q.isEmpty()) {
        TreeNode temp = q.poll();
        System.out.print(temp.data + " ");
        if (temp.left != null) q.add(temp.left);
        if (temp.right != null) q.add(temp.right);
    }
}

複製代碼

擴展

二叉樹的做用

二叉樹是種很是強大的數據結構,那她到底強大在哪裏呢?咱們來看下面這個簡單的例子:

該例來自於Aditya Bhargava 的《算法圖解》

假如說,你想從微博中找到一我的,最快的方法通常是二分查找。但當有新用戶增長時,都得將新用戶插入組別內再排序,由於二分查找法只會有序的組別纔有用。

因此就有人想了,若是能夠將新增的用戶插入到數組的正確位置就行了,這樣就不須要在插入後在排序了。

因而就有人設計了一種二叉樹:對於每一個結點,左子節點的值都比它小,右子節點值都比它大。以下圖所示:

Maggie排在David後面,所以向右找Maggie,排在Manning前面,所以向左找。

這個運行時間,用大O表示法,平均運行時間是O(log2 N),最差運行時間是O(N)

在有序數組查找時,與二分查找法運行時間相同。

二叉樹對比二分查找法優點在於<以下圖>:

v2-0b55e5ee0452f94d476a70ffa0237779_hd

能夠看出插入和刪除速度都快。二叉樹的缺點也很明顯,就是不能隨機訪問。

二叉樹的擴展

上述例子其實就是一個二叉查找樹的簡易使用,那麼除此以外二叉樹還有什麼常見的應用呢?下面列出四個,有興趣的小夥伴能夠本身搜索相關的文檔閱讀學習。

  • B- 樹,是一種特殊的二叉樹,數據庫經常使用它來存儲數據。
  • B+ 樹,B+樹是 B-樹的一種變體,主要用於磁盤文件組織、數據索引和數據庫索引等場景。
  • 紅黑樹,二叉平衡樹的一種,Java 中的 TreeSet ,TreeMap,HashMap就是這種數據結構。
  • 堆,是一種徹底二叉樹,能夠實現優先隊列。

END

種一棵樹最好的時間是十年前,然後是如今。咱們經常去後悔過去的事情。遺憾本身犯的錯誤,遺憾本身錯過的機會。雖然現實很讓人感到惋惜,但其實不少事情早就該作了,再懊惱又有什麼用呢?與其無故抱怨還不如行動起來。當你感到遺憾時,纔是行動的最好時機!

相關文章
相關標籤/搜索