資料結構之二叉搜尋樹—Java實現

Camork發表於2019-03-07

二叉查詢樹(Binary Search Tree,簡稱BST)是一棵二叉樹,它的左子節點的值比父節點的值要小,右節點的值要比父節點的值大。它的高度決定了它的查詢效率。

在理想的情況下,二叉查詢樹增刪查改的時間複雜度為O(logN)(其中N為節點數),最壞的情況下為O(N)。當它的高度為logN+1時,我們就說二叉查詢樹是平衡的。

資料結構之二叉搜尋樹—Java實現

首先定義二叉樹節點

class TreeNode {
    TreeNode(int value) {
        this.value = value;
    }

    TreeNode left;
    TreeNode right;
    int value;
}
複製程式碼

本文中rootNode為建立二叉搜尋樹的root節點,在類初始化階段賦值。

private final TreeNode rootNode;

public BinarySearchTree(int value) {
    rootNode = new TreeNode(value);
}
複製程式碼

插入操作:

首先從root節點開始迴圈遍歷,

  • 如果插入節點等於當前root節點,直接結束不做任何處理。
  • 如果插入節點小於當前root節點,從左節點開始查詢。 (root=root.left)
  • 如果插入節點大於當前root節點,從右節點開始查詢。 (root=root.right)
  • 噹噹前節點的左節點或者右節點為空時,插入新節點,結束過程。

上程式碼:

public TreeNode insert(int value) {
    TreeNode currentRoot = rootNode;

    TreeNode newNode = new TreeNode(value);
    while (true) {
        if (value == currentRoot.value) {
            return null;
        }
        else if (value < currentRoot.value) {
            if (currentRoot.left != null) {
                currentRoot = currentRoot.left;
            }
            else {
                return currentRoot.left = newNode;
            }
        }
        else {
            if (currentRoot.right != null) {
                currentRoot = currentRoot.right;
            }
            else {
                return currentRoot.right = newNode;
            }
        }
    }
}
複製程式碼

查詢操作:

類似於插入操作:

  • 如果插入節點等於root節點,查詢完成
  • 如果插入節點小於root節點,從left節點開始查詢。 (root=root.left)
  • 如果插入節點大於root節點,從right節點開始查詢。(root=root.right)

上程式碼:

public TreeNode get(int value) {
    TreeNode currentRoot = rootNode;

    while (true) {
        if (currentRoot == null) {
            return null;
        }

        if (value == currentRoot.value) {
            return currentRoot;
        }
        else if (value < currentRoot.value) {
            currentRoot = currentRoot.left;
        }
        else {
            currentRoot = currentRoot.right;
        }
    }
}
複製程式碼

遍歷操作:

遍歷分為

  • 前序:根節點放在左節點的左邊        (→左→右)
  • 中序:根節點放在左節點和右節點的中間(左→→右)
  • 後序:根節點放在右節點的右邊        (左→右→

中序遍歷的程式碼:

public void getTreeByInOrder(List<TreeNode> list, TreeNode root) {
    if (root != null) {
        getTreeByInOrder(list, root.left);
        list.add(root);
        getTreeByInOrder(list, root.right);
    }
}
複製程式碼

前序後序和其類似,就不貼程式碼了

層次遍歷(也可以叫做廣度優先遍歷):

思路:

  1. 由根從上往下迴圈遍歷,利用佇列(queue)先進先出的原則,每訪問一個節點之後,將其左右子節點入隊。這樣,同一層的所有節點肯定先於下一層的節點先訪問

  2. 如果需要分層儲存節點呢?(比如需要換行列印):問題的難點是如何知道本層已經遍歷完了,可以使用兩個指標last和nextLast,一個指向當前層的最後一個節點,一個指向下一層的最後一個節點,這樣難點轉換成了last和nextLast的更新。當節點入隊時,更新nextLast(設定nextLast為佇列的最後一個節點),因為佇列中的最後一個節點一定是下一層的最後節點。

上程式碼:

public List<List<Integer>> printTreeByHierarchy() {
    TreeNode currentRoot = rootNode;

    Queue<TreeNode> queue = new LinkedList<>();

    TreeNode last = currentRoot;
    TreeNode nextLast = null;

    queue.offer(currentRoot);

    List<List<Integer>> lists = new ArrayList<>();
    List<Integer> nodes = new ArrayList<>();
    while (!queue.isEmpty()) {
        currentRoot = queue.poll();

        nodes.add(currentRoot.value);

        if (currentRoot.left != null) {
            queue.offer(currentRoot.left);
            nextLast = currentRoot.left;
        }

        if (currentRoot.right != null) {
            queue.offer(currentRoot.right);
            nextLast = currentRoot.right;
        }

        if (last == currentRoot) {//因為last是當前層的最後一個節點,如果等式成立,說明這層已經遍歷完
            last = nextLast;

            lists.add(nodes);
            nodes = new ArrayList<>();
        }
    }

    System.out.println(lists);
    return lists;
}
    
複製程式碼

刪除操作:

資料結構之二叉搜尋樹—Java實現

刪除操作比較麻煩,需要分4種情況考慮。我們以上面這顆樹作為參考:

  1. 如果刪除節點含有左右子節點:需要用刪除節點的後繼節點(以中序遍歷)取代刪除節點的位置。----比如節點刪除20,需要用26代替20的位置
  2. 如果刪除節點只含有單個節點(或左或右):需要用刪除節點的左(右)節點取代刪除節點的位置。----比如節點刪除27,需要用26代替27的位置
  3. 如果刪除節點無子節點(是葉子節點):直接刪除。

上程式碼:

public void remove(int value) {
    TreeNode currentRoot = rootNode;

    while (true) {
        if (currentRoot == null) {
            return;//沒找到,返回或者拋異常
        }

        if (value < currentRoot.value) {
            currentRoot = currentRoot.left;
        }
        else if (value > currentRoot.value) {
            currentRoot = currentRoot.right;
        }
        else {
            TreeNode replacement;//代替者
            if (currentRoot.left == null && currentRoot.right == null) {//無雙子節點
                resetParentNode(currentRoot, null);
            }
            else if (currentRoot.left != null && currentRoot.right != null) {//雙子節點
                replacement = getSuccessor(currentRoot);

                replacement.left = currentRoot.left;
                replacement.right = currentRoot.right;

                resetParentNode(replacement, null);       //重設後繼節點的父節點
                resetParentNode(currentRoot, replacement);//重設正在移除節點的父節點
            }
            else if (currentRoot.left != null) {
                resetParentNode(currentRoot, currentRoot.left);  //
            }                                                    //
            else {                                               //單節點
                resetParentNode(currentRoot, currentRoot.right); //
            }                                                    //

            return;
        }
    }
}
複製程式碼

所有程式碼:BinarySearchTree.java

如果文章中有分析錯誤的地方或者值得改進的地方,歡迎大家指出。

參考文章:

美團技術部落格---紅黑樹深入剖析及Java實現

相關文章