資料結構與演算法:二叉排序樹

小高飛發表於2020-10-13

二叉排序樹

二叉排序樹(Binary Sort Tree),又稱二叉查詢樹(Binary Search Tree),亦稱二叉搜尋樹。是資料結構中的一類。在一般情況下,查詢效率比連結串列結構要高。

二叉排序樹的定義

  • 當左子樹不為空時,左子樹上的所有節點值都小於左子樹的根節點值

  • 當右子樹不為空時,右子樹上的所有節點值都小於右子樹的根節點值

  • 如果二叉樹中有相同值節點時,可以放在它的左子節點或右子節點(如果不是開發需要,儘量不要有相同值的節點)

資料結構與演算法:二叉排序樹

 

插入操作

步驟:

(1) 判斷根節點是否為空,如果為空則將插入節點設定為根節點

(2.1) 判斷插入節點值是否小於當前節點值,如果小於則往左節點走

(2.2) 當往左節點走時判斷左節點是否為空,如為空則將左節點設定為插入節點,不為空則跳到步驟(2)

(3.1) 判斷插入節點值是否大於當前節點值,如果大於則往右節點走

(3.2) 當往右節點走時判斷右節點是否為空,如為空則將右節點設定為插入節點,不為空則跳到步驟(2)

下圖是將 [6, 2, 7, 1, 4, 8] 陣列按順序插入二叉排序樹的過程圖示:

資料結構與演算法:二叉排序樹

程式碼實現

BSTNode root;//二叉排序樹的根節點

public void add(BSTNode node){
    //如果根節點為空則,則將傳入節點設定為根節點
    if (root == null){
        root = node;
    }else {
        add(node, root);
    }
}

/**
 * 在二叉排序樹中新增節點
 * @param node 新增的節點
 * @param pointer 輔助指標節點,初始指向根節點
 */
public void add(BSTNode node, BSTNode pointer){
    if (node == null){
        return;
    }

    if (pointer.value > node.value){//指標節點值大於新增節點值時
        //如果指標節點的左節點剛好為空,則將新增節點插入到該左節點
        if (pointer.left == null){
            pointer.left = node;
        }else {
            //如果不是則繼續往左節點走
            add(node, pointer.left);
        }
    }else {//指標節點值小於新增節點值時
        //如果指標節點的右節點剛好為空,則將新增節點插入到該右節點
        if (pointer.right == null){
            pointer.right = node;
        }else {
            //如果不是則繼續往右節點走
            add(node, pointer.right);
        }
    }
}

 

查詢操作

步驟:

(1) 判斷當前節點是否是查詢節點,如果是則直接返回當前節點

(2) 判斷當前節點值是否大於查詢節點值,如果大於則往左節點查詢,跳到步驟(1)

(3) 判斷當前節點值是否小於查詢節點值,如果小於則往右節點查詢,跳到步驟(1)

下圖是從二叉排序樹中查詢值為4的節點的圖示:

資料結構與演算法:二叉排序樹

程式碼實現

//根據value值查詢節點
public BSTNode searchNode(int value){
    if (root == null){
        return null;
    }
    return searchNode(value, root);
}

/**
 * 根據value值查詢節點
 * @param value 查詢的節點
 * @param node 查詢的樹
 * @return
 */
public BSTNode searchNode(int value, BSTNode node){
    //如果當前節點的值等於value時,則返回該節點
    if (node.value == value) {
        return node;
    } else if (node.value > value){//當前節點的值大於value時
        //如果該節點的左節點為空,則表示二叉排序樹內沒有該值的節點,返回空
        if (node.left == null)
            return null;
        //左節點不為空,繼續往左子樹查詢
        return searchNode(value, node.left);
    }else {//當前節點的值小於value時
        //如果該節點的右節點為空,則表示二叉排序樹內沒有該值的節點,返回空
        if (node.right == null)
            return null;
        //右節點不為空,繼續往右子樹查詢
        return searchNode(value, node.right);
    }
}

 

刪除操作

刪除節點可能有的3中狀態:

  • 刪除節點是葉子節點

  • 刪除節點只有左子樹為空或右子樹為空

  • 刪除節點左子樹和右子樹都為空

步驟:

(1) 判斷刪除節點值是否小於當前節點值,如小於則往左節點走

(2) 判斷刪除節點值是否大於當前節點值,如大於則往右節點走

(3) 當刪除節點值等於當前節點值時,即當前節點時要刪除的節點,判斷當前節點是什麼狀態

(3.1) 當前節點是葉子節點時,則直接刪除當前節點

(3.2) 當前節點左子樹為空時,則將右節點頂上,代替當前節點位置

(3.3) 當前節點右子樹為空時,則將左節點頂上,代替當前節點位置

(3.4) 當前節點左子樹和右子樹都不為空時,則將左子樹的最大值節點取出後刪除,再用最大值節點頂替當前節點位置

資料結構與演算法:二叉排序樹

程式碼實現

