1. 程式人生 > 其它 >二叉排序樹(二叉搜尋樹)

二叉排序樹(二叉搜尋樹)

技術標籤:演算法與資料結構二叉排序樹二叉搜尋樹二叉樹java資料結構

一、二叉排序樹概述

二叉排序樹(Binary Sort Tree),又稱二叉查詢樹(Binary Search Tree),亦稱二叉搜尋樹。對於二叉排序樹的任何一個非葉子節點,要求左子節點的值比當前節點的值小右子節點的值比當前節點的值大(如果有相同的值,則該節點放在左子節點或右子節點都可)。在一般情況下,二叉排序樹的查詢效率比連結串列結構要高。

如下圖所示就是一個二叉排序樹:

在這裡插入圖片描述

二、二叉排序樹的基本操作

2.1 建立和遍歷二叉排序樹

【案例描述】

給定如下陣列:

arr = {7, 3, 10, 12, 5, 1
, 9}

其二叉排序樹如下圖所示:

在這裡插入圖片描述

要求:

  1. 建立二叉排序樹
  2. 中序遍歷二叉排序樹

【程式碼實現】

/**
 * @Description 建立和遍歷二叉排序樹
 */
public class No1_BinarySortTree {
    public static void main(String[] args) {
        int[] arr = {7, 3, 10, 12, 5, 1, 9};
        BinarySortTree tree = new BinarySortTree(null);
        for (int item : arr){
            tree.
addNode(tree.new Node(item)); // 新增節點 } tree.preOrder(); // 前序遍歷 } } class BinarySortTree{ private Node root; // 根節點 public BinarySortTree(Node node){ this.root = node; } // 新增節點到二叉排序樹中 public void addNode(Node node){ if (root == null){ root =
node; }else{ root.addNode(node); } } // 中序遍歷:左根右 public void preOrder(){ if (root != null){ root.preOrder(); } } /** * 內部類:節點 */ class Node{ int value; Node left; // 指向左子節點 Node right; // 指向右子節點 public Node(int value){ this.value = value; } // 新增節點到二叉排序樹中 public void addNode(Node node){ if (node != null){ if (node.value >= this.value){ // 如果大於當前節點的值 if (this.right != null){ // 如果當前節點存在右子節點 this.right.addNode(node); // 右子節點遞迴新增 }else{ this.right = node; } }else{ if (this.left != null){ // 如果小於當前節點的值 this.left.addNode(node); }else{ this.left = node; } } } } // 中序遍歷:根左右 public void preOrder(){ System.out.println(this); // 根 if (this.left != null){ // 左子節點 this.left.preOrder(); } if (this.right != null){ // 右子節點 this.right.preOrder(); } } @Override public String toString() { return "Node{" + "value=" + value + '}'; } } }

2.2 刪除二叉排序樹節點

【案例描述】

刪除二叉排序樹中的指定節點,要求刪除節點後仍是一個二叉排序樹。

【思路分析】

考慮待刪除節點有三種情況,每種情況下對節點的刪除的具體操作是不同的。下面將分別介紹刪除這三種節點的思路:

  1. 待刪除節點為葉子節點。

    在這裡插入圖片描述

    這種情況下的節點刪除最簡單,只需要找到目標節點以及它的父節點,然後藉助父節點刪除目標節點即可。

  2. 待刪除節點為只有一棵子樹的節點。

    在這裡插入圖片描述

    如果目標節點只有一個子節點,要想刪除目標節點之後依然是一個二叉排序樹。只需要讓目標節點的父節點原來指向目標節點的指標指向目標節點的子節點即可(例如:上圖目標節點 3 是節點 7 的左子節點,只需要讓節點 7 的左指標改為指向目標節點的子節點 5 即可實現刪除目標節點)。

    在實際操作的過程中,需要注意判斷下面兩點:

    • 目標節點是父節點的左子節點還是右子節點;
    • 目標節點的唯一子節點是左子節點還是右子節點。
  3. 待刪除節點為有兩棵子樹的節點。

    在這裡插入圖片描述

    如果目標節點有兩個子樹,則將目標節點的右子樹中的最小的節點的資訊拷貝至目標節點,然後將最小節點刪除,即可實現刪除目標節點(例如:上圖目標節點為的值 3,它的右子樹中最小節點的值為 4,則將目標節點的值修改為 4,然後刪除最小節點即可)。

    在實際操作過程中,要注意一點:通過目標節點的右子樹中最小節點的父節點才能刪除最小節點,因此還需要找到最小節點的父節點。

【程式碼實現】

class BinarySortTree{

    private Node root;  // 根節點

    public BinarySortTree(Node node){
        this.root = node;
    }
    // 刪除指定節點
    public void deleteBST(int key){
        if (root != null){
            if (root.value == key){ // 如果目標節點是根節點,直接刪除
                root = null;
            }else{  // 如果不是根節點,就呼叫刪除方法
                root.deleteBST(key);
            }
        }
    }

