算法與數據結構之二分搜索樹

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

二分查找法

wiki定義

是一種在有序數組中查找某一特定元素的搜索算法。搜索過程從數組的中間元素開始,若是中間元素正好是要查找的元素,則搜索過程結束;若是某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,並且跟開始同樣從中間元素開始比較。若是在某一步驟數組爲空,則表明找不到。這種搜索算法每一次比較都使搜索範圍縮小一半。node

注意ios

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

實現

<?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;bash

實現
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來實現一個查找表,而數組只能用索引dom

節點結構

先實現每個節點:節點的要素有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,那麼和當前節點比較,若是小於節點就往左子樹放,若是大於節點就往右子樹放。
  • 而後分別對左子樹或者右子樹遞歸的遞歸進行如上一、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;
}
複製代碼

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

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

我的博客番茄技術小棧掘金主頁

想了解更多,歡迎關注個人微信公衆號:番茄技術小棧

番茄技術小棧
相關文章
相關標籤/搜索