第5章 二分搜索樹

5-1 爲什麼要研究樹結構

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

5-2 二分搜索樹基礎

在這裏插入圖片描述

  • 二叉樹具有唯一根節點
    在這裏插入圖片描述
  • 二叉樹種每個節點最多有兩個孩子
    在這裏插入圖片描述
  • 二叉樹每個節點最多有一個父親
  • 二叉樹具有天然遞歸結構
    • 每個節點的左子樹也是一棵二叉樹
    • 每個節點的右子樹也是一棵二叉樹
  • 二叉樹不一定是“滿”的
    • 一個節點也可以看作是二叉樹
    • 空也是二叉樹
      在這裏插入圖片描述
      在這裏插入圖片描述
      在這裏插入圖片描述
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;
    }
}

5-3 向二分搜索樹種添加元素

在這裏插入圖片描述

  • 我們的二分搜索樹不包含重複元素
    如果向包含重複元素的話,只需要定義:
    左子樹小於等於節點;或者右子樹大於等於節點
  • 之前講的數組和鏈表,可以有重複元素
  • 二分搜索樹添加元素的非遞歸寫法和鏈表很像
  • 在二分搜索樹方面,遞歸比非遞歸實現簡單
    // 向二分搜索樹中添加新的元素e
    public void add(E e){

        if(root == null){
            root = new Node(e);
            size ++;
        }
        else
            add(root, e);
    }

    // 向以node爲根的二分搜索樹中插入元素e,遞歸算法
    private void add(Node node, E e){
        if(e.equals(node.e))
            return;
        else if(e.compareTo(node.e) < 0 && node.left == null){
            node.left = new Node(e);
            size ++;
            return;
        }
        else if(e.compareTo(node.e) > 0 && node.right == null){
            node.right = new Node(e);
            size ++;
            return;
        }

        if(e.compareTo(node.e) < 0)
            add(node.left, e);
        else //e.compareTo(node.e) > 0
            add(node.right, e);
    }

5-4 改進添加操作:深入理解遞歸終止條件

    // 向二分搜索樹中添加新的元素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);

        return node;
    }

5-5 二分搜索樹的查詢操作


    // 看二分搜索樹中是否包含元素e
    public boolean contains(E e){
        return contains(root, e);
    }

    // 看以node爲根的二分搜索樹中是否包含元素e, 遞歸算法
    private boolean contains(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 contains(node.left, e);
        else // e.compareTo(node.e) > 0
            return contains(node.right, e);
    }

6-6 二分搜索樹的前序遍歷

在這裏插入圖片描述
在這裏插入圖片描述

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 前序遍歷以node爲根的二分搜索樹, 遞歸算法
    private void preOrder(Node node){
        if(node == null)
            return;

        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

5-7 二分搜索樹的中序遍歷和後序遍歷

二分搜索樹的中序遍歷結果是順序的

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 中序遍歷以node爲根的二分搜索樹, 遞歸算法
    private void inOrder(Node node){
        if(node == null)
            return;

        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

後序遍歷可以用來爲二分搜索樹釋放內存

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 後序遍歷以node爲根的二分搜索樹, 遞歸算法
    private void postOrder(Node node){
        if(node == null)
            return;

        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

5-8 深入理解二分搜索樹的前中後序遍歷

通過觀察直接得出遍歷的結果
每個節點在遍歷時需要訪問三次
在這裏插入圖片描述
前序遍歷也稱爲深度優先遍歷
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
總結:前序遍歷的結果爲每個節點第一次訪問,中序遍歷爲第二次,後序遍歷爲第三次。

5-9 二分搜索樹前序遍歷的非遞歸實現

在這裏插入圖片描述
利用棧,先將右孩子入棧,再左孩子入棧,每次訪問棧頂元素。

    // 二分搜索樹的非遞歸前序遍歷
    public void preOrderNR(){

        if(root == null)
            return;

        Stack<Node> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            Node cur = stack.pop();
            System.out.println(cur.e);

            if(cur.right != null)
                stack.push(cur.right);
            if(cur.left != null)
                stack.push(cur.left);
        }
    }
  • 二分搜索樹遍歷的非遞歸實現,比遞歸實現複雜很多
  • 中序遍歷和後序遍歷的非遞歸實現很複雜(尤其後序遍歷)
  • 中序便利和後序遍歷的非遞歸實現,實際應用不廣

5-10 二分搜索樹的層序遍歷

層序遍歷也稱爲廣度優先遍歷
在這裏插入圖片描述
使用隊列輔助遍歷,訪問節點,左孩子入隊,再右孩子入隊,訪問隊首。

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        if(root == null)
            return;

        Queue<Node> q = new LinkedList<>();
        q.add(root);
        while(!q.isEmpty()){
            Node cur = q.remove();
            System.out.println(cur.e);

            if(cur.left != null)
                q.add(cur.left);
            if(cur.right != null)
                q.add(cur.right);
        }
    }

在這裏插入圖片描述

5-11 刪除二分搜索樹的最大元素和最小元素

  • 尋找二分搜索樹的最小元素
    // 尋找二分搜索樹的最小元素
    public E minimum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        Node minNode = minimum(root);
        return minNode.e;
    }

    // 返回以node爲根的二分搜索樹的最小值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }
  • 尋找二分搜索樹的最大元素
    // 尋找二分搜索樹的最大元素
    public E maximum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        return maximum(root).e;
    }

    // 返回以node爲根的二分搜索樹的最大值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }
  • 從二分搜索樹中刪除最小值所在節點, 返回最小值
    // 從二分搜索樹中刪除最小值所在節點, 返回最小值
    public E removeMin(){
        E ret = minimum();
        root = removeMin(root);
        return ret;
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

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

        node.left = removeMin(node.left);
        return node;
    }
    // 從二分搜索樹中刪除最大值所在節點
    public E removeMax(){
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    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;
    }
  • 從二分搜索樹中刪除最大值所在節點
    // 從二分搜索樹中刪除最大值所在節點
    public E removeMax(){
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    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;
    }

5-12 刪除二分搜索樹的任意元素

刪除只有左孩子的節點與刪除最大元素類似
刪除只有右孩子的節點與刪除最小元素類似
難點是刪除一個左右孩子都有的節點,主要方法由Hibbard在1962年提出。

方法1
在這裏插入圖片描述

    // 從二分搜索樹中刪除元素爲e的節點
    public void remove(E e){
        root = remove(root, e);
    }

    // 刪除掉以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{   // e.compareTo(node.e) == 0

            // 待刪除節點左子樹爲空的情況
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 待刪除節點右子樹爲空的情況
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待刪除節點左右子樹均不爲空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;

            return successor;
        }
    }

方法2
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章