相對友好的 Tree 教程

樹是什麼

舉個現實生活中的例子,公司的組織架構以下:node

# |ceo|
# / \
# |cto| |cfo|
# / \ / \
# |se| |se| |sm| |sm|
複製代碼

經過上圖能夠獲得一個直觀的感覺,就是明顯的層級關係。從頂端的 CEO ,到僱傭各類職能人員組成公司,就像一棵 「樹」 (顛倒的),從根部開始生長,到開枝散葉。在開發中多少都接觸過樹的概念 ,做爲一種數據結構被普遍得應用在各類計算機語言中,瞭解它的設計思想對往後的開發是很是有幫助的。以上圖公司的組織架構來了解下經常使用的術語,都比較基礎:bash

  • 節點:從 ceo 到研發,都是一個個節點 ;
  • 邊:兩個節點間的聯繫,如 cto 與研發 ;
  • 根節點:架構最上層的節點 ,對應 ceo ;
  • 葉子節點:架構最下層的節點,如研發,沒有子節點 ;
  • 高度:相對葉子節點而言,如 ceo 的高度 -> 研發;+1;
  • 深度:相對根節點而言,如 cto 的深度 -> ceo ; +1;

二叉樹是什麼

二叉樹是一種特殊的樹 ,每一個節點最多隻能有兩個子節點(左子節點、右子節點):數據結構

從上圖能夠看出二叉樹是一組節點的集合,每一個節點有 3 個屬性:架構

  • 當前節點保存的值
  • 左子節點
  • 右子節點

捋清楚這點關係後就能夠動手敲代碼了,從最簡單的開始:ide

public class BinaryTree {
    int value;
    BinaryTree left;
    BinaryTree right;

    public BinaryTree(int value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }

    @Override
    public String toString() {
        return "value: " + value + " left: " + left + " right: " + right;
    }
}
複製代碼

二叉樹 -- 插入

接下來嘗試往二叉樹中插入節點,分別定義插入左子節點和右子節點的方法。規則以下post

  • 若是當前節點沒有左/右子節點,則將建立新的節點做爲當前節點的左/右子節點;
  • 若是當前節點存在左/右子節點,則用新建立的節點 「插入」 到當前節點的左/右子節點位置;

第一點比較好理解,第二點畫個圖:測試

也很好理解,思路有了,代碼就比較好寫了, BinaryTree 類中添加以下方法:ui

public void insertLeft(int value) {
    if (this.left == null) {
        this.left = new BinaryTree(value);
    } else {
        BinaryTree newNode = new BinaryTree(value);
        newNode.left = this.left;
        this.left = newNode;
    }
}

public void insertRight(int value) {
    if (this.right == null) {
        this.right = new BinaryTree(value);
    } else {
        BinaryTree newNode = new BinaryTree(value);
        newNode.right = this.right;
        this.right = newNode;
    }
}
複製代碼

測試下上面寫的代碼,假設要生成的二叉樹以下this

測試:spa

BinaryTree node11 = new BinaryTree(11);
node11.insertLeft(8);
node11.insertRight(16);

BinaryTree node8 = node11.left;
BinaryTree node16 = node11.right;

node8.insertLeft(5);
node8.insertRight(10);
node16.insertRight(18);

BinaryTree node5 = node8.left;
BinaryTree node10 = node8.right;
BinaryTree node18 = node16.right;

System.out.println(node11.value);//11
System.out.println(node8.value);//8
System.out.println(node16.value);//16
System.out.println(node5.value);//5
System.out.println(node10.value);//10
System.out.println(node18.value);//18
複製代碼

二叉樹 -- 遍歷

二叉樹的遍歷分爲深度優先遍歷(Depth-First Search)和廣度優先遍歷 (Breadth-First Search)。

深度優先--先序
F-B-A-D-C-E-G-I-H

代碼實現:

public static void preOrder(BinaryTree currentNode) {
    System.out.println(currentNode.value);
    if (currentNode.left != null) {
        preOrder(currentNode.left);
    }
    if (currentNode.right != null) {
        preOrder(currentNode.right);
    }
}
複製代碼
深度優先--中序
A-B-C-D-E-F-G-H-I

代碼實現:

public static void inOrder(BinaryTree currentNode) {
    if (currentNode.left != null) {
        inOrder(currentNode.left);
    }
    System.out.println(currentNode.value);
    if (currentNode.right != null) {
        inOrder(currentNode.right);
    }
}
複製代碼
深度優先--後序
A-C-E-D-B-H-I-G-F

代碼實現:

public static void postOrder(BinaryTree currentNode) {
    if (currentNode.left != null) {
        postOrder(currentNode.left);
    }
    if (currentNode.right != null) {
        postOrder(currentNode.right);
    }
    System.out.println(currentNode.value);
}
複製代碼
廣度優先--分層
F-B-G-A-D-I-C-E-H

代碼實現:

