演算法與資料結構之二分搜尋樹

吳軍旗發表於2019-03-04

主要介紹:介紹一下二分搜尋樹相關的知識,有二分查詢法、二分搜尋樹等。

二分查詢法

wiki定義

是一種在有序陣列中查詢某一特定元素的搜尋演算法。搜尋過程從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則搜尋過程結束;如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較。如果在某一步驟陣列為空,則代表找不到。這種搜尋演算法每一次比較都使搜尋範圍縮小一半。

注意

對於有序數列,才能使用二分查詢法。

實現

<?php
/**
 * 二分查詢
 */

// 二分查詢法,在有序陣列arr中,查詢target
// 如果找到target,返回相應的索引index
// 如果沒有找到target,返回-1
function binarySearch($arr, $n, $target){
	//在陣列arr[l...r]中查詢target
	$l = 0;
	$r = $n -1;
	while ($l <= $r) {
		//$mid = (int)(($l + $r) / 2);//容易越界
		$mid = $l + (int)(($r - $l) / 2);
		if ($arr[$mid] == $target) {
			return $mid;
		}elseif ($arr[$mid]  < $target) {
			//在arr[mid + 1, r]中尋找
			$l = $mid + 1;
		}else{
			$r = $mid - 1;
		}
	}

	return -1;
}


echo "程式執行\n";
//測試
$arr = array();
$n = 100000;
for ($i=0; $i < $n; $i++) { 
	$arr[$i] = $i;
}
$t1 = microtime(true);
for ($i=0; $i < 2 * $n; $i++) { 
	$v = binarySearch($arr, $n, $i);
	if ($i < $n) {
		assert($v == $i);
	}elseif ($i >= $n) {
		assert($v == -1);
	}
}
$t2 = microtime(true);
echo "{$sortName}執行的時間為:". (($t2-$t1)).'s'."\n";

複製程式碼

需要特別注意的是:

//$mid = (int)(($l + $r) / 2);//容易越界
$mid = $l + (int)(($r - $l) / 2);
複製程式碼

求mid值不採用1是因為可能造成越界

遞迴實現:

<?php
/**
 * 二分查詢
 */

//遞迴法,邊界函式
function _binarySearch2($arr, $l, $r, $target){
	if ($l > $r) {
		return -1;
	}
	$mid = $l + (int)(($r - $l) / 2);
	if ($arr[$mid] == $target) {
		return $mid;
	}elseif($arr[$mid] < $target){
		return _binarySearch2($arr, $mid + 1, $r, $target);
	}else{
		return _binarySearch2($arr, $l, $mid - 1, $target);
	}
}

function binarySearch2($arr, $n, $target){
	return _binarySearch2($arr, 0, $n-1, $target);
}


echo "程式執行\n";
//測試
$arr = array();
$n = 100000;
for ($i=0; $i < $n; $i++) { 
	$arr[$i] = $i;
}
$t1 = microtime(true);
for ($i=0; $i < 2 * $n; $i++) { 
	$v = binarySearch2($arr, $n, $i);
	if ($i < $n) {
		assert($v == $i);
	}elseif ($i >= $n) {
		assert($v == -1);
	}
}
$t2 = microtime(true);
echo "{$sortName}執行的時間為:". (($t2-$t1)).'s'."\n";

複製程式碼

二分查詢的延伸:floor與ceil

floor 定義

二分查詢法, 在有序陣列arr中, 查詢target; 如果找到target, 返回第一個target相應的索引index; 如果沒有找到target, 返回比target小的最大值相應的索引, 如果這個最大值有多個, 返回最大索引; 如果這個target比整個陣列的最小元素值還要小, 則不存在這個target的floor值, 返回-1;

實現
int floor(T arr[], int n, T target){

    assert( n >= 0 );

    // 尋找比target小的最大索引
    int l = -1, r = n-1;
    while( l < r ){
        // 使用向上取整避免死迴圈
        int mid = l + (r-l+1)/2;
        if( arr[mid] >= target )
            r = mid - 1;
        else
            l = mid;
    }

    assert( l == r );

    // 如果該索引+1就是target本身, 該索引+1即為返回值
    if( l + 1 < n && arr[l+1] == target )
        return l + 1;

    // 否則, 該索引即為返回值
    return l;
}
複製程式碼

