二叉搜索树,二叉排序树(Binary Search Tree)【详解】

二叉搜索树概念

二叉搜索树又称为二叉排序,它或者是一个空树,或者是具有已下性质的二叉树:

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
它的左右子树也分别为二叉搜索树

二叉搜索树的构建

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

在这里插入图片描述

它的特别之处在于中序遍历:1,3,4,6,7,8,10,13,14.

基本框架

template<class K>
struct BSTreeNode
{
    BSTreeNode(const K& key)
        :_left(nullptr)
        ,_right(nullptr)
        ,_key(key)
    {}
    
    BSTreeNode<k> _left;
    BSTreeNode<K> _right;
    K _key;
};

template<class K>
class BSTree
{
   typedef BSTreeNode<K> Node;
public:
//查找,删除,插入...
private:
   Node* _root = nullptr;
};

插入

1.空直接插入
2.非空找插入位置,大的右边找,小的左边找

在这里插入图片描述

bool insert(const K& key)
{
   if(_root == nullptr)//为空
   {
      _root = new Node(key);
      return true;
   }
  //不为空
  Node* parent = nullptr;//记录父节点,为了连接起来
  Node* cur = _root;
  while(cur)
  {
     if(cur->_key < key)
     {
         parent = cur;
         cur = cur->_right; 
     }
     else if(cur->_key > key)
     {
         parent = cur;
         cur = cur->_left;
     }
     else//有相同的值
        return false;
  }
  //找到,准备插入
  cur = new Node(key);
  if(parent->_key > key)//需要判断插入位置在左边还是右边
  {
     parent->_left = cur;
  }
  else
  {
    parent->_right = cur;
  }
  return true;
}

查找

bool Find(const K& key)
{
   Node* cur = _root;
   while(cur)
   {
      if(cur->_key > key)
      {
         cur = cur->_left;
      }
      else if(cur->_key < key)
      {
         cur = cur->_right;
      }
      else
         return true;
   }
   return false;
}

删除(难点)

在这里插入图片描述
删除需要分三种情况:
1.删除的结点为叶子结点(直接删除就可以) ,但是其实可以并入2中。
比如删除4,7,13,1;
2.删除结点的左子树or右子树为空(将这个结点有的子树给连接上去)
在这里插入图片描述

3.删除的左右子树都不为空(找出左子树的最大结点or右子树的最小结点来替换删除的结点就可以)
左子树最大结点:左子树最右结点
右子树最小结点:右子树最左结点
在这里插入图片描述

bool Erase(const K& key)
{
    if(_root == nullptr)
       return false;

    //先找到要删除的结点
    Node* cur = _root;
    Node* parent = nullptr;
    while(cur)
    {
       if(cur->_key > key)
       {
           parent = cur;
           cur = cur->_left;
       }
       else if(cur->_key < key)
       {
            parent = cur;
            cur = cur->_right;
       }
       else //找到了,准备删除
       {
           //为了更好理解,我们在判断2.左子树为空or右子树为空的过程中
           //可以加上判断左右子树都为空,皆1的情况
           if(cur->_left == nullptr)
           { 
             //判断是不是特殊结点:根节点,根节点parent == nullptr,->会报错
               if(cur == _root)
               {
            //当右子树也为空时候,为1.情况但是也让_root=nullptr,都一样
                    _root = cur->_right;
                    delete cur;
               }
               else//不为特殊结点
               {
                //依旧要判断cur是parent的左还是右
                   if(cur == parent->_right)
                   {
                      parent->_right = cur->_right;
                      delete cur;
                   }
                   else
                   {
                       parent->_left = cur->_right;
                       delete cur;
                   }
               }
           }
           else if(cur->_right == nullptr)
           {
              //跟上面判断相同的方法
              if(cur == _root)
              {
                  _root = cur->_left;
                  delete cur;
              }
              else
              {
                  if(cur == parent->_right)
                  {
                     parent->_right = cur->_left;
                     delete cur;
                  }
                  else
                  {
                     parenn->_left = cur->_left;
                     delete cur;
                  }
              }  
           }
           else//左子树和右子树都不为空,替换法删除
           {
               //找右子树的最小结点(最左结点)
               Node* subleft = cur->_right;
               Node* parent = cur;//为空的话到后面解引用会出现问题,头节点情况
               while(subleft->_left)
               {
                  parent = subleft;
                  subleft = subleft->_left;
               }
               swap(cur->_key,subleft->_key);
               //交换完删除,但是不能递归删,因为已经交换完了,不再是BST树了
               //你甚至找不到你删除的结点

               //error : parent->_left = subleft->_right;
               //头结点的情况也要考虑
               if(subleft == parent->_left)
               {
                   parent->_left = subleft->_right;
                   delete subleft;
               }
               else
               {
                   parent->_right = subleft->_right;
                   delete subleft;
               }
           }
           return true;
       }
    }
    return false;
}

