【數據結構】二分搜索樹小結

一、基本概念

二分搜索樹:(又:二分查找樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質:

                     若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;

                     若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;

                     它的左、右子樹也分別爲二叉排序樹

 

二、代碼實現

1.首先構建二分搜索數的基本結構

  這裏使用泛型來構建,始終記住二分搜索樹存儲的元素必須有可比較性

  size用來記錄二分搜索的元素個數

public class BST<E extends Comparable<E>> {

    private class Node {
        public E e;
        public Node left, right;

        public Node(E e) {
            this.e = e;
            left = null;
            right = null;
        }
    }

    private Node root;
    private int size;

    public BST() {
        root = null;
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
}

2.實現二分搜索樹的添加方法

//向二分搜索樹添加新的元素e
    public void add(E e) {
        root = add(root, e);
    }

    //向以node爲根的二分搜索樹中插入元素e,遞歸
    //返回插入新節點後的二分搜索樹
    private Node add(Node node, E e) {
        if (node == null) {
            size++;
            return new Node(e);
        }

        if (e.compareTo(node.e) < 0) {
            node.left = add(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            node.right = add(node.right, e);
        }
        //向以node爲根的二分搜索樹中插入元素e,但二分搜索樹的根還是node,所以返回根節點
        return node;
    }

3.實現二叉搜索樹的查詢方法

//判斷是否包含元素e
    public boolean contains(E e) {
        return contain(root, e);
    }

    private boolean contain(Node node, E e) {
        if (node == null)
            return false;
        if (e.compareTo(node.e) == 0) {
            return true;
        } else if (e.compareTo(node.e) < 0) {
            return contain(node.left, e);
        } else{
            return contain(node.right, e);
        }
    }

4.實現二叉樹的前、中、後序遍歷

   1.前序遍歷

//前序遍歷
    public void preOrder() {
        preOrder(root);
    }

    //謙虛遍歷以node爲根的二分搜索樹,遞歸算法
    private void preOrder(Node node) {
        if (node == null)
            return;
        System.out.print(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

  2.中序遍歷

//中序遍歷
    public void inOrder() {
        inOrder(root);
    }

    //中序結果爲從小到大
    private void inOrder(Node root) {
        if (root == null)
            return;
        inOrder(root.left);
        System.out.print(root.e);
        inOrder(root.right);
    }

  3.後序遍歷

//後序遍歷
    public void postOrder() {
        postOrder(root);
    }

    private void postOrder(Node root) {
        if (root == null)
            return;
        postOrder(root.left);
        postOrder(root.right);
        System.out.print(root.e);
    }

  4.測試前、中、後遍歷

     這裏先是構建了一個名爲bst的二分搜索樹,然後分別進行前、中、後序輸出

     有兩個System.out.println是爲了將前、中、後序分行來輸出

public class Main {
    public static void main(String[] args) {
        BST<Integer> bst = new BST<>();
        int[] nums = {5,3,6,8,4,2,1};
        for(int num:nums)
            bst.add(num);
        bst.preOrder();
        System.out.println();
        bst.inOrder();
        System.out.println();
        bst.postOrder();


    }
}

  構建的二分搜索樹如下(每次添加元素都會先從root根結點開始向下比較

    

    測試結果:

    前序:5321468
    中序:1234568            注意!!!   可以發現二分搜索樹的中序遍歷結果是從小到大輸出元素的
    後續:1243865

5.二叉搜索樹的層序遍歷

  層序遍歷顧名思義就是一層一層的遍歷,也稱爲廣度優先遍歷

  這裏藉助隊列實現層序遍歷,首先將root根節點添加到隊列中,然後移除root再將其左右子樹添加.....

public void levelOrder(){
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()){
            Node cur = queue.remove();
            System.out.println(cur.e);
            if(cur.left != null)
                queue.add(cur.left);
            if(cur.right != null)
                queue.add(cur.right);
        }
    }

6.刪除二叉樹的最小、最大值結點

        刪除二叉樹最小值結點思路:一遍遞歸尋找node,left==null; 的結點,該結點就是該二叉搜索樹的最小值結點,此時需要刪除該結點,只需將結點被賦值爲該結點的右子樹即可。

//從二分搜索樹中刪除小值所在結點,返回最小值
    public E removeMin(){
        //miniNode()函數返回該樹的最小值
        E min = miniNode();
        //removeMin()函數刪除該樹的最小值且返回刪除後最小值的根結點
        root = removeMin(root);
        return min;
    }

    private Node removeMin(Node node) {
        //當node結點的左孩子爲空時,可以判斷出此時node爲此二叉搜索樹的最小值,所以刪除該結點
        //使用該node結點的右子樹代替node
        if(node.left==null){
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        node.left = removeMin(node.left);
        return node;
    }

        刪除二叉樹最大值結點思路:一遍遞歸尋找node,right==null; 的結點,該結點就是該二叉搜索樹的最大值結點,此時需要刪除該結點,只需將結點被賦值爲該結點的左子樹即可。
 

public E removeMax(){
        E max = maxNode();
        root = removeMax(root);
        return max;
    }

    private Node removeMax(Node node) {
        if(node.right==null){
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }
        node.right = removeMax(node.right);
        return node;
    }

      注意:刪除二叉搜索樹只包含左孩子和只包含右孩子其實和刪除最小、最大值方法類似。

 

 7.刪除二叉搜索樹的任意結點

       對於上面的第6點,只是刪除最小或最大值的節點,最小和最大值節點對於二叉搜索樹而言絕對是葉子節點。

       然而對於刪除二叉搜索樹的任意節點,此時就要考慮到如果待刪節點左右孩子都不爲空的情況。

       對於下面的二叉搜索樹如果要刪除的節點是58,那麼刪除之後應該是用那個節點來代替58節點的位置呢?

       仔細觀察下圖會發現可以選擇58節點右子樹後節點的最小值!也就是59號節點。所以也就是找到待刪節點右子樹後最小值,將該最小值去替代待刪節點  (前提是,該待刪節點左右孩子都不爲空,如果爲空就是上面第6點講的方法了~)

//刪除以node爲根的二分搜索樹中值爲e的結點
    private Node remove(Node node, E e) {
        if(node == null)
            return null;
        if(e.compareTo(node.e) < 0){
            node.left = remove(node.left,e);
            return node;
        }else if(e.compareTo(node.e) > 0){
            node.right = remove(node.right,e);
            return node;
        } else{
            //如果該待刪節點左子樹爲空
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
            }
            //如果該待刪節點右子樹爲空
            if(node.left == null){
                Node leftNode = node.right;
                node.right = null;
                size--;
                return leftNode;
            }
            //待刪除節點左右子樹都不爲空
            //調用miniNode函數找出node右子樹後最小的節點值
            Node successor = miniNode(node.right);
            //調用removeMin函數刪除node右子樹後最小值的節點
            successor.right = removeMin(node.right);
            successor.left = node.left;
            
            node.left = node.right = null;
            return successor;
        }
    }

     如果將上面代碼看懂了後,你也許會想到對於刪除有左右孩子的節點,也可以採用待刪節點的左孩子的最大值來代替自己!

     如下圖待刪節點58,你也可以選擇用該待刪節點的左子樹中的最大值,就是53來代替自己