/**
 * 根據value值刪除節點
 * 刪除節點可能有的3種狀態:
 * 1.該節點是葉子節點
 * 2.該節點只有左子樹或只有右子樹
 * 3.該節點左子樹和右子樹都有
 * @param value 要刪除節點的value值
 */
public BSTNode delete(int value, BSTNode node){
    if (value < node.value){//當查詢節點值小於當前節點值
        //向左子樹遞迴遍歷,並將刪除後的新的左子樹連線到左節點位置代替原先左子樹
        node.left = delete(value, node.left);
        //返回刪除後新的樹
        return node;
    }else if(value > node.value){//當查詢節點值大於當前節點值
        //向右子樹遞迴遍歷,並將刪除後的新的右子樹連線到右節點位置代替原先右子樹
        node.right = delete(value, node.right);
        //返回刪除後新的樹
        return node;
    }else {//當查詢節點值等於當前節點值時,即當前節點就是要刪除的節點
        //刪除節點時葉子節點的狀態
        if (node.left == null && node.right == null){
            //直接將該節點設為空
            return null;
        }
        //刪除節點左子樹為空,右子樹不為空的狀態
        else if (node.left == null && node.right != null){
            //儲存刪除節點的右子樹
            BSTNode rightSubTree = node.right;
            //將刪除節點的右子樹設為空,使得該節點能夠儘早被垃圾回收
            node.right = null;
            //返回刪除節點的右子樹,連線到刪除節點的父節點
            return rightSubTree;
        }
        //刪除節點右子樹為空,左子樹不為空的狀態
        else if (node.right == null && node.left != null){
            BSTNode leftSubTree = node.left;
            node.left = null;
            return leftSubTree;
        }
        //刪除節點的左子樹和右子樹都不為空的狀態
        //這裡我們使用的是左子樹的最大值節點代替的方法
        else {
            //獲取左子樹的最大值節點並從左子樹中刪除它
            BSTNode max = max(node.left);
            //將該最大值節點的左子樹和右子樹設定為該節點的左子樹和右子樹
            max.left = delMax(node.left);
            max.right = node.right;
            //將刪除節點的左子樹和右子樹設為空,使得該節點能夠儘早被垃圾回收
            node.left = null;
            node.right = null;
            //執行完刪除操作後,返回以最大值節點為根節點的新的樹,連線的刪除節點的父節點
            return max;
        }
    }
}

/**
 * 查詢傳入節點樹下value值最大的節點並刪除該節點
 * @param node
 * @return
 */
public BSTNode delMax(BSTNode node){
    if (node.right != null){
        node.right = delMax(node.right);
        return node;
    }else {
        BSTNode leftSubTree = node.left;
        node.left = null;
        return leftSubTree;
    }
}
/**
 * 查詢傳入節點樹下value值最大的節點並放回該節點
 * 在二叉排序樹中最大值的節點就是最右葉子節點
 * @param node
 * @return
 */
public BSTNode max(BSTNode node){
    BSTNode max = node;
    while (max.right != null){
        max = max.right;
    }
    return max;
}

 

完整程式碼

public class BinarySortTreeDemo {
    public static void main(String[] args) {
        int array[] = {13,7,8,3,29,6,1};
        BinarySortTree binarySortTree = new BinarySortTree();
        for (int i=0; i<array.length; i++){
            binarySortTree.add(new BSTNode(array[i]));
        }
        binarySortTree.midOrder();

        System.out.println("刪除後二叉順序樹的節點:");
        binarySortTree.delete(13);
        binarySortTree.delete(7);
        binarySortTree.delete(1);
        binarySortTree.delete(29);
        binarySortTree.delete(6);

        binarySortTree.midOrder();
    }
}

//二叉排序樹
class BinarySortTree{
    BSTNode root;

    public void setRoot(BSTNode root){
        this.root = root;
    }

    //新增節點
    public void add(BSTNode node){
        //如果根節點為空則,則將傳入節點設定為根節點
        if (root == null){
            root = node;
        }else {
            add(node, root);
        }
    }

    /**
     * 在二叉排序樹中新增節點
     * @param node 新增的節點
     * @param pointer 指標節點,初始指向根節點
     */
    public void add(BSTNode node, BSTNode pointer){
        if (node == null){
            return;
        }

        if (pointer.value > node.value){//指標節點值大於新增節點值時
            //如果指標節點的左節點剛好為空,則將新增節點插入到該左節點
            if (pointer.left == null){
                pointer.left = node;
            }else {
                //如果不是則繼續往左節點走
                add(node, pointer.left);
            }
        }else {//指標節點值小於新增節點值時
            //如果指標節點的右節點剛好為空,則將新增節點插入到該右節點
            if (pointer.right == null){
                pointer.right = node;
            }else {
                //如果不是則繼續往右節點走
                add(node, pointer.right);
            }
        }
    }

    //根據value值查詢節點
    public BSTNode searchNode(int value){
        if (root == null){
            return null;
        }
        return searchNode(value, root);
    }