1和2. 删除的节点如果是根节点,父节点为nullptr,要特殊处理,要不然会报错!!
在这里插入图片描述
在这里插入图片描述
3.交换后也要判断跟结点的情况在这里插入图片描述

二叉搜索树功能的递归实现

查找的递归

public:bool FindR(const K& key)
        {
            return _FindR(_root,key);
        }

private:bool _FindR(Node* root,const K& key)
{
    if(root == nullptr)
       return false;
    if(root->_key > key)
    {
        return _FindR(root->_left,key);
    }
    else if(root->_key < key)
    {
        return _FindR(root->_right,key);
    }
    else return true;
}

插入递归

public:bool InsertR(const K& key)
        {
            return _InsertR(_root,key);
        }

private:bool _InsertR(Node* & root,const K& key)
{
    if(root == nullptr)
    {
       root = new Node(key);
       return true;
    }
    if(root->_key > key)
    {
        return _InsertR(root->_left,key);
    }
    else if(root->_key < key)
    {
        return _InsertR(root->_right,key);
    }
    else return false;
}

为什么这里是Node* &root,用上了引用呢?
在传root的时候,使用传引用传参,可以做到自动链接新建的结点。
在我们当前函数root = new Node(key)创建新的结点,因为用了引用,这里的root就是上一个函数栈帧里面的root->_right;所以结点创建完成,返回的过程就相当于完成了链接。

删除递归

public:bool EraseR(const K& key)
        {
            return _EraseR(_root,key);
        }

private:bool _EraseR(Node* & root,const K& key)
{
    if(root == nullptr)
       return false;
    if(root->_key > key)
    {
        return _EraseR(root->_left,key);
    }
    else if(root->_key < key)
    {
        return _EraseR(root->_right,key);
    }
    else
    {
       //找到了开始删除,递归成删除根节点了
       if(root->_left == nullptr)
       {
            Node* del = root;  
            root = root->_right;
            delete del;
            return true;
       }
       else if(root->_right == nullptr)
       {
            Node* del = root; 
            root = root->_left;
            delete del;
            return true;
       }
       else 
       {
           Node* subleft  = root->_left;
           while(subleft->_left)
           {
               subleft = subleft->_left;
           }
           swap(root->_key,subleft->_key);
           return _EraseR(root->_right,key);
       }   
    }
}

二叉搜索树部分默认成员函数实现

构造函数

BSTree(){}; 
拷贝构造也是构造,默认构造的说法是:只要我们写了构造,它就不会再自动生成,
拷贝构造也是构造,没写默认构造也不会自动生成,会报没有默认拷贝构造错误。

C++11的新特性:default
在函数声明后加=default,将该函数声明为 default 函数,编译器将为显式声明的default函数自动生成函数体。

拷贝构造函数

   BSTree(const BSTree<K>& t)
   {
      _root = Copy(t._root);
   }


	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* newRoot = new Node(root->_key);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);

		return newRoot;
	}

析构函数

    ~BSTree()
   {
       Destroy(_root);
   }
   
	 void Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		//左-右-根
		Destory(root->_left);
		Destory(root->_right);
	
		delete root;
		root = nullptr;
	}

赋值运算符重载

   //t1 = t3
   BSTree<K>& operator=(BSTree<K> t)
   {
	swap(_root, t._root);
	return *this;
   }

二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
那么我们后续章节学习的AVL树和红黑树就可以上场了。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值