ceil定義

二分查詢法, 在有序陣列arr中, 查詢target 如果找到target, 返回最後一個target相應的索引index; 如果沒有找到target, 返回比target大的最小值相應的索引, 如果這個最小值有多個, 返回最小的索引; 如果這個target比整個陣列的最大元素值還要大, 則不存在這個target的ceil值, 返回整個陣列元素個數n;

實現
int ceil(T arr[], int n, T target){

    assert( n >= 0 );

    // 尋找比target大的最小索引值
    int l = 0, r = n;
    while( l < r ){
        // 使用普通的向下取整即可避免死迴圈
        int mid = l + (r-l)/2;
        if( arr[mid] <= target )
            l = mid + 1;
        else // arr[mid] > target
            r = mid;
    }

    assert( l == r );

    // 如果該索引-1就是target本身, 該索引+1即為返回值
    if( r - 1 >= 0 && arr[r-1] == target )
        return r-1;

    // 否則, 該索引即為返回值
    return r;
}
複製程式碼

二分搜尋樹基礎

二叉查詢樹(Binary Search Tree)定義

也可叫做二分查詢樹。它不僅可以查詢資料,還可以高效地插入、刪除資料。 特點:每個節點的key值大於左子節點,小於右子節點。注意它不一定是完全的二叉樹。 所以節點的key是唯一的,我們就是通過它來索引key對應的value,注意圖中標註的都是key哦。

paste image

所以二叉搜尋樹也不適合用陣列來表示,一般都是用node節點來表示。 相比陣列的資料結構的優勢:

paste image

還有一個優勢是,它的key可以自己定義比如用String來作為key來實現一個查詢表,而陣列只能用索引

節點結構

先實現每一個節點:節點的要素有key、value、左子節點、右子節點。

    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;
        }
    };
複製程式碼

為了方便操作在這裡構建的一個BST的類。

#include <iostream>

using namespace std;

template <typename Key, typename Value>
class BST{

private:
    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 *root;
    int count;

public:
    BST(){
        root = NULL;
        count = 0;
    }
    ~BST(){
        // TODO: ~BST()
    }

    int size(){
        return count;
    }

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

int main() {

    return 0;
}
複製程式碼

二分搜尋樹的建立(插入操作)

其實樹的建立就是一個插入過程,每次插入都不改變其樹的性質和結構。如果該節點存在,直接更新

思想 核心思想:從根節點開始找插入的位置,滿足二叉搜尋樹的特性,比左子節點大,比右子節點小.

  • 步驟:
  • 從根節點開始,先比較當前節點,如果當前節點為null那麼很明顯就應該插入到這個節點。
  • 如果上面的節點不是null,那麼和當前節點比較,如果小於節點就往左子樹放,如果大於節點就往右子樹放。
  • 然後分別對左子樹或者右子樹遞迴的遞迴進行如上1、2步驟的操作

** 注意**

此時就用到了遞迴,那麼遞迴是對某一個問題,它的子問題需要是同樣的模型。 此處的一個小的問題就是:對某個node,然後進行操作,所以引數應該有個node才能實現迴圈起來。 此處向以node為根的二叉搜尋樹中,插入節點(key, value).此處就都用int型別了,外部的使用者是 不需要了解node的概念.它們只需要知道傳入的的key和value就行。 暫時的設計便於理解傳入使用者自己的key和value,到時候也方便用於自己根據key進行所以.

** 實現**

public:
    void insert(Key key, Value value){
        root = insert(root, key, value);
    }

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->left = insert( node->left , key, value);
        else    // key > node->key
            node->right = insert( node->right, key, value);

        return node;
    }
};

複製程式碼

二分搜尋樹的搜尋操作

