算法与数据结构之二分搜索树

本文详细介绍了二分搜索树的基本概念、实现原理及其各种操作,包括查找、插入、删除等,并探讨了二分搜索树的局限性和改进方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要介绍:介绍一下二分搜索树相关的知识,有二分查找法、二分搜索树等。

二分查找法

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哦。

所以二叉搜索树也不适合用数组来表示,一般都是用node节点来表示。 相比数组的数据结构的优势:

还有一个优势是,它的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):先递归访问左右子树,最后再访问当前节点。

上面第一种的递归访问怎么理解那?比如中序遍历,任何节点的左子节点未访问完,继续访问它的左子节点,直到左子节点完 全遍历完毕,接下来才会从最先访问完的那个节点,进行中序遍历,然后依次往上。

步骤

前序遍历

中序遍历

后续遍历

注: 每一个节点都会被访问三次,这三种的前序中序后序,是说的当前节点的顺序(也就是中间那个)

代码实现

#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 删除任意节点和删除最小、最大节点的区别就是,删除任意节点的时候有可能左右两个都有子节点。 首先我们不可以简单的把左子节点或者右子节点,直接放到当前删除的节点的位置,因为这样 很容易导致不满足二叉搜索树的特性,我们应该找到当前述的前驱或者后继放入当前位置,前驱:前面 一个比它小的元素;后继:后面一个比它打的元素。比如一种方法,我们找到它的右子节点中所有的节点 中的最小的节点,然后把这个最小的节点放入到删除的节点中,此时仍然满足二叉搜索树的特性,左子节点 都小于它,右子节点都大于它。还一种中类似的就是找左子树中所有节点的最大的节点.

** 步骤**

  • 找到右子节点的最小值,删除它。
  • 然后把这个删除的节点放到找到的这个节点位置。就是把找到的节点的右子节点和左子节点分别赋值给删除的这个节点.
  • 最后这个节点还应该赋值给删除的节点的父节点的正确的位置.

代码实现

// 从二叉树中删除键值为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的时候对相应的域进行维护,一定不要忘记了。

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

  • 支持重复元素的二叉搜索树

    • 第一种实现:把大于节点的放在右子节点、小于等于当前节点的放在左子节点。但这种当有大量重复元素的时候浪费空间
    • 第二种实现:也是重新为Node加一个域,这个域用来标记当前节点的个数。

二分搜索树的局限性

同样的数据,可以对应不同的二分搜索树。 当数据的插入顺序接近有序的时候,二叉搜索树就有可能退化成链表此时的时间复杂度从logn又变成了n 但是我们也不能一次性打乱元素,因为有可能数据时一点点你输入的,你无法拿到全部的元素。此时就改进的二叉树了:

平衡二叉树的实现有: 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;
}
复制代码

-------------------------华丽的分割线--------------------

看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。

个人博客番茄技术小栈掘金主页

想了解更多,欢迎关注我的微信公众号:番茄技术小栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值