public static void bfs(BinaryTree currentNode) {
    Queue<BinaryTree> queue = new LinkedList<>();
    if (currentNode == null) {
        return;
    }
    queue.clear();
    queue.add(currentNode);
    while (!queue.isEmpty()) {
        BinaryTree node = queue.remove();
        System.out.println(node.value);
        if (node.left != null) {
            queue.add(node.left);
        }
        if (node.right != null) {
            queue.add(node.right);
        }
    }
}
複製代碼

這裏藉助隊列來實現廣度優先遍歷,假如要遍歷下面這棵二叉樹:

整個流程以下:

二叉查找樹

二叉查找樹就是排序後的二叉樹,通俗理解以下:

左子樹上全部節點的值 < 根節點的值 < 右子樹上全部節點的值
複製代碼
- A:不知足條件,子樹 7-5-8-6 與子樹 2-1-3 順序顛倒; - B:知足條件; - C:不知足條件,4 應該在 5 的左邊;

二叉查找樹 -- 插入

假如要按照 50,76,21,4,32,100,64,52 順序生成一顆樹,流程以下

代碼以下:

public static BinaryTree insert(BinaryTree currentNode, int value) {
        if (currentNode == null) {
            return new BinaryTree(value);
        } else if (value < currentNode.value) {
            currentNode.left = insert(currentNode.left, value);
        } else if (value > currentNode.value) {
            currentNode.right = insert(currentNode.right, value);
        }
        return currentNode;
    }
複製代碼

二叉查找樹 -- 查找

在上面例子的基礎上,假設要查找 52 這個值是否存在,流程以下:

代碼以下:

public static boolean find(BinaryTree currentNode, int value) {
        if (currentNode == null) {
            return false;
        }
        if (value < currentNode.value) {
            return find(currentNode.left, value);
        }
        if (value > currentNode.value) {
            return find(currentNode.right, value);
        }
        return true;
}
複製代碼

二叉查找樹 -- 刪除

刪除操做相對繁瑣,須要考慮幾種狀況:

  • 被刪除的節點無子節點;
# |50| |50|
# / \ / \
# |30| |70| (DELETE 20) ---> |30| |70|
# / \ \
# |20| |40| |40|
複製代碼

這種狀況很簡單,直接刪除便可

  • 被刪除的節點有一個子節點;
# |50| |50|
# / \ / \
# |30| |70| (DELETE 30) ---> |20| |70|
# / 
# |20|
複製代碼

這種狀況也相對簡單,做爲 20 父節點的 30 被刪除了,那麼此時 20 就由 30 的父節點 50 來 「接管」。

  • 被刪除的節點有兩個子節點;
# |50| |50|
# / \ / \
# |30| |70| (DELETE 30) ---> |40| |70|
# / \ /
# |20| |40| |20|
複製代碼

這種狀況稍微麻煩一些,首先查找節點 30 的右子樹中最小的值(40),並用它替換節點 30 的值,再將節點 40 刪掉。不太好理解?下面會有更詳細的分解介紹。

代碼以下:

public static BinaryTree delete(BinaryTree currentNode, int value) {
    if (currentNode == null) {
        return null;
    }
    if (value < currentNode.value) {
        currentNode.left = delete(currentNode.left, value);
    } else if (value > currentNode.value) {
        currentNode.right = delete(currentNode.right, value);
    } else {
        if (currentNode.left == null && currentNode.right == null) {
            System.out.println("deleting leaf node" + value);
            return null;
        } else if (currentNode.left == null) {
            System.out.println("no left node; deleting " + value);
            return currentNode.right;
        } else if (currentNode.right == null) {
            System.out.println("no right node; deleting " + value);
            return currentNode.left;
        } else {
            currentNode.value = minimumValue(currentNode.right);
            delete(currentNode.right, currentNode.value);
            System.out.println("with two child node; deleting " + value);
        }
    }
    return currentNode;
}

public static int minimumValue(BinaryTree root) {
    if (root.left != null) {
        return minimumValue(root.left);
    }
    return root.value;
}
複製代碼

作個測試,經過下面代碼生成一棵二叉查找樹:

BinaryTree root = new BinaryTree(15);
insert(root, 10);
insert(root, 20);
insert(root, 8);
insert(root, 12);
insert(root, 17);
insert(root, 25);
insert(root, 19);
bfs(root);

# |15|
# / \
# |10| |20|
# / \ / \
# |8| |12| |17| |25|
# \
# |19|
複製代碼

刪無子節點的節點 8 :

delete(root, 8);
bfs(root);

# |15|
# / \
# |10| |20|
# \ / \
# |12| |17| |25|
# \
# |19|
複製代碼

再刪帶一個子節點的節點 17:

delete(root, 17);
bfs(root);

# |15|
# / \
# |10| |20|
# \ / \
# |12| |19| |25|
複製代碼

再刪帶兩個子節點的節點 15:

delete(root, 15);
bfs(root);

# |19|
# / \
# |10| |20|
# \ \
# |12| |25|
複製代碼

刪帶兩個子節點的節點的過程用流程表示:

Enjoy --☺

相關文章
相關標籤/搜索