    /**
     * 根據value值查詢節點
     * @param value 查詢的節點
     * @param node 查詢的樹
     * @return
     */
    public BSTNode searchNode(int value, BSTNode node){
        //如果當前節點的值等於value時,則返回該節點
        if (node.value == value) {
            return node;
        } else if (node.value > value){//當前節點的值大於value時
            //如果該節點的左節點為空,則表示二叉排序樹內沒有該值的節點,返回空
            if (node.left == null)
                return null;
            //左節點不為空,繼續往左子樹查詢
            return searchNode(value, node.left);
        }else {//當前節點的值小於value時
            //如果該節點的右節點為空,則表示二叉排序樹內沒有該值的節點,返回空
            if (node.right == null)
                return null;
            //右節點不為空,繼續往右子樹查詢
            return searchNode(value, node.right);
        }
    }


    public void delete(int value){
        //判斷刪除節點在二叉排序樹中是否存在
        BSTNode node = searchNode(value);
        if (node == null){
            throw new RuntimeException("二叉排序樹內無對應節點");
        }
        //將刪除後新的二叉排序樹更換掉原先二叉排序樹
        root = delete(value, root);
    }

    /**
     * 根據value值刪除節點
     * 刪除節點可能有的3種狀態:
     * 1.該節點是葉子節點
     * 2.該節點只有左子樹或只有右子樹
     * 3.該節點左子樹和右子樹都有
     * @param value 要刪除節點的value值
     */
    public BSTNode delete(int value, BSTNode node){
        if (value < node.value){//當查詢節點值小於當前節點值
            //向左子樹遞迴遍歷,並將刪除後的新的左子樹連線到左節點位置代替原先左子樹
            node.left = delete(value, node.left);
            //返回刪除後新的樹
            return node;
        }else if(value > node.value){//當查詢節點值大於當前節點值
            //向右子樹遞迴遍歷,並將刪除後的新的右子樹連線到右節點位置代替原先右子樹
            node.right = delete(value, node.right);
            //返回刪除後新的樹
            return node;
        }else {//當查詢節點值等於當前節點值時,即當前節點就是要刪除的節點
            //刪除節點時葉子節點的狀態
            if (node.left == null && node.right == null){
                //直接將該節點設為空
                return null;
            }
            //刪除節點左子樹為空,右子樹不為空的狀態
            else if (node.left == null && node.right != null){
                //儲存刪除節點的右子樹
                BSTNode rightSubTree = node.right;
                //將刪除節點的右子樹設為空,使得該節點能夠儘早被垃圾回收
                node.right = null;
                //返回刪除節點的右子樹,連線到刪除節點的父節點
                return rightSubTree;
            }
            //刪除節點右子樹為空,左子樹不為空的狀態
            else if (node.right == null && node.left != null){
                BSTNode leftSubTree = node.left;
                node.left = null;
                return leftSubTree;
            }
            //刪除節點的左子樹和右子樹都不為空的狀態
            //這裡我們使用的是左子樹的最大值節點代替的方法
            else {
                //獲取左子樹的最大值節點並從左子樹中刪除它
                BSTNode max = max(node.left);
                //將該最大值節點的左子樹和右子樹設定為該節點的左子樹和右子樹
                max.left = delMax(node.left);
                max.right = node.right;
                //將刪除節點的左子樹和右子樹設為空,使得該節點能夠儘早被垃圾回收
                node.left = null;
                node.right = null;
                //執行完刪除操作後,返回以最大值節點為根節點的新的樹,連線的刪除節點的父節點
                return max;
            }
        }
    }

    /**
     * 查詢傳入節點樹下value值最大的節點並刪除該節點
     * @param node
     * @return
     */
    public BSTNode delMax(BSTNode node){
        if (node.right != null){
            node.right = delMax(node.right);
            return node;
        }else {
            BSTNode leftSubTree = node.left;
            node.left = null;
            return leftSubTree;
        }
    }
    /**
     * 查詢傳入節點樹下value值最大的節點並放回該節點
     * 在二叉排序樹中最大值的節點就是最右葉子節點
     * @param node
     * @return
     */
    public BSTNode max(BSTNode node){
        BSTNode max = node;
        while (max.right != null){
            max = max.right;
        }
        return max;
    }

    public void midOrder(){
        if (root != null){
            midOrder(root);
        }else {
            System.out.println("二叉順序樹為空,無法遍歷");
        }
    }

    //中序遍歷
    public void midOrder(BSTNode node){
        if (node.left != null){
            midOrder(node.left);
        }
        System.out.println(this);
        if (node.right != null){
            midOrder(node.right);
        }
    }
}

//二叉排序樹節點
class BSTNode{
    int value;
    BSTNode left;
    BSTNode right;

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

    @Override
    public String toString() {
        return "BSTNode{" +
                "value=" + value +
                '}';
    }
}

 

相關文章