文章目录

  • 二叉搜索树的概念
  • 二叉搜索树的实现
    • 二叉搜索树的节点
    • 构造函数
    • 拷贝构造函数
    • 赋值运算符重载
    • 析构函数
    • 插入函数
      • 非递归实现
      • 递归实现
    • 删除函数
      • 非递归实现
      • 递归实现
    • 查找函数
      • 非递归实现
      • 递归实现
  • 二叉搜索树的性能分析
  • 二叉搜索树的完整代码

二叉搜索树的概念

二叉搜索树又称二叉排序树或二叉查找树,它可以是空树,也可以是具备以下性质的二叉树:
1: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值.

2: 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值.

3: 它的左右子树也分别为二叉搜索树.

注意:
二叉搜索树具备升序和排序的功能.因为根据二叉搜索树的性质,它一般是按照中序遍历的.

二叉搜索树的实现

二叉搜索树的节点

二叉搜索树有三个成员变量分别为: 左指针,右指针,节点值.
为了能够匹配储存多种数据类型,我们选择类模板.

 template < class K >struct BSTreeNode{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode( const K& key = K() ) //初始化列表:_left(nullptr),_right(nullptr),_key(key)}

构造函数

二查搜索树的成员只有_root根节点,所以只需要对根节点_root构造.

BSTree()
:_root(nullptr)
{}

注意:
如果我们只写了拷贝构造没写构造函数,此时编译器就不会生成默认构造函数,即使在我们给了缺省值的情况下.

拷贝构造函数

拷贝构造的主要思路就是按照前序遍历的思想,拷贝出一个根节点后依次对它的左子树,右子树进行遍历.

//拷贝构造
BSTree( const BSTree<K>& t )
{_root = _Copy(t._root);
}//拷贝函数
Node* _Copy(Node* root )
{//如果根节点为nulptr,则直接返回到上一层函数栈帧.if( root == nullptr ) {return nullptr;}//遍历左右子树,并与根左右指针建立关系.Node* CopyRoot = new Node(root->_key );CopyRoot->_left = _Copy(root->_left);CopyRoot->_right = _Copy(root->_right);//左右子树递归完毕后返回到第一层函数栈帧.return CopyRoot;}

注意:
因为递归实参要传对象中的private成员,但是在类外却访问不到对象的私密成员,所以在类外我们很难传参.所以在用到递归的函数中,我们都将拷贝构造,插入等成员函数放在private位置,另外我们可以在public位置写一个成员函数来调用对象的内置成员传参,这样可以在类外调用拷贝构造了.(以下成员函数不另作说明)

赋值运算符重载

当我们显示写了拷贝构造就可以考虑现代写法,利用传值传参调用的拷贝构造产生的
BSTree类型的二叉搜索树与旧的二叉搜索树交换_root就行.

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

析构函数

二叉搜索树的析构函数本质上是后序遍历,排除root为nullptr后,依次遍历左右子树,再对根节点进行删除.

  ~BSTree()
{_Destroy(_root);
}
Node* _Destroy( Node*& root )  //为了让形参的改变能影响到实参,故而取引用.
{if( root == nullptr ){return nullptr;    // 返回上一次函数栈帧.}_Destroy(root->_left);_Destroy(root->_right);delete(root);//当根节点删除完毕后,BSTree的_root为空.return nullptr;
}

插入函数

非递归实现

1: 如果为空树,直接在根节点处插入新节点.
 
2: 如果不为空,那么我们便要先查找要插入的位置.
(a):如果所给值key大于该节点值,那么cur就右遍历,并记录当前cur
的parent.
(b):如果所给值key小于该节点值,那么cur就向左遍历,并记录当前cur的parent.
( c ): 如果所给值key等于该节点值,那么表明插入的节点已经有了,不需要再插入了.

3:然后插入位置后与cur的parent的左右指针建立联系.

bool Insert( const K& key )
{if( _root == nullptr ){_root = new(root);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);           //插入key构造的新节点if( parent->_key < key )      //走到这,parent的左右子树一定为空.{parent->_right = cur;                                       //将新节点与parent的右指针相连.}else{parent->_left = cur;}retrn true;
}

递归实现

递归实现本质上类似与前序遍历思想.
1: 如果根为空,就在根处插入新节点.

2: 如果根不为空,比较key与当前节点root的值
( a ):如果key大于当前节点值,就递归root的右子树.
( b ):如果key小于当前节点值,就递归root的左子树.
( c) : 如果key等于当前节点值,就返回错误.(插入失败)

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

删除函数

非递归实现

查找指定值
二叉搜索树删除时,首先要从根节点循环查找指定值,直到cur为空.
1:如果指定值大于当前结点值,则从当前结点的右子树寻找,并记录当前结点即为父节点.
2: 如果指定值小于当前结点值,则从当前结点的左子树寻找,并记录当前结点即为父节点.
删除指定值
( a )直接删除:
当删除结点只有一个孩子且左为空:
1:如果删除结点cur为根结点,则删除结点的右孩子就为根结点.
2:如果删除结点cur不为根结点,则直接让父结点指向min的右孩子

当删除结点只有一个孩子且右为空:
1:如果删除结点cur为根结点,则删除结点的左孩子就为根结点.
2:如果删除结点不为根结点,则直接让父结点指向右孩子

然后根据上述情况直接对删除结点删除.
( b )替换法删除:
当:删除结点左右都不为空,此时便要寻找右子树的最左结点替换结点值后删除,这样删除后可以保持二叉搜索树左子树的结点都比根结点小,右子树的结点都比根结点大的特性..

注意
对于替换发删除:
 
(a):寻找右子树的最左结点.用cur记录要删除结点,根据右子树的最左结点必为空的特性下不断向左循环查找,并且我们要记录右子树最左节点的父节点,便于父节点链接最左节点的孩子结点.
(b):替换删除.在寻找到右子树的最左结点的情况下,我们最终删除的是与cur交换过结点值的替换结点min,但是我们要先确定min结点在minParent的左边还是右边,这样才能方便与min的孩子连接(min的孩子一定为右结点),这种处理同时也包含了min无孩子这一情况.

如果min在minParent的左边,则需要将minParent的左指针指向min的右孩子后将cur与min的值替换删除.

如果min在minParent的右边,及删除结点右子树的最左结点为空的情况下,此时min就是右子树的最左结点,如果minParent初始化为nullptr,则无法进入循环寻找右子树的最左节点,所以可以将minParent初始化为cur.然后将minParent的右指针指向min的右孩子后将cur与min的值替换删除.

重要图示:
min在minParent的左边.

min在minParent的右边;

代码实现:

bool Erase( const K& key )
{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                   //找到删除结点{ if( cur->_left == nullptr  ) //当cur的左孩子为空{ if( cur == _root )  //如果删除结点为根结点,则直接让_root指向cur的右孩子{_root = cur->_right;              } else{if( parent->_right == cur ){parent->_right = cur->_right; }else{parent->_left = cur->_right;}}    else if( cur->_right == nullptr )//当cur的右孩子为空{ if( cur == _root ) {_root = cur->_left;} else {if ( parent->_left == cur  ){     parent->_left = cur->_left;}else{parent->_right = cur->_left;}  }delete cur;       //删除结点return true;  }else            //左右都不为空{//替换法删除//Node* minParent = nullptr; 不能初始化为nullptr;Node* minParent = cur; Node* min =  cur->_right;while( min ->_left ){minParent = min;min = min->_left;    }//判断min在minParent的左边还是右边.if( minParent->_right == min ){minParent->_right = min->_right;}else{minParent->_left = min->_right;}swap( min->_key,cur->_key );delete min;     }
//走到这里,说明指定结点删除完毕.return true;
}
}
//如果出了循环没有删除,二叉搜索树已经找完了.return false;
}

递归实现

1:首先对空树进行判断,如果为空,则返回false;

2: 根据所给结点值递归查找删除结点.
如果所给值大于当前结点值,就在当前结点的右子树递归查找.
如果所给值小于当前结点值,就在当前结点的左子树递归查找.
3: 删除指定结点
找到删除结点后,此时便要让删除结点父节点与删除节点孩子连接,此时有两种综合情况:
( a ):当删除结点只有一个孩子的情况下:
如果删除结点只有左孩子,则删除结点的父结点与删除结点左孩子连接.
如果删除结点只有右孩子,则删除结点的父节点与删除结点右孩子连接.
( b ): 当删除结点左右孩子都存在:
从删除结点的右子树的最左结点寻找替换点.
交换删除结点与替换点min,从删除结点的右子树递归删除.

//针对递归,公有成员函数调用私有
bool EraseR( const K& key )
{return _EraseR(_root,key );
}bool _EraseR( Node*& root,const K& key )
{     //寻找删除结点if( root == nullptr ){ return false;}if( root->_key < key ){ return _EraseR( root->_right,key );}else if( root -> _key > key ){return _EraseR ( root->_left,key);}else  //找到了,连接关系后删除{Node* del = root;if( root->_left == nullptr ){root = root->_right;  //此时的root实际上是删除结点父节点的右指针.}else if ( root->_right == nullptr ){ root = root->_left;  //此时root实际上是删除结点父节点的左指针.}else{Node* min = root->_right;while( min->_left )  //从右子树的最左结点开始寻找.{min = min->left;                }//走到这,说明已经找到了替换点.swap(root->_key,min->_key );//替换完成后,此时要删除替换点,再次复用递归.return _EraseR( root->_right,key );               }delete del;return true;}}

注意:
在找到替换结点(min)后,由于替换结点min的左孩子必为nullptr,所以再度递归EraseR时,编译器就不会再度走删除

查找函数

非递归实现

非递归查找与插入前的查找几乎一致:

bool Find(const K& key){Node* cur = _root;//如果二叉树不为空的情况下。while (cur){if (cur->_key < key){//parent = cur;cur = cur->_right;}else if (cur->_key > key){//parent = cur;cur = cur->_left;}//找到相等的,return true就行。。else{return true;}}//在循环里都没找到,说明找不到了。return false;}

递归实现

1: 如果为空,则返回false;

2: 如果指定值大于当前结点值,则递归从前结点的右子树寻找.

3: 如果指定值小于当前结点值的,则递从当前结点的左子树寻找.

4: 如果直接值等于当前结点值,就说明查找值已经找到了,返回true.

bool  FindR( const K& key )
{return _FindR( _root,key );
}bool _FindR( Node* root, const K& key )
{if( root = nullptr ){return false;}if( root->_key < key ) //递归从右子树开始查找{return _FindR( root->right,key );}else if ( root->_key > key ) //递归从左子树开始查找{ return _FindR( root->_left,key );}else       //走到这说明找到了.{return true;}}

二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近为完全二叉树),时间复杂度为log2N.

最差情况下,二叉搜索树退化为单支树(或者为单支) ,时间复杂度为O(N);

二叉搜索树的完整代码

struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://注意,如果写了拷贝构造的话,编译器就不会主动生成默认构造函数,而我们有没有写,这是就会没有合适的默认构造,即使我们//在内置成员给上缺省值。BSTree(){}//拷贝构造BSTree(const BSTree<K>& t){_root = _Copy(t._root);}//析构~BSTree(){_Destroy(_root);}bool Inseret(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;}}//找到之后new一个结点给它,可是在插入一个数之后,除了函数作用域cur就被销毁了,编译器找不到//这个结点了,所以链式结构,我们还得找到这个空的父亲的位置。cur = new Node(key);if( parent->_key < key ){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;//如果二叉树不为空的情况下。while (cur){if (cur->_key < key){//parent = cur;cur = cur->_right;}else if (cur->_key > key){//parent = cur;cur = cur->_left;}//找到相等的,return true就行。。else{return true;}}//在循环里都没找到,说明找不到了。return false;}bool Erase(const K& key){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{if (cur->_left == nullptr){//如果删除结点为跟结点if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}//替换后删除delete cur;cur = nullptr;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}//替换后删除。delete cur;}//删除的结点左右都不为空。//替换法删除。else{//如果设置MinParent的初始值为空的情况下,如果删除的结点的为root,//那么替换点要找右树的最左节点,此时min-left为空,不会进入1循环,//anemic在MinParent->_left会导致访问空指针程序崩溃。Node* MinParent = cur;Node* min = cur->_right;while (min->_left){MinParent = min;min = min->_left;} swap(min->_key, cur->_key);if (MinParent->_left == min){MinParent->_left = min->_right;}else{MinParent->_right = min->_right;}delete min;min = nullptr;}return true;}}return false;}bool FindR( const K& key ){return     _FindR(_root,key);}bool  InsertR(const K& key){return _InsertR(_root, key);}bool  EraseR(const K& key){return _EraseR(_root, key);}void Inorder(){_Inorder(_root);}//显示写了拷贝构造就可以考虑使用现代写法,//传值传参,t是实参的临时拷贝,此时不需要加const因为临时拷贝对于实参本身没有任何的关系。//此时能加const对于类型,但是赋值过程中const BSTree<int>类型t已经发生改变了,//引用加const除了考虑权限还得考虑是否改变,类型加cosnt只需要考虑拷贝。BSTree<K>& operator=(  BSTree<K> t){swap(_root,t._root);//通过this指针获取BSTree<int>类型。return *this;}private://析构函数//为了让_root=nullptr为空形参改变实参,取引用。Node* _Destroy(Node*& root){//如果root为nullptr;if (root == nullptr){return nullptr;}//如果不为nullptr_Destroy(root->_left);_Destroy(root->_right);delete root;}private:Node* _root = nullptr;//拷贝构造:Node* _Copy(Node* root){if (root == nullptr){return nullptr; }//生成一个结点Node* CopyRoot = new Node(root->_key);//与结点的左右指针建立关系。CopyRoot->_left = _Copy(root->_left);CopyRoot->_right = _Copy(root->_right);return CopyRoot;}//查找的非递归玩法。bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->_key < key){return  _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}//走到这,说明找到了else{return true;}}//删除的Erase玩法:bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}//删除前先判断值的大小,以便确定删除的位置。if (root->_key < key){return _EraseR(root->_right,key);}else if (root->_key > key){return _EraseR(root->_left,key);}//如果相等就说明找到了,删除。else{Node* del = root;if (root->_left == nullptr){//与被删除的结点建立关系。root = root->_right;}else if (root->_right == nullptr) {    //与被删除的结点建立关系。root = root-> _left;}//删除的第三种情况,删除结点的_left和_right都不为nullptr问题。else{//寻找替换点,在右树的最左节点中寻找。Node* min = root->_right;  while (min->_left){min = min->_left;}//跳出来,说明已经找到了。swap(root->_key, min->_key);//转换为在root的右子树进行递归删除。return _EraseR(root->_right, key);}delete del;return true;}}//插入的递归玩法:bool _InsertR(Node*& root, const K& key){//找到了。if (root == nullptr){root = new Node(key);return;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _Insert(root->_left, key);}else{return false;}}void _Inorder(Node* root){if (root == nullptr){return;}_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}};

二叉搜索树(二叉树进阶)相关推荐

  1. 刷题记录8---验证二叉搜索树+二叉树的层序遍历+从前序与中序遍历序列构造二叉树+二叉树展开为链表+二叉树的最近公共祖先

    前言 所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习 98.验证二叉搜索树 题目: 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树. 有效 二叉搜 ...

  2. pta 是否完全二叉搜索树_二叉树:构造一棵搜索树

    给「代码随想录」一个星标吧! ❝ 构造二叉搜索树,一不小心就平衡了 ❞ 108.将有序数组转换为二叉搜索树 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指 ...

  3. 二叉搜索树的简明实现(ES5 ES6)

    二叉树 & 二叉搜索树 二叉树(Binary Tree)是 n(n >= 0)个节点的有限集合,集合为空集时,叫作空二叉树:不为空时,由根节点及左子树.右子树组成,左子树.右子树也都是二 ...

  4. javascript实现二叉搜索树

    在使用javascript实现基本的数据结构中,练习了好几周,对基本的数据结构如 栈.队列.链表.集合.哈希表.树.图等内容进行了总结并且写了笔记和代码. 在 github中可以看到  点击查看,可以 ...

  5. js遍历树节点下的所有子节点_「Javascript数据结构」× 二叉搜索树

    目录 树的相关术语 二叉树和二叉搜索树 二叉树的遍历 二叉搜索树的搜索 二叉搜索树的移除 「 树的相关术语 」 一个树结构包含一些列存在父子关系的节点.每个节点都有一个父节点(除了顶部的第一个节点)以 ...

  6. 【C++进阶:二叉树进阶】二叉搜索树的操作和key模型、key/value模型的实现 | 二叉搜索树的应用 | 二叉搜索树的性能分析

    [写在前面] 从这里开始 C++ 的语法就告一段落了.二叉树在前面的数据结构和算法初阶中就讲过,本文取名为二叉树进阶是因为: 二叉树进阶有着承上启下的作用,承上就是借助二叉搜索树,对二叉树初阶部分进行 ...

  7. C++初阶学习————二叉树进阶(二叉搜索树)

    二叉树进阶 二叉搜索树的概念 二叉搜索树的操作 基本框架 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 整体代码 循环写法 递归写法 二叉搜索树的应用 二叉搜索树的性能分析 前面的文章介绍过二 ...

  8. 二叉树进阶--二叉搜索树

    目录 1.二叉搜索树 1.1 二叉搜索树概念 1.2 二叉搜索树操作 1.3 二叉搜索树的实现 1.4 二叉搜索树的应用 1.5 二叉搜索树的性能分析 2.二叉树进阶经典题: 1.二叉搜索树 1.1 ...

  9. 【C++】二叉树进阶(二叉搜索树)

    文章目录 前言 1.二叉搜索树 1-1. 二叉搜索树概念 2.二叉搜索树操作 2-1.树和节点的基本框架 2-2.二叉搜索树的查找 2-3.中序遍历 2-4.二叉搜索树的插入 2-5.二叉搜索树的删除 ...

  10. 【二叉树进阶】红黑树(Red Black Tree) - 平衡二叉搜索树

    文章目录 一.红黑树的概念 二.红黑树的性质 2.1 红黑树和AVL树效率对比 三.红黑树的结构(KV模型) 四.红黑树的插入 4.1 插入节点 4.2 平衡化操作(难点) 4.2.1 情况一 4.2 ...

最新文章

  1. 英雄探长的机器人怎么拼_LOL路人局都畏惧的辅助英雄,新版机器人布里兹上手指南...
  2. python项目实战:最简单的图片转字符画
  3. BZOJ 1460 Pku2114 Boatherds
  4. python计算数组元素的和_python中数组的运算
  5. python作用域顺序排列_详解Python函数作用域的LEGB顺序
  6. 成都Uber优步司机奖励政策(3月28日)
  7. android学习笔记---63-PopupWindow,泡泡窗口的实现
  8. 周记 2019.3.25~2019.3.31
  9. 《大型数据库技术》MySQL管理维护
  10. windows播放声音
  11. Sping和springBoot框架技术栈
  12. 加速你的vs.net开发环境
  13. 并发编程-------操作系统的发展
  14. 【软件】XPS格式文件怎么打开,用XPSViewer(百度云免费下载链接)
  15. Exadata使用EXAchk进行健康检查
  16. 16进制ff转化为二进制_二进制与十六进制的转换与应用
  17. 【矩阵论】矩阵的相似标准型(4)(5)
  18. PHP的优势是什么?
  19. 店盈通:你肯定不知道,拼多多如何打造基础销量
  20. 自称菜鸟的二本大龄程序员居然拿到百度offer,还有嘉实offer(百度三面面经)

热门文章

  1. kotlin发音!腾讯3轮面试都问了Android事件分发,已开源
  2. 激光切割机,激光切割机的几种导入方式
  3. 关于《k-means算法在流式细胞仪中细胞分类的应用》的学习笔记总结
  4. PostgreSQL 10 基于日志的备份与还原
  5. 将EXCEL根据经纬度发布成地图,并且在单击时获取点数据
  6. google earth engine(GEE)进行非监督分类
  7. 轻松写日记---日记软件
  8. 如何用python编写抢购程序源码_Python实现淘宝秒杀聚划算抢购自动提醒源码
  9. 杰出校友录管理系统C语言,校友录管理系统概要设计.doc
  10. 超好用的网页版组态编辑器BY组态