public:

    bool contain(Key key){
        return contain(root, key);
    }

    Value* search(Key key){
        return search( root , key );
    }

private:

    // 檢視以node為根的二叉搜尋樹中是否包含鍵值為key的節點
    bool contain(Node* node, Key key){

        if( node == NULL )
            return false;

        if( key == node->key )
            return true;
        else if( key < node->key )
            return contain( node->left , key );
        else // key > node->key
            return contain( node->right , key );
    }

    // 在以node為根的二叉搜尋樹中查詢key所對應的value
    Value* search(Node* node, Key key){

        if( node == NULL )
            return NULL;

        if( key == node->key )
            return &(node->value);
        else if( key < node->key )
            return search( node->left , key );
        else // key > node->key
            return search( node->right, key );
    }
複製程式碼

二分搜尋樹的遍歷(深度優先遍歷)

二叉搜尋樹的遍歷。

遍歷(Traversal)是指沿著某條搜尋路線,依次對樹中每個結點均做一次且僅做一次訪問。二叉樹的遍歷有三種:

  • 前序遍歷(Preorder Traversal):先訪問當前節點,再依次遞迴訪問左右子樹
  • 中序遍歷(Inorder Traversal):先遞迴訪問左子樹,再訪問自身,再遞迴訪問右子樹
  • 後序遍歷(Postorder Traversal):先遞迴訪問左右子樹,最後再訪問當前節點。

上面第一種的遞迴訪問怎麼理解那?比如中序遍歷,任何節點的左子節點未訪問完,繼續訪問它的左子節點,直到左子節點完 全遍歷完畢,接下來才會從最先訪問完的那個節點,進行中序遍歷,然後依次往上。

步驟

paste image

前序遍歷

paste image

中序遍歷

paste image

後續遍歷

paste image

注: 每一個節點都會被訪問三次,這三種的前序中序後序,是說的當前節點的順序(也就是中間那個)

程式碼實現

#include <iostream>
#include <queue>

using namespace std;

template <typename Key, typename Value>
class BST{

private:
    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 *root;
    int count;

public:
    BST(){
        root = NULL;
        count = 0;
    }
    ~BST(){
        destroy( root );
    }

    int size(){
        return count;
    }

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

    void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    bool contain(Key key){
        return contain(root, key);
    }

    Value* search(Key key){
        return search( root , key );
    }

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

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

