二叉查找樹的原理及實現

前言: 

學習了一些數據結構之後,你是不是已經有些小得意了,以爲數據結構就這點東西嘛,不就用好棧、隊列什麼什麼的就好,呵呵,那些只是皮毛。接下來的東西纔會讓你真正認識到數據結構的博大精深,當你看到接下來那些酷炫狂賽的操作時,你會驚歎於數據結構的神奇。在學習那些酷炫的AVL樹,紅黑樹之前,我們先入個門,學習最簡單的動態查找表——二叉查找樹。

二叉查找樹:

二叉查找樹有被稱爲二叉排序樹,它要麼是個空樹,要麼就滿足下列條件:

  1. 若左子樹不空,則左子樹中所有元素的值比根節點小
  2. 若右子樹不空,則左子樹中所有元素的值比根節點大
  3. 二叉查找樹的左右子樹都是二叉查找樹(瘋狂暗示遞歸實現)

如圖,便是一個二叉查找樹

基本實現

存儲實現:

二叉查找樹就使用鏈表實現,這樣能夠很好的理解,每個節點有一個元素存儲值,兩個指針分別指向它的左子樹和右子樹。

template<class elemType>
class binarySearchTree
{
private:
    struct node{
        elemType data;
        node *left;
        node *right;
        
        node(const elemType &d, node *ln = NULL, node *rn = NULL):data(d), left(ln), right(rn){}
    };
    
    node *root;
}

find函數實現:

查找操作從根節點開始,一個一個節點遞歸查找。對於一個特定節點,就四個步驟:

  • 如果該節點是NULL,那麼說明已經查找完了整個樹,還沒有找到。
  • 若該節點的值與要找的值吻合,那麼找到,退出
  • 若要找的值大於該節點的值,查找它的右子樹節點
  • 若要找的值小於該節點的值,查找它的左子樹節點

 

    elemType *find(const elemType &x){
        return find(x, root);
    }
    
    elemType *find(const elemType &x, node *t){
        if(t == NULL){
            cout << "Can not find" << endl;
            return 0;
        }
        if(t -> data == x){
            return &(t -> data);
        }
        if(x < t -> data){
            return find(x, t -> left);
        }
        else{
            return find(x, t -> right);
        }
    }

midOrder操作:

中序遍歷輸出整個樹的值,細心的人其實已經從二叉查找樹做小右大的性質想到了,二叉查找樹中序遍歷的結果必然是一個從小到大序列啊。

    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }

insert操作:

insert操作插入的一定是葉節點

記住這一點,插入的時候一個一個比較就好,從根節點開始,大就向右,小就向左,直到最後的NULL,在NULL處插入節點就好。

    void insert(const elemType &x){
        insert(x, root);
    }
    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                }
            }
        }
    }

remove操作:

remove操作是二叉查找樹中最難的,但你也不要害怕,畢竟你都已經看到這兒了對吧,不看完怎麼行。

想想remove其實就分三種情況:

  • remove的節點是葉節點,那麼二話不說直接刪了就好
  • remove的節點有一個左子樹或者有一個右子樹,那麼直接把這個節點刪去,它的子樹補上來就好
  • remove的節點有左子樹右子樹都有,這就比較麻煩了,是不是已經蒙圈了,不知道怎麼補了,不要慌,且待我慢慢給你講解:刪除了這個節點,這個位置又不能空着,所以我們按正常人的思維就找了個替身來填補這個位置,那麼怎樣選擇這個替身才能保證樹仍保持有序呢?靜下心來想想,要想保持有序,那必須補上來左子樹的最大值或是右子樹的最小值啊,其實就是中序遍歷的該值的相鄰兩個值是吧。
    找左子樹的最大值,其實就是從左子樹的根節點開始,一直往右找,直到末端,那末端的值必然是左子樹的最大值了,同理,找右子樹的最小值,其實就是從右子樹的根節點開始,一直往左找,直到末端,那末端的值必然是右子樹的最小值了。(代碼以找右子樹的最小值爲例寫的)
    找到了替身,使要刪節點的值改爲替身的值,然後刪掉替身就好(有沒有一種恩將仇報的趕腳),這裏的刪掉替身的操作一定是滿足前兩種操作的。

    void remove(const elemType &x){
        remove(x, root);
    }
    
    void remove(const elemType &x, node *&t){//注意這裏是指針的引用,不是複製構造,因爲要滿    
                                             //足補上來子樹與上面的節點能夠接上
        if(t == NULL){
            
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
            }
            else{                        //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
    }

完整代碼:

#include <iostream>

using namespace std;

template<class elemType>
class binarySearchTree
{
private:
    struct node{
        elemType data;
        node *left;
        node *right;
        
        node(const elemType &d, node *ln = NULL, node *rn = NULL):data(d), left(ln), right(rn){}
    };
    
    node *root;
    
    
public:
    binarySearchTree(){
        root = NULL;
    }
    ~binarySearchTree(){
        clear(root);
    }
    
    void clear(node *t){
        if(t == NULL){
            return;
        }
        clear(t -> left);
        clear(t -> right);
        delete t;
    }
    
    
    elemType *find(const elemType &x){
        return find(x, root);
    }
    
    elemType *find(const elemType &x, node *t){
        if(t == NULL){
            cout << "Can not find" << endl;
            return 0;
        }
        if(t -> data == x){
            cout << "haha" << endl;
            return &(t -> data);
        }
        if(x < t -> data){
            return find(x, t -> left);
        }
        else{
            return find(x, t -> right);
        }
    }
    
    void insert(const elemType &x){
        insert(x, root);
    }
    void insert(const elemType &x, node *&t){
        if(t == NULL){
            t = new node(x, NULL, NULL);
        }
        else{
            if(x < t -> data){
                insert(x, t -> left);
            }
            else{
                if(x == t -> data){
                    cout << "The node has existed" << endl;
                    return;
                }
                else{
                    insert(x, t -> right);
                }
            }
        }
    }
    
    void remove(const elemType &x){
        remove(x, root);
    }
    
    void remove(const elemType &x, node *&t){
        if(t == NULL){
            
            return;
        }
        if(x < t -> data){
            remove(x, t -> left);
        }
        else{
            if(x > t -> data){
                remove(x, t -> right);
            }
            else{   //==
                if(t -> left != NULL && t -> right != NULL){
                    node *tmp = t -> right;
                    while(tmp -> left != NULL){
                        tmp = tmp -> left;
                    }
                    t -> data = tmp -> data;
                    remove(t -> data, t -> right);
                }
                else{
                    node *old = t;
                    if(t -> left == NULL && t -> right == NULL){
                        delete old;
                    }
                    else{
                        if(t -> left!= NULL){
                            t = t -> left;
                        }
                        else{
                            t = t -> right;
                        }
                        delete old;
                    }
                }
            }
        }
    }
    
    void midOrder(){
        midOrder(root);
    }
    void midOrder(node *p){
        if(p == NULL){
            return;
        }
        midOrder(p -> left);
        cout << p -> data << ' ';
        midOrder(p -> right);
    }
    
};

總結:

二叉查找樹性能與結構有很大關係,如果構建的好,也就是二叉查找樹接近於一棵完全二叉樹,那麼其所有操作都是O(logn)的,但如果數據很變態,剛好形成了一條鏈,那就是線性O(n)了。

不過總歸是有辦法解決的,那就是傳說中的AVL樹和紅黑樹,之後會慢慢講到。

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