    /**
     * 內部類:節點
     */
    class Node{
        int value;  // 節點的值
        Node left;  // 指向左子節點
        Node right; // 指向右子節點

        public Node(int value){
            this.value = value;
        }

        /**
         * 查詢指定節點
         * @param key   指定節點的值
         */
        public Node searchNode(int key) {
            if (this.value == key) {    // 首先判斷是否是本節點
                return this;
            } else {
                if (this.value > key) { // 如果小於本節點,向左遞迴
                    if (this.left != null) {    // 如果左節點存在才遞迴
                        return this.left.searchNode(key);
                    }
                }else{  // 不是小於,就是大於本節點,向右遞迴
                    if (this.right != null) {   // 右節點存在才遞迴
                        return this.right.searchNode(key);
                    }
                }
                return null;
            }
        }

        /**
         * 查詢指定節點的父節點
         * @param key   指定節點的值
         */
        public Node searchParentNode(int key){
            if ((this.left !=null && this.left.value == key) || 
                (this.right != null && this.right.value == key)){ // 如果當前節點的某個子節點是目標節點
                return this;    // 返回當前節點
            }else{
                if (key < this.value && this.left != null){  // 如果目標值小於當前節點
                    return this.left.searchParentNode(key);
                }else if (key > this.value && this.right != null){  // 如果目標值大於當前節點
                    return this.right.searchParentNode(key);
                }else{
                    return null;
                }
            }
        }

        /**
         * 刪除節點
         * @param key 待刪除節點的值
         */
        public boolean deleteBST(int key){
            if (this.value == key){ // 如果等於當前節點,就執行刪除節點操作
                deleteNode(root, key);  // 執行刪除操作
                return true;
            }else{
                if (key < this.value && this.left != null){  // 如果目標值小於當前節點,就向左子樹遞迴查詢
                    return this.left.deleteBST(key);
                }else if (key > this.value && this.right != null){  // 如果大於當前節點,就向右子樹遞迴查詢
                    return this.right.deleteBST(key);
                }else{
                    return false;
                }
            }
        }

        /**
         * 刪除節點的操作過程
         * @param root  根節點
         * @param key   待刪除節點的值
         */
        public void deleteNode(Node root, int key){
            Node targetNode = root.searchNode(key);  // 首先找到要刪除的節點
            if (targetNode != null){
                Node parentNode = root.searchParentNode(key);   // 找到待刪除節點的父節點
                if (targetNode.left == null &&  targetNode.right == null){  // 情況一:目標是一個葉子節點
                    if (parentNode.left.value == key){  // 判斷目標節點是父節點的左子節點還是右子節點
                        parentNode.left = null; // 直接刪除即可
                    }else{
                        parentNode.right = null;
                    }
                }else if (targetNode.left != null && 
                          targetNode.right != null){ // 情況三:目標節點有兩個子樹
                    /* 找出目標節點右子樹中的最小值 */
                    Node rightTreeNode = targetNode.right;  // 獲取目標節點的右子節點
                    if (rightTreeNode.left == null){       // 如果右子節點沒有左子樹,說明右子節點就是最小節點
                        targetNode.value = rightTreeNode.value; // 將最小節點複製到目標結點
                        targetNode.right = null;    // 刪除最小節點
                    }else{  // 如果目標節點的右子節點有左子樹,就往下找
                        while (rightTreeNode.left.left != null){    // 要刪除最小節點,所以找最小節點的父節點
                            rightTreeNode = rightTreeNode.left;
                        }
                        targetNode.value = rightTreeNode.left.value;
                        rightTreeNode.left = null;
                    }
                }else{  // 情況二:目標節點只有一個子樹
                    if (targetNode.left != null){   // 如果目標節點只有左子樹
                        if (parentNode.left != null && parentNode.left.value == key){  // 如果目標節點是父節點的左子樹
                            parentNode.left = targetNode.left;  // 讓父節點的左指標指向目標節點的左子樹
                        }else if (parentNode.right != null && parentNode.right.value == key){  // 如果目標節點是父節點的右子樹
                            parentNode.right = targetNode.left; // 讓父節點的右指標指向目標節點左子樹
                        }
                    }else{  // 如果目標節點只有右子樹
                        if (parentNode.left != null && parentNode.left.value == key){  // 如果目標節點是父節點的左子樹
                            parentNode.left = targetNode.right;
                        }else if (parentNode.right != null && parentNode.right.value == key){  // 如果目標節點是父節點的右子樹
                            parentNode.right = targetNode.right;
                        }
                    }
                }
            }
        }
        @Override
        public String toString() {
            return "Node{" +
                    "value=" + value +
                    '}';
        }
    }
}

【附】

二叉排序樹的完整程式碼請訪問我的 Gitee 倉庫