    // 後序遍歷
    void postOrder(){
        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 );
        }
    }

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->left = insert( node->left , key, value);
        else    // key > node->key
            node->right = insert( node->right, key, value);

        return node;
    }

    // 檢視以node為根的二叉搜尋樹中是否包含鍵值為key的節點
    bool contain(Node* node, Key key){

        if( node == NULL )
            return false;

        if( key == node->key )
            return true;
        else if( key < node->key )
            return contain( node->left , key );
        else // key > node->key
            return contain( node->right , key );
    }

    // 在以node為根的二叉搜尋樹中查詢key所對應的value
    Value* search(Node* node, Key key){

        if( node == NULL )
            return NULL;

        if( key == node->key )
            return &(node->value);
        else if( key < node->key )
            return search( node->left , key );
        else // key > node->key
            return search( node->right, key );
    }

    // 對以node為根的二叉搜尋樹進行前序遍歷
    void preOrder(Node* node){

        if( node != NULL ){
            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 destroy(Node* node){

        if( node != NULL ){
            destroy( node->left );
            destroy( node->right );

            delete node;
            count --;
        }
    }
};


int main() {

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

    int n = 10;
    for( int i = 0 ; i < n ; i ++ ){
        int key = rand()%n;
        // 為了後續測試方便,這裡value值取和key值一樣
        int value = key;
        cout<<key<<" ";
        bst.insert(key,value);
    }
    cout<<endl;

    // test size
    cout<<"size: "<<bst.size()<<endl<<endl;

    // test preOrder
    cout<<"preOrder: "<<endl;
    bst.preOrder();
    cout<<endl<<endl;

    // test inOrder
    cout<<"inOrder: "<<endl;
    bst.inOrder();
    cout<<endl<<endl;

    // test postOrder
    cout<<"postOrder: "<<endl;
    bst.postOrder();
    cout<<endl<<endl;

    // test levelOrder
    cout<<"levelOrder: "<<endl;
    bst.levelOrder();
    cout<<endl<<endl;

    return 0;
}
複製程式碼

結果

2 0 8 3 6 8 3 0 0 1 
size: 6

preOrder: 
2
0
1
8
3
6


inOrder: 
0
1
2
3
6
8


postOrder: 
1
0
6
3
8
2


levelOrder: 
2
0
8
1
3
6



複製程式碼

層序遍歷(廣度優先遍歷)

我們前面提到的都是通過遞迴實現的深度優先遍歷,只要往下的節點還有符合要求的條件,那麼就會繼續西先往下執行。

而層序遍歷是一種廣度優先的遍歷方式,先遍歷根節點這一層,再遍歷第二層,依次這樣從上到下,從左到右。此處實現的思想:利用佇列的先入先出的特性.(由於對佇列的具體實現不清楚,暫時只理解此處的思想). 在佇列不為空的時候,開始進行操作,佇列不為空那麼root節點是肯定存在的,先把root入隊,然後開始迴圈判斷:判斷條件佇列為kong,先遍歷處理當前節點,然後出隊,(此時佇列為空了)然後看看這個節點有沒有左右子節點,如果有入隊(這樣就又不為空了,並且往下走了一層),左右子節點處理完畢的時候.再繼續迴圈做同樣的操作。

實現



public:

    // 層序遍歷
    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 );
        }
    }

private:

    void destroy(Node* node){

        if( node != NULL ){
            destroy( node->left );
            destroy( node->right );

            delete node;
            count --;
        }
    }
};
}
複製程式碼

注意

對二叉樹的刪除就是使用後續遍歷

刪除最大值,最小值

查詢最大值與最小值

// 尋找最小的鍵值
    Key minimum(){
        assert( count != 0 );
        Node* minNode = minimum( root );
        return minNode->key;
    }

    // 尋找最大的鍵值
    Key maximum(){
        assert( count != 0 );
        Node* maxNode = maximum(root);
        return maxNode->key;
    }
    
    // 在以node為根的二叉搜尋樹中,返回最小鍵值的節點
    Node* minimum(Node* node){
        if( node->left == NULL )
            return node;

        return minimum(node->left);
    }

    // 在以node為根的二叉搜尋樹中,返回最大鍵值的節點
    Node* maximum(Node* node){
        if( node->right == NULL )
            return node;

        return maximum(node->right);
    }
    
複製程式碼

刪除二叉搜尋樹的最大值和最小值.

// 從二叉樹中刪除最小值所在節點
    void removeMin(){
        if( root )
            root = removeMin( root );
    }

    // 從二叉樹中刪除最大值所在節點
    void removeMax(){
        if( root )
            root = removeMax( root );
    }
    
    
    // 刪除掉以node為根的二分搜尋樹中的最小節點
    // 返回刪除節點後新的二分搜尋樹的根
    Node* removeMin(Node* node){

        if( node->left == NULL ){

            Node* rightNode = node->right;
            delete node;
            count --;
            return rightNode;
        }

        node->left = removeMin(node->left);
        return node;
    }

    // 刪除掉以node為根的二分搜尋樹中的最大節點
    // 返回刪除節點後新的二分搜尋樹的根
    Node* removeMax(Node* node){

        if( node->right == NULL ){

            Node* leftNode = node->left;
            delete node;
            count --;
            return leftNode;
        }

        node->right = removeMax(node->right);
        return node;
    }
複製程式碼

二分搜尋樹的刪除

