二叉樹的插入與刪除


二分搜索樹是一顆二叉樹

  1. 每個節點的鍵值大於左孩子
  2. 每個節點的鍵值小於右孩子
  3. 一左右孩子爲根的子樹仍爲二分搜索數

下圖爲二分搜索樹,左邊爲完全二叉樹
在這裏插入圖片描述
堆是一個完全二叉樹,可以用數組表示
二分搜索樹不一定是完全二叉樹,數組表示不方便,設置node節點,來表示這些數據對

1、構建二分搜索樹

public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        //左右子樹
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }
}

2、插入元素

插入60,先跟41 作比較,比41要大,插入到41的右側,
60比58還要大,插到58的右子樹中。
插入相同的元素,用的新的數據覆蓋舊的數據
在這裏插入圖片描述

// 向二分搜索樹中插入一個新的(key, value)數據對
public void insert(Key key, Value value){
    root = insert(root, key, value);
}
// 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
// 返回插入新節點後的二分搜索樹的根
private Node insert(Node node, Key key, Value value){
    if( node == null ){
        count ++;
        return new Node(key, value);
    }
    //這個key是否等於當前傳過來node的key,更新
    //這個value換成新傳過來的value
    if( key.compareTo(node.key) == 0 )
        node.value = value;
    //否則就是這個key小於node的key,
    //向node的左子樹傳入key,value數據對,返回數據是返回給node的左子樹
    else if( key.compareTo(node.key) < 0 )
        node.left = insert( node.left , key, value);
    else    // key > node->key
        node.right = insert( node.right, key, value);

    return node;
}

3、查找元素

查找鍵key所對應的值,
其實要是比根節點小,去左子樹繼續找,遞歸下去
要是比根節點大,就去右子數找,直到直到爲止。

// 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
public Value search(Key key){
    return search( root , key );
}
// 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
// 若value不存在, 則返回NULL
private Value search(Node node, Key key){

    if( node == null )
        return null;
	//看着key是否是需要找的key值
	//如果相等就返回
    if( key.compareTo(node.key) == 0 )
        return node.value;
    //否則要是key值比該根節點的key要小,去它的左子樹繼續尋找    
    else if( key.compareTo(node.key) < 0 )
        return search( node.left , key );
    //否則要是key值比該根節點的key要大,去它的右子樹繼續尋找
    //直到找到爲止  
    else // key > node->key
        return search( node.right, key );
}

4、刪除最大、最小元素

刪除最大值、最小值
要刪除就要先找到最大值和最小值
找最大值,就是二分搜索樹的最左邊的孩子節點,如果左子樹沒有值,就是它的根節點

// 尋找二分搜索樹的最大的鍵值
public Key minimum(){
    assert count != 0;
    Node maxNode = minimum(root);
    return minimum.key;
}
// 返回以node爲根的二分搜索樹的最小鍵值所在的節點
private Node minimum(Node node){
    //就是這個node的左孩子爲空,就是最小值了
    if( node.left == null )
        return node;
    //否則就返回回去,在在這個二叉樹的左孩子繼續找
    return minimum(node.left);
}

找最小值,就是最右邊的子樹的節點,如果右子樹沒有值,就是它的根節點

下面開始刪除最小值
最小值22,22爲根節點,它沒有左子樹了,所以它就是最小值
將22拿出去,將22的右子樹作爲22位置的根節點,就是33作爲根節點,同時維護空間,索引減1
刪除最大值也是同理
在這裏插入圖片描述

// 從二分搜索樹中刪除最小值所在節點
public void removeMin(){
    if( root != null )
        root = removeMin( root );
}
// 刪除掉以node爲根的二分搜索樹中的最小節點
// 返回刪除節點後新的二分搜索樹的根
private Node removeMin(Node node){
    //看當前的節點的左孩子是否爲空
    //就找到最小的節點,就是node節點,然後刪除掉
    if( node.left == null ){
        //看下這個node右子樹節點是不是存在,如果存在
        //就要代替這個node稱爲新的二分搜索樹的根
        //就是成爲node節點的父親節點新的左孩子
        Node rightNode = node.right;
        //維護空間,釋放空間
        node.right = null;
        count --;
        return rightNode;
    }
    //如果不成立,就需要繼續向當前的 node爲根的左孩子二分搜索樹中
    //去查找最小的節點  ,將他刪除,刪除節點在賦值返回給node的左孩子
    node.left = removeMin(node.left);
    return node;
}

