二叉搜索樹

  1. 什麼是二叉查找樹
    樹的概念這裏不做說明,在假設我們都知道二叉樹的情況下,那麼二叉查找樹就是符合以下情況的一種特殊的二叉樹:對於二叉查找樹的某個節點X,他的左子樹中所有的節點值都小於X節點的值,右子樹所有節點的值都大於X節點的值。例如下面這顆樹,就是一個二叉搜索樹。該樹結構中,16左邊的節點都小於16,16右邊的節點大於16,其他節點同理。那下面我們分別討論二叉查找樹的增刪改查幾個重要方法。
    在這裏插入圖片描述
  2. 查找
    因爲二叉搜索樹的特點,所以我們查找一個節點是不是在二叉搜索樹中,一般有兩步,以上面的二叉搜索樹爲例,查找元素9:1. 訪問根節點,如果根節點的值正好和查詢的元素相等,那麼直接返回,這裏9不等於16,接續第二步。2. 判斷9是否大於16,是則從16的右邊開始查,否則,從16的左邊查,這裏9小於16,從16的左邊查,定位到8,發現9大於8,從8的右邊查,找到9。很顯然,二叉搜索樹的查詢時間複雜度,是優於鏈表的。下面給出代碼實現。
   /**
     * 以node爲根的二叉搜索樹是否包含元素e
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e) {
        // 遞歸終止條件
        if (null == node) {
            return false;
        }

        if (e.compareTo(node.e) == 0) {
            return true;
        } else if (e.compareTo(node.e) < 0) {
            return contains(node.left, e);
        } else  {
            return contains(node.right, e);
        }
    }
  1. 插入
    瞭解了查詢的過程,插入就是可以用contains的方法遍歷樹,如果找到對應的節點,在做一次更新,否則,就插入到遍歷的路徑上的最後一個空節點。下面給出代碼。
/**
     * 遞歸添加元素
     * @param node
     * @param e
     * @return 返回插入新節後二叉搜索樹的根
     */
    private Node add(Node node, E e) {
        if (null == node) {
            size ++;
            return new Node(e);
        }

        if (e.compareTo(node.e) < 0) {
            // 添加返回值將new出來的節點掛載到樹上
            node.left = add(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            node.right = add(node.right, e);
        }

        return node;
    }
  1. 刪除
    刪除相對於增加和查找元素,比較複雜,因爲要考慮到這樣幾種情況。
    case1:以下圖爲例,刪除元素30,那麼我們直接刪除30就可以
    在這裏插入圖片描述
    case2:刪除元素8,這個時候8沒有左孩子,所以刪除元素8以後,我們可以直接讓8的右孩子替代8,成爲16的左孩子。
    在這裏插入圖片描述
    case3:還是刪除元素8,這個時候8沒有右孩子,那麼我們可以讓8的左孩子替換8成爲16的左孩子。
    在這裏插入圖片描述
    case4:還是刪除元素8,這次8既有左孩子,也有右孩子。這種情況我們一般的策略是找到元素8右孩子中的最小的元素,也就是9,讓他替代8的位置。代碼如下。
    在這裏插入圖片描述
 /**
     * 刪除指定元素
     * @param node 節點
     * @param e 待刪除的元素
     * @return 刪除後的根節點
     */
    private Node remove(Node node, E e) {
        if (null == node) {
            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 (null == node.left) {
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 右子樹爲空
            if (null == node.right) {
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待刪除節點左右子樹都不爲空
            // 找到待刪除元素的右子樹的最小節點,替換待刪除元素
            Node successor = minNum(node.right);
            // successor現在替換了node的位置,就要將successor從原來的位置刪除
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = null;
            node.right = null;
            return successor;
        }
    }
  1. 關於二叉搜索樹的其他操作,例如深度優先遍歷、廣度優先遍歷、查詢最大最小元素等代碼,基本都是用了遞歸的方法,在這裏做一個記錄,就不贅述了。
package com.tree.BST;



import java.util.LinkedList;
import java.util.Queue;

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

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

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

    }

    private Node root;
    private int size;

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

    private int size() {
        return size;
    }

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

    /**
     * 添加元素
     * @param e
     */
    public void add(E e) {
        root = add(root, e);
    }

    /**
     * 遞歸添加元素
     * @param node
     * @param e
     * @return 返回插入新節後二叉搜索樹的根
     */
    private Node add(Node node, E e) {
        if (null == node) {
            size ++;
            return new Node(e);
        }

        if (e.compareTo(node.e) < 0) {
            // 添加返回值將new出來的節點掛載到樹上
            node.left = add(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            node.right = add(node.right, e);
        }

        return node;
    }

    /**
     * 是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e) {
        return contains(root, e);
    }

    /**
     * 以node爲根的二叉搜索樹是否包含元素e
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e) {
        if (null == node) {
            return false;
        }

        if (e.compareTo(node.e) == 0) {
            return true;
        } else if (e.compareTo(node.e) < 0) {
            return contains(node.left, e);
        } else  {
            return contains(node.right, e);
        }
    }

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

    /**
     * 前序遍歷 先訪問根節點,在訪問左右子樹, 遞歸算法
     * @param node
     */
    private void preOrder(Node node) {
        if (null == node) {
            return;
        }
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

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

    /**
     * 中序遍歷 先訪問左子樹,在訪問根節點和右子樹, 遞歸算法
     * @param node
     */
    private void inOrder(Node node) {
        if (null == node) {
            return;
        }
        inOrder(node.left);
        //Gson gson = new Gson();
        //String json = gson.toJson(node);
        System.out.println(node.e);
        inOrder(node.right);
    }

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

    /**
     * 後序遍歷 先訪問左子樹,在訪問右子樹和根節點, 遞歸算法
     * @param node
     */
    private void postOrder(Node node) {
        if (null == node) {
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

    /**
     * 層序遍歷
     */
    public void levelOrder() {
        if (null == root) {
            return;
        }

        Queue<Node> queue = new LinkedList<>();
        Queue<Node> levelQueue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            Node cur = queue.remove();
            System.out.print(cur.e + " ");
            if (null != cur.left) {
                queue.add(cur.left);
            }
            if (null != cur.right) {
                queue.add(cur.right);
            }
            if(queue.isEmpty()){
                System.out.println();
                queue.addAll(levelQueue);
                levelQueue.clear();
            }
        }
    }

    /**
     * 獲取二叉搜索樹的最大節點
     */
    public E maxNum() {
        if (size == 0) {
            throw new IllegalArgumentException("BSI is empty");
        }

        return maxNum(root).e;
    }

    /**
     * 遞歸獲取最大元素,從樹的右邊遍歷
     * @param node
     * @return
     */
    private Node maxNum(Node node) {
        if (null == node.right) {
            return node;
        }

        return maxNum(node.right);
    }

    /**
     * 獲取二叉搜索樹的最小節點
     */
    public E minNum() {
        if (size == 0) {
            throw new IllegalArgumentException("BSI is empty");
        }

        return minNum(root).e;
    }

    /**
     * 遞歸獲取最小元素,從樹的左邊遍歷
     * @param node 根節點
     * @return 以node爲根的最小節點
     */
    private Node minNum(Node node) {
        if (null == node.left) {
            return node;
        }

        return minNum(node.left);
    }

    /**
     * 移除最大元素
     * @return 最大元素值
     */
    public E removeMax() {
        E e = maxNum();
        removeMax(root);
        return e;
    }

    /**
     * 移除最大元素
     * @param node 根節點
     * @return 移除後根節點
     */
    private Node removeMax(Node node) {
        // 如果右子樹爲空,則根節點爲最大節點,刪除根節點
        if (null == node.right) {
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    /**
     * 刪除最小元素
     * @return 最小元素的值
     */
    public E removeMin() {
        E e = minNum();
        removeMin(root);
        return e;
    }

    /**
     * 刪除最小元素
     * @param node
     * @return 刪除最小節點後的新的二叉搜索樹
     */
    private Node removeMin(Node node) {
        // 如果左子樹爲空,則根節點爲最小節點,刪除根節點
        if (null == node.left) {
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    /**
     * 刪除指定元素
     * @param e
     */
    public void remove(E e) {
        root = remove(root, e);
    }

    /**
     * 刪除指定元素
     * @param node 節點
     * @param e 待刪除的元素
     * @return 刪除後的根節點
     */
    private Node remove(Node node, E e) {
        if (null == node) {
            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 (null == node.left) {
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 右子樹爲空
            if (null == node.right) {
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待刪除節點左右子樹都不爲空
            // 找到待刪除元素的右子樹的最小節點,替換待刪除元素
            Node successor = minNum(node.right);
            // successor現在替換了node的位置,就要將successor從原來的位置刪除
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = null;
            node.right = null;
            return successor;
        }
    }

    /**
     * 查找key的floor值
     * @param key
     * @return
     */
    public E floor(E key) {
        if (key.compareTo(minNum()) < 0) {
            return null;
        }

        return floor(root, key);
    }

    /**
     * 查找key的floor值
     * @param node
     * @param key
     * @return
     */
    private E floor(Node node, E key) {
        if (null == node) {
            return null;
        }

        // 如果key和node值相等,node就是key對應的floor節點
        if (key.compareTo(node.e) == 0) {
            return key;
        }

        // 如果key比node的值小,那麼對應的floor節點肯定在node的左子樹
        if (key.compareTo(node.e) < 0) {
            return floor(node.left, key);
        }

        // 如果key比node的值大,node有可能是key對應的floor節點,也有可能不是
        E floor = floor(node.right, key);
        if (floor != null) {
            return floor;
        }

        return node.e;
    }

    /**
     * 查找key的ceil值
     * @param key
     * @return
     */
    public E ceil(E key) {
        if (key.compareTo(maxNum()) > 0) {
            return null;
        }

        return ceil(root, key);
    }

    /**
     * 查找key的ceil值
     * @param node
     * @param key
     * @return
     */
    private E ceil(Node node, E key) {
        if (null == node) {
            return null;
        }

        // 如果key和node值相等,node就是key對應的ceil節點
        if (key.compareTo(node.e) == 0) {
            return key;
        }

        // 如果key比node的值大,那麼對應的ceil節點肯定在node的右子樹
        if (key.compareTo(node.e) > 0) {
            return ceil(node.right, key);
        }

        // 如果key比node的值小,node有可能是key對應的ceil節點,也有可能不是
        E ceil = ceil(node.left, key);
        if (ceil != null) {
            return ceil;
        }

        return node.e;
    }

    /**
     * 求樹的深度
     * @return 樹的深度
     */
    public int depth() {
        return depth(root);
    }

    /**
     * 求樹的深度
     * @return node爲節點的書的樹的深度
     */
    private int depth(Node node){
        if (null == node) {
            return 0;
        }
        int leftDepth = depth(node.left);
        int rightDepth = depth(node.right);
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

文章代碼參考《數據結構與算法分析》和慕課網liuyubobobo老師的算法大師帶你玩轉數據結構的課程。這裏作爲學習筆記整理。推薦一篇寫的很不錯的博客:淺談算法和數據結構: 七 二叉查找樹

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