刪除任意節點:由Hibbard提出的一種方法,稱為Hubbard Deletion 刪除任意節點和刪除最小、最大節點的區別就是,刪除任意節點的時候有可能左右兩個都有子節點。 首先我們不可以簡單的把左子節點或者右子節點,直接放到當前刪除的節點的位置,因為這樣 很容易導致不滿足二叉搜尋樹的特性,我們應該找到當前述的前驅或者後繼放入當前位置,前驅:前面 一個比它小的元素;後繼:後面一個比它打的元素。比如一種方法,我們找到它的右子節點中所有的節點 中的最小的節點,然後把這個最小的節點放入到刪除的節點中,此時仍然滿足二叉搜尋樹的特性,左子節點 都小於它,右子節點都大於它。還一種中類似的就是找左子樹中所有節點的最大的節點.

** 步驟**

  • 找到右子節點的最小值,刪除它。
  • 然後把這個刪除的節點放到找到的這個節點位置。就是把找到的節點的右子節點和左子節點分別賦值給刪除的這個節點.
  • 最後這個節點還應該賦值給刪除的節點的父節點的正確的位置.

paste image

程式碼實現

// 從二叉樹中刪除鍵值為key的節點
    void remove(Key key){
        root = remove(root, key);
    }
    
    // 刪除掉以node為根的二分搜尋樹中鍵值為key的節點
    // 返回刪除節點後新的二分搜尋樹的根
    Node* remove(Node* node, Key key){

        if( node == NULL )
            return NULL;

        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;
        }
        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->left != NULL && node->right != NULL
            Node *successor = new Node(minimum(node->right));
            count ++;

            successor->right = removeMin(node->right);
            successor->left = node->left;

            delete node;
            count --;

            return successor;
        }
    }
複製程式碼

二分搜尋樹的順序性

  • 前驅(predecessor)
  • 後繼(successor)
  • floor:不大於傳入的key對應的值是
  • ceil:不小於傳入的key對應的值是
  • 二叉搜尋書的排名rank

想要知道二叉搜尋樹中的某個key在書中排名第幾? 為每個樹的節點,新增一個域,這個域來標記當前節點有多少個子節點,然後就可以通過簡單的邏輯和計算得 出排名第幾了。注意類似這種新增一個域記錄東西的,最難的在insert和remove的時候對相應的域進行維護,一定不要忘記了。

paste image

  • slect:排名第10的元素是誰?

paste image

  • 支援重複元素的二叉搜尋樹

    • 第一種實現:把大於節點的放在右子節點、小於等於當前節點的放在左子節點。但這種當有大量重複元素的時候浪費空間
    • 第二種實現:也是重新為Node加一個域,這個域用來標記當前節點的個數。

    paste image

二分搜尋樹的侷限性

同樣的資料,可以對應不同的二分搜尋樹。 當資料的插入順序接近有序的時候,二叉搜尋樹就有可能退化成連結串列此時的時間複雜度從logn又變成了n 但是我們也不能一次性打亂元素,因為有可能資料時一點點你輸入的,你無法拿到全部的元素。此時就改進的二叉樹了:

paste image

平衡二叉樹的實現有: 1)、紅黑樹 2)、2-3 tree 3)、AVL tree 4)、Splay tree 5)、Treap 平衡二叉樹和堆的結合. Balanced Binary Tree 具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹.

樹形問題和更多樹。

  • KD樹 :(k-dimensional樹的簡稱),是一種分割k維資料空間的資料結構。主要應用於多維空間關鍵資料的搜尋(如:範圍搜尋和最近鄰搜尋)。K-D樹是二進位制空間分割樹的特殊的情況。
  • 區間樹:區間樹是在紅黑樹基礎上進行擴充套件得到的支援以區間為元素的動態集合的操作,其中每個節點的關鍵值是區間的左端點。
  • 哈夫曼樹:給定n個權值作為n個葉子結點,構造一棵二叉樹,若帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。

最終程式碼封裝

#include <iostream>
#include <queue>
#include <cassert>

using namespace std;

template <typename Key, typename Value>
class BST{

private:
    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(){
        destroy( root );
    }

    int size(){
        return count;
    }

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