同理刪除最大值

// 從二分搜索樹中刪除最大值所在節點
public void removeMax(){
    if( root != null )
        root = removeMax( root );
}
// 刪除掉以node爲根的二分搜索樹中的最大節點
// 返回刪除節點後新的二分搜索樹的根
private Node removeMax(Node node){

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

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

5、刪除任意節點元素

下面刪除二叉樹種任意節點的元素
比如刪除58的節點d
因爲58左右都有孩子節點,

  1. 那麼就要找一個數值代替58,這個值是58的右子樹中最小值s爲59,這樣就可以變爲d的後續替換元素,因爲要維護二分搜索樹的性質
  2. 下面就是把59這樣元素從這個節點s上面刪除掉
  3. 將59變成原來d
  4. 將s節點的右邊指向原來d的右子樹,這裏爲什麼不用d來指向右子樹,因爲右孩子的值不全了
  5. 最後在維護左孩子,s節點的左孩子是原來d的右孩子

然後將d徹底刪除掉,s節點59稱爲新的子樹的根節點
在這裏插入圖片描述

// 從二分搜索樹中刪除鍵值爲key的節點
public void remove(Key key){
    root = remove(root, key);
}
// 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
private class Node {
    private Key key;
    private Value value;
    private Node left, right;

    public Node(Key key, Value value) {
        this.key = key;
        this.value = value;
        left = right = null;
    }
   //將小的賦值的node節點都賦值新的node節點
    public Node(Node node){
        this.key = node.key;
        this.value = node.value;
        this.left = node.left;
        this.right = node.right;
    }
}
// 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
// 返回刪除節點後新的二分搜索樹的根
Node remove(Node node, Key key){
    //如果這個鍵值的節點爲空,說明沒有這個鍵值的節點就返回回去
    if( node == null )
        return null;
    //否則就是要找到的key與當前的node中的key作比較
    if( key.compareTo(node.key) < 0 ){
        //小於這個key,就在node的左孩子中找到這個key,
        //並把結果賦值返回node左孩子
        node.left = remove( node.left , key );
        return node;
    }
    else if( key.compareTo(node.key) > 0 ){
        node.right = remove( node.right, key );
        return node;
    }
    //上面兩種都沒有存在,
    //就要看這個node有幾個孩子
    else{   // key == node->key

        // 待刪除節點左子樹爲空的情況
        //還有一種左右孩子都爲空,那就進入左子樹爲空的情況
        //如果左孩子爲空的話
        //就相當於回到刪除最小值,這就爲什麼要提上面要刪除最小值的原由
 	   //就找到最小的節點,就是node節點,然後刪除掉
        if( node.left == null ){
        	//看下這個node右子樹節點是不是存在,如果存在
        	//就要代替這個node稱爲新的二分搜索樹的根
       	    //就是成爲node節點的父親節點新的左孩子
            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        // 待刪除節點右子樹爲空的情況
        //就相當於刪除最大值
        //同理如上
        if( node.right == null ){
            Node leftNode = node.left;
            node.left = null;
            count--;
            return leftNode;
        }

        // 待刪除節點左右子樹均不爲空的情況
        
        // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
        // 用這個節點頂替待刪除節點的位置
        //把要刪除的node保存下路,要找到一個節點,是這個node節點的後繼
        //這裏就可以重新建立一個node
        Node successor = new Node(minimum(node.right));
        //這個元素要放入二叉樹中維護count
        count ++;

        //爲這個後繼 左右孩子賦值
        //右孩子就是返回來的指針
        //下面調用這個賦值的時候count--也,這個count並沒有改變
        successor.right = removeMin(node.right);
        successor.left = node.left;

        node.left = node.right = null;
        count --;
        //這個successo返回回去作爲新的二分搜索的根節點
        return successor;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章