二分搜尋樹系列之「特性及完整原始碼-code」

ice_moss發表於2021-05-20

一.特性

1.順序性

二分搜尋樹可以當做查詢表的一種實現。

我們使用二分搜尋樹的目的是透過查詢 key 馬上得到 value。minimum、maximum、successor(後繼)、predecessor(前驅)、floor(地板)、ceil(天花板、rank(排名第幾的元素)、select(排名第n的元素是誰)這些都是二分搜尋樹順序性的表現。

2.侷限性

二分搜尋樹在時間效能上是具有侷限性的。

如下圖所示,元素節點一樣,組成兩種不同的二分搜尋樹,都是滿足定義的:

二分搜尋樹系列之【特性及完整原始碼-code】

二叉搜尋樹可能退化成連結串列,相應的,二叉搜尋樹的查詢操作是和這棵樹的高度相關的,而此時這顆樹的高度就是這顆樹的節點數 n,同時二叉搜尋樹相應的演算法全部退化成 O(n) 級別。

二.完整程式碼

前面我們將了二分搜尋樹元素的插入、查詢、遍歷刪除等,我將完整的原始碼放在這裡了:

#include <iostream>
#include <queue>
#include <cassert>
using namespace std;

//套用模板函式
template <typename Key, typename Value>
class BST {
private:
    //構造節點Node
  struct Node {
        Key key;
  Value value;
  Node *left;
  Node *right;

  Node(Key key, Value value) {
             this->key = key;
             this->value = value;
  //初始值為空
             this->left = this->right = NULL;
  }
        Node(Node *node){
            this->key = node->key;
            this->value = node->value;
            this->left = node->left;
            this->right = node->right;
         }
    };

  //根節點
  Node *root;
  //節點數量
  int count;

public:
    BST() {
        //初始值為空
  root = NULL;
  count = 0;
  }

    ~BST() {
        distroy(root);
  }

    int size() {
        return count;
  }

    bool isEmpty() {
        return count == 0;
  }

    //插入操作
  void insert(Key key, Value value) {
        //向根節點中插入key, value
         root = insert(root, key, value);
  }

    //在樹中尋找是否存在key
  bool contain(Key key) {
         return contain(root, key);
  }

    //找到key相應的節點並且返回value的地址
  Node *seacher(Key key, Value value) {
          return seacher(root, key, value);
  }

    //前序遍歷,傳入節點,列印節點相應資訊
  void preOrder() {
        return preOrder(root);
  }

    //中序遍歷,以節點為node的節點為根節點
  void inOrder() {
        return inOrder(root);
  }

    //後序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
  void postOrder() {
        return postOrder(root);
  }

    //層序遍歷
  void levelOrder(){
        queue<Node*> q;
        q.push(root);
  //佇列不為空的情況
  while(!q.empty()){
            Node *node  = q.front();
            q.pop();

            cout<<node->key<<endl;
            if(node->left)
                q.push(node->left);
            if(node->right)
                q.push(node->right);
  }
    }

    // 尋找二分搜尋樹的最小的鍵值
  Node* minmum(){
        assert(count != 0);
        Node* minnode = minmum(root);
        return minnode->left;
  }

    // 尋找二分搜尋樹的最大的鍵值
  Node* maxmum(){
        assert(count != 0);
        Node* maxnode = maxmum(root);
        return maxnode ->right;
  }

    //刪除最小值的node
  void removeMin(){
        if(root)
            root = removeMin(root);
  }

    //刪除最大值的node
  void removeMax(){
        if(root)
            root = removeMax(root);
  }

    //刪除二分搜尋樹中值的任意節點
  void remove( Key key){
        root = remove(root, key);
  }

private:
    //插入操作
 //向以node為根節點的二分搜尋樹中,插入節點(key,value),使用遞迴演算法 //返回插入新節點後的二分搜尋樹的根 
 Node *insert(Node *node, Key key, Value value) {
        if (node == NULL) {
            count++;
            return new Node(key, value);
  }

        if (key == node->key) {
            node->value = value;
         } 
        else if (key > node->key) {
            node->right = insert(node->right, key, value);
  }
        else //key < node->key
            node->left = insert(node->left, key, value);
  }

    //在二分搜尋樹中查詢key,存在返回trun不存在返回false
  bool contain(Node *node, Key key) {
        //元素不存在
       if (key == NULL)
            return false;
  //元素存在
       if (key == node->key)
            return true;

       else if (key > node->key)
            return contain(node->right, key);

       else return contain(node->left, key);
  }

    //在二分搜尋樹中找到相應元素並返回該元素的地址
  Value *seacher(Node *node, Key key) {
        if (key == NULL)
            return NULL;
  //找到key 返回value的地址
        if (key == node->key)
            return &(node->value);

        else if (key > node->key)
            return seacher(node->right, key);

        else 
            return seacher(node->left, key);
  }

    //前序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
  void preOrder(Node *node) {
        if (node != NULL) {
            //不一定用列印,還可以對node->key和node->value進行操作
               cout << node->key << endl;

               preOrder(node->left);

               preOrder(node->right);
         }
   }

    //中序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
  void inOrder(Node *node) {
        if (node != NULL) {
            inOrder(node->left);

            cout << node->key << endl;

            inOrder(node->right);
       }
  }

    //後序遍歷,以node為根節點的二分搜尋樹進行前序遍歷,列印節點相應資訊
  void postOrder(Node *node) {
        if (node != NULL) {
            postOrder(node->left);

            postOrder(node->right);

            cout << node->key << endl;
         }
  }

    //解構函式的實現,其本質是後序遍歷
  void distroy(Node *node) {
        if (node != NULL) {
            distroy(node->left);
            distroy(node->right);

            delete node;
            count--;

         }
  }

    // 尋找二分搜尋樹的最小的鍵值
  Node* minmum(Node* node){
            if(node != NULL){
                minmum(node->left);
             }
            return node;
  }

    // 尋找二分搜尋樹的最大的鍵值
  Node* maxmum(Node* node){
        if(node != NULL){
            maxmum(node->right);
         }
        return node;
  }

    // 從二分搜尋樹中刪除最小值所在的節點
  Node* removeMin(Node* node){
        if(node->left == NULL){
            Node* NODE = node->right;
            delete node;
            count--;
            return NODE;
          }
         node->left = removeMax(node->left);
         return node;
  }
    //從二分搜尋樹中刪除最大值所在的節點
  Node* removeMax(Node* node){
        if(node->right == NULL){
            Node* NODE = node->left;
            delete node;
            count--;
            return NODE;
         }
        node->right = removeMax(node->right);
        return node;
  }

    //刪除二分搜尋樹中值的任意節點
  Node* remove(Node* node, Key key){
        //判斷node是否為空
       if(node == NULL) {
            return NULL;
        }

       //先找到需要刪除的值的node
       else if(key < node->key) {
            node->left =  remove(node->left, key);
            return node;
        }

        else if(key > node->key) {
            node->right = remove(node->right, key);
            return node;
        }

        //這裡就找到了需要delete的node
        else {   //key == node->key)

               // 待刪除節點左子樹為空的情況 
                if(node->left == NULL){
                    Node* rightNode = node->right;
                    delete node;
                    count--;
                    return rightNode;
                 }
            // 待刪除節點右子樹為空的情況
                if(node->right == NULL){
                    Node* leftNode = node->left;
                    delete node;
                    count--;
                    return leftNode;
                 }

            // 待刪除節點左右子樹都不為為空的情況
                 Node *succeer =new Node(minmum(node->right)); //找到最小key值的節點返回給succeer
                 count ++;
                 succeer->right = removeMin(node->right); //將最小key值的node刪除,並將返回值給succeer的右孩子

                 succeer->left = node->left;
                 delete node;
                 count--;
                 return succeer;
        }
    }
 };

void shuffle( int arr[], int n ){

    srand( time(NULL) );
 for( int i = n-1 ; i >= 0 ; i -- ){
        int x = rand()%(i+1);
  swap( arr[i] , arr[x] );
  }
}

測試也寫在這裡了:

// 測試 remove
int main() {

    srand(time(NULL));
    BST<int,int> bst = BST<int,int>();

  // 取n個取值範圍在[0...n)的隨機整數放進二分搜尋樹中
    int n = 10000;
    for( int i = 0 ; i < n ; i ++ ){
         int key = rand()%n;
  // 為了後續測試方便,這裡value值取和key值一樣
    int value = key;
    bst.insert(key,value);
    }
    // 注意, 由於隨機生成的資料有重複, 所以bst中的資料數量大機率是小於n的

 // order陣列中存放[0...n)的所有元素  int order[n];
   for( int i = 0 ; i < n ; i ++ )
          order[i] = i;
  // 打亂order陣列的順序
   shuffle( order , n );

  // 亂序刪除[0...n)範圍裡的所有元素
   for( int i = 0 ; i < n ; i ++ )
        if( bst.contain( order[i] )){
            bst.remove( order[i] );
   cout<<"After remove "<<order[i]<<" size = "<<bst.size()<<endl;
  }

    // 最終整個二分搜尋樹應該為空
    cout << bst.size() << endl;

    return 0;
}

(圖片來源:慕課網bobo老師)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章