    void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    bool contain(Key key){
        return contain(root, key);
    }

    Value* search(Key key){
        return search( root , key );
    }

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

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

    // 後序遍歷
    void postOrder(){
        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 );
        }
    }

    // 尋找最小的鍵值
    Key minimum(){
        assert( count != 0 );
        Node* minNode = minimum( root );
        return minNode->key;
    }

    // 尋找最大的鍵值
    Key maximum(){
        assert( count != 0 );
        Node* maxNode = maximum(root);
        return maxNode->key;
    }

    // 從二叉樹中刪除最小值所在節點
    void removeMin(){
        if( root )
            root = removeMin( root );
    }

    // 從二叉樹中刪除最大值所在節點
    void removeMax(){
        if( root )
            root = removeMax( root );
    }

    // 從二叉樹中刪除鍵值為key的節點
    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->left = insert( node->left , key, value);
        else    // key > node->key
            node->right = insert( node->right, key, value);

        return node;
    }

    // 檢視以node為根的二叉搜尋樹中是否包含鍵值為key的節點
    bool contain(Node* node, Key key){

        if( node == NULL )
            return false;

        if( key == node->key )
            return true;
        else if( key < node->key )
            return contain( node->left , key );
        else // key > node->key
            return contain( node->right , key );
    }

    // 在以node為根的二叉搜尋樹中查詢key所對應的value
    Value* search(Node* node, Key key){

        if( node == NULL )
            return NULL;

        if( key == node->key )
            return &(node->value);
        else if( key < node->key )
            return search( node->left , key );
        else // key > node->key
            return search( node->right, key );
    }

    // 對以node為根的二叉搜尋樹進行前序遍歷
    void preOrder(Node* node){

        if( node != NULL ){
            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 destroy(Node* node){

        if( node != NULL ){
            destroy( node->left );
            destroy( node->right );

            delete node;
            count --;
        }
    }

    // 在以node為根的二叉搜尋樹中,返回最小鍵值的節點
    Node* minimum(Node* node){
        if( node->left == NULL )
            return node;

        return minimum(node->left);
    }

    // 在以node為根的二叉搜尋樹中,返回最大鍵值的節點
    Node* maximum(Node* node){
        if( node->right == NULL )
            return node;

        return maximum(node->right);
    }

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

        if( node->left == NULL ){

            Node* rightNode = node->right;
            delete node;
            count --;
            return rightNode;
        }

        node->left = removeMin(node->left);
        return node;
    }

    // 刪除掉以node為根的二分搜尋樹中的最大節點
    // 返回刪除節點後新的二分搜尋樹的根
    Node* removeMax(Node* node){

        if( node->right == NULL ){

            Node* leftNode = node->left;
            delete node;
            count --;
            return leftNode;
        }

        node->right = removeMax(node->right);
        return node;
    }

    // 刪除掉以node為根的二分搜尋樹中鍵值為key的節點
    // 返回刪除節點後新的二分搜尋樹的根
    Node* remove(Node* node, Key key){

        if( node == NULL )
            return NULL;

        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;
        }
        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->left != NULL && node->right != NULL
            Node *successor = new Node(minimum(node->right));
            count ++;

            successor->right = removeMin(node->right);
            successor->left = node->left;

            delete node;
            count --;

            return successor;
        }
    }
};


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] );
    }
}

int main() {

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

    int n = 10000;
    for( int i = 0 ; i < n ; i ++ ){
        int key = rand()%n;
        // 為了後續測試方便,這裡value值取和key值一樣
        int value = key;
        //cout<<key<<" ";
        bst.insert(key,value);
    }

    // test remove
    // remove elements in random order
    int order[n];
    for( int i = 0 ; i < n ; i ++ )
        order[i] = i;
    shuffle( order , 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;
        }

    return 0;
}
複製程式碼

-------------------------華麗的分割線--------------------

看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。

個人部落格番茄技術小棧掘金主頁

想了解更多,歡迎關注我的微信公眾號:番茄技術小棧

番茄技術小棧

相關文章