本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/

二叉搜索树简介

顾名思义,二叉搜索树是以一棵二叉树来组织的,这样的一棵树可以用一个链表数据结构来表示,每个节点除了key和卫星数据(除了二叉树节点的基本数据以外人为添加的数据,这些数据和树的基本结构无关),还有left、right、parent,分别指向节点的左孩子、右孩子和父节点,如果对应的节点不存在则指向NIL节点(因为最简单的二叉搜索树中的NIL节点里并没有有用的信息,所以在实现的时候简单的指向null也可以,本文的代码部分就会这么处理)。

二叉搜索树的性质

1,任意节点x,其左子树中的key不大于x.key,其右子树中的key不小于x.key。

2,不同的二叉搜索树可以代表同一组值的集合。

3,二叉搜索树的基本操作和树的高度成正比,所以如果是一棵完全二叉树的话最坏运行时间为Θ(lgn),但是若是一个n个节点连接成的线性树,那么最坏运行时间是Θ(n)。

4,根节点是唯一一个parent指针指向NIL节点的节点。

5,每一个节点至少包括key、left、right与parent四个属性,构建二叉搜索树时,必须存在针对key的比较算法。

下面给出一张wiki百科上的二叉搜索树的图示

二叉搜索树的操作

二叉搜索树的基本结构(C++)

 //节点结构
struct Node{
Node(int k=):key(k){}
Node* parent = nullptr;
Node* left = nullptr;
Node* right = nullptr;
int key;
};
//我把二叉搜索相关的基本方法放到了一个类里
class MyBST{
private:
Node* root=nullptr;
public:
MyBST(){};
MyBST(vector<int> v);
Node* getRoot(){ return root; }
void insertNode(int k);
void deleteNode(int k);
Node* findByKey(int k);
Node* findSuccessor(int k);//寻找后继节点
Node* findPredecessor(int k);//寻找前驱节点
void traversal(Node* root, void(*f)(int k) = [](int k)->void{cout <<k << " "; });//遍历输出所有节点 void insertNode(Node* n);
void deleteNode(Node* n);
Node* findSuccessor(Node* n);
Node* findPredecessor(Node* n);
};

基本的构造函数代码如下(另一个是是空的就当成内联函数写在声明里了,同样是内联函数的还有getRoot())

 MyBST::MyBST(vector<int> v):MyBST(){
for (auto n : v){
insertNode(n);
}
}

下面我们来实现类中所有的方法,并同时了解二叉搜索树操作的细节。

查找操作

查找具有特定key的节点

当我们要查找一个具有给定key的节点时,我们从根节点开始与节点的key值进行比较,若是小于此节点的key则继续比对这个节点的左孩子的key,若是大于此节点的key,则继续比对这个节点的右孩子的key,直到key相等返回节点,或者查找失败,节点不存在返回null,这是一个明显类似递归查找的过程,但是我们可以用一个循环来代替这个递归过程,这样的效率更好一点。

 Node* MyBST::findByKey(int k){
Node* temp = root;//获取根节点
while (temp != nullptr){
if (k == temp->key)//当key匹配的时候返回匹配节点
return temp;
temp = k < temp->key ? temp->left : temp->right;//通过比较key的值来决定搜索向哪一棵子树进行
}
cout << "can't find" << endl;
return nullptr;
}

查找特定节点的前驱

节点x的前驱,就是指key值小于x.key的节点中key值最大的那个,若x的左子树不为空,则x前驱是x节点左子树里最靠右的那个节点,如果x的左子树为空,那么我们就要向上找x的第一个有右孩子且左子树里没有x节点的祖先。(此时x就相当于这个祖先的后继,结合后继的查找方式来理解第二种情况会比较简单,看看图中两个节点的位置关系会很有助于理解)

 Node* MyBST::findPredecessor(Node* n){
if (n->left != nullptr){//若x的左子树不为空,则x前驱是x节点左子树里最靠右的那个节点
n = n->left;
while (n->right != nullptr)
n = n->right;
return n;
}
while (n->parent != nullptr&&n->parent->left == n)//如果x的左子树为空,那么我们就要向上找x的第一个有右孩子且左子树里没有x节点的祖先
n = n->parent;
return n->parent;
} Node* MyBST::findPredecessor(int k){
return findPredecessor(findByKey(k));
}

查找特定节点的后继

节点x的后继,就是指key值大于x.key的节点中key值最小的那个,若x的右子树不为空,则x后继是x节点右子树里最靠左的那个节点,如果x的右子树为空,那么我们就要向上找x的第一个有左孩子且右子树里没有x节点的祖先。(此时x就相当于这个祖先的前驱,结合前驱的查找方式来理解第二种情况会比较简单,所以前驱和后继的查找要两个一起看,因为如果x是y的前驱,那么y就是x的后继,所以画一个图来看他们的位置关系会有助于理解,可以试试上面那张二叉树的图例)

 Node* MyBST::findSuccessor(Node* n){
if (n->right != nullptr){//若x的右子树不为空,则x后继是x节点右子树里最靠左的那个节点
n = n->right;
while (n->left != nullptr)
n = n->left;
return n;
}
while (n->parent != nullptr&&n->parent->right == n)//如果x的左子树为空,那么我们就要向上找x的第一个有右孩子且左子树里没有x节点的祖先
n = n->parent;
return n->parent;
}
Node* MyBST::findSuccessor(int k){
return findSuccessor(findByKey(k));
}

遍历操作

如果我们想按照key的从小到大的顺序遍历整个树,我们只要对树进行中序遍历即可,另外特别说明一下遍历函数的两个参数

void traversal(Node* root, void(*f)(int k) = [](int k)->void{cout <<k << " "; });//遍历输出所有节点

第一个参数是为了进行递归而设立的。

第二个参数是一个函数指针,这个函数指针指向一个没有返回值同时有一个int类型参数的函数,并且我用lamda表达式给这个函数指针赋了一个默认值,这个默认函数的功能是输出参数k,被我用来遍历输出每一个节点的key值,大家可以用别的lamda表达式或者函数来覆盖这个默认值,以达到定制功能的目的(和二叉搜索树的性质关系不大,是博主用来练手兼复习用的= =)

 void MyBST::traversal(Node* root, void(*f)(int k)){
if (root == nullptr)
return;
traversal(root->left);
f(root->key);
traversal(root->right);
}

插入操作

新插入的节点一定会取代一个原来的叶子节点,我们只要确定被取代的是哪一个叶子节点就行了,我们从根节点开始,利用二叉搜索树的性质比对key值来向下查找,直到到达最后的叶子节点,这个节点就是要用插入节点替换的叶子节点。

 void MyBST::insertNode(int k){
Node* n = new Node(k);
insertNode(n);
} void MyBST::insertNode(Node* n){
Node* temp = root;
if (temp==nullptr){//树为空就设置根节点
root = n;
return;
}
while (true){//这个循环里有一个大的if-else结构,用来决定插入到左子树还是右子树
if (temp->key > n->key){
if (temp->left != nullptr)//里层各有一个if-else结构判断是否已经到达要插入的地方,到达则替换,没有则深入
temp = temp->left;
else{
temp->left = n;//因为我们用null代替了NIL节点,所以替换时只需要修改两个指针
n->parent = temp;
return;
}
}
else{
if (temp->right != nullptr)
temp = temp->right;
else{
temp->right = n;
n->parent = temp;
return;
}
}
}
}

删除操作

删除的情况比插入复杂,一共有3种可能的情况。

1,被删除的节点x没有NIL节点以外的孩子节点时,直接删除,修改父节点指针指向NIL节点即可。

2,被删除的节点x只有一个孩子时,用这个孩子节点替换被删除节点的位置即可。

3,被删除的节点x有两个孩子时,我们就要查找x节点的后继y节点,注意y节点一定在x节点的右子树中而且y节点没有左孩子,此时,我们先用y节点的右孩子代替y节点原先的位置,然后再用y节点代替x节点位置即可完成删除操作。(其实我们用x的前驱节点代替x也是可以的,不过本文只写一种情况,另一种情况是类似的,大家可以自行实现)

 void MyBST::deleteNode(Node* n){
Node* temp;//用来存取代n节点位置的节点,下面的if分三种情况确定取代n节点位置的节点temp的取值
if (n->left == nullptr&&n->right == nullptr)//情况一:n没有孩子节点
temp = nullptr;
else if (n->left == nullptr || n->right == nullptr){//情况二:n有一个孩子节点
temp = (n->left == nullptr) ? n->right : n->left;//我们用这个孩子节点当做取代n的节点
temp->parent = n->parent;//因为temp要取代n,所以要用temp复制n的属性,因为temp就是n的孩子节点之一,且另一个孩子节点是nullptr,所以n的孩子节点的信息不用复制
}
else{//情况三:n有两个孩子节点
temp = findSuccessor(n);//我们用n节点的后继y当做取代n的节点
Node* successor_right;//y只可能有一个右孩子或者没有孩子,我们先要让y的右孩子取代y,再让y取代n
if (temp->right == nullptr)//如果y没有孩子,则用nullptr取代y
successor_right = nullptr;
else{
successor_right = temp->right;
successor_right->parent = temp->parent;//y有右孩子的时候要处理右孩子的父节点
} if (temp->parent->left == temp)//这个if用来让y的父节点指向取代y的节点
temp->parent->left = successor_right;
else
temp->parent->right = successor_right;
//接下来要让y取代n了,其实我们只需要把y的key值给n就行了,然后直接退出,不需要再修改其他的部分了
n->key = temp->key;
delete temp;
return;
}
//情况一和情况二到此为止取代n节点的temp已经确定,而且当temp不是nullptr的时候,temp也已经复制了必要的n的属性,剩下的就是让n的父节点指向temp了
//注意被删除的节点有可能是根节点,所以当我们发现被删除的是根节点时,不需要让n的父节点指向temp,因为n没有父节点了,但是这个时候必须修改root指针的指向
if (n->parent != nullptr){
if (n->parent->left == n)
n->parent->left = temp;
else
n->parent->right = temp;
}
else
root = temp;//修改root指针的指向
delete n;
}

到此二叉搜索树基本相关操作实现完成,大家可以自行调试输出观察效果(记得加上必要的头文件哦)

参考资料:

1,《算法导论 中文版》(英文版第三版)(美)ThomasH.Cormen,CharlesE.Leiserson,RonaldL.Rivest,CliffordStein 著;王刚,邹恒明,殷建平,王宏志等译。

2,WIKI百科https://en.wikipedia.org/wiki/Binary_search_tree

数据结构-二叉搜索树(BST binary search tree)的更多相关文章

  1. 二叉搜索树BST(Binary Search Tree)

    二叉搜索树(Binary Search Tree)也叫二叉排序树或二叉查找树.它满足以下性质: 1.非空左子树的所有键值小于其根结点的键值: 2.非空右子树的所有键值大于其根结点的键值: 3.左右子树 ...

  2. 数据结构——二叉搜索树(Binary Search Tree)

    二叉树(Binary Tree)的基础下 每个父节点下 左节点小,右节点大. 节点的插入: 若root==NULL则root=newnode 否则不断与节点值比较,较小则向左比较,较大则向右比较. 完 ...

  3. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

  4. 二叉搜索树(Binary Search Tree)

    二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树. 二叉搜索树:一棵二叉树,可以为空:如果不为空,满足以下性质: 非空左子树的所有键值小于其根结点的键值: 非空右 ...

  5. 【数据结构05】红-黑树基础----二叉搜索树(Binary Search Tree)

    目录 1.二分法引言 2.二叉搜索树定义 3.二叉搜索树的CRUD 4.二叉搜索树的两种极端情况 5.二叉搜索树总结 前言 在[算法04]树与二叉树中,已经介绍过了关于树的一些基本概念以及二叉树的前中 ...

  6. [Swift]LeetCode98. 验证二叉搜索树 | Validate Binary Search Tree

    Given a binary tree, determine if it is a valid binary search tree (BST). Assume a BST is defined as ...

  7. [Swift]LeetCode173. 二叉搜索树迭代器 | Binary Search Tree Iterator

    Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the ro ...

  8. 原生JS实现二叉搜索树(Binary Search Tree)

    1.简述 二叉搜索树树(Binary Search Tree),它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子 ...

  9. [Swift]LeetCode99. 恢复二叉搜索树 | Recover Binary Search Tree

    Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing ...

随机推荐

  1. [COGS 1535] [ZJOI2004]树的果实 树状数组+桶

    我们用树状数组做差就可以解决一切问题,我用桶排并用此来表示出第几大就可以直接求前缀和了 #include<cstdio> #include<algorithm> #define ...

  2. Prepare and Deploy Windows Server 2016 Active Directory Federation Services

    https://docs.microsoft.com/en-us/windows/security/identity-protection/hello-for-business/hello-key-t ...

  3. 转:nginx入门指南,快速搭建静态文件服务器和代理服务器

    本文介绍 Nginx 入门基础知识,让你迅速搭建 Nginx 服务器.主要内容包括 Nginx 安装和简单使用.Nginx的简单原理.Nginx 配置文件的结构.如何使用 Nginx 来提供静态文件服 ...

  4. java bigdemical比较大小

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/qq_33451004/article/details/71247041 java中对bigdimic ...

  5. USACO月赛2005 january volume

    2013-09-18 08:12 由题可知,ans=∑i  ∑j(x[i]-x[j]) 最后整理完之后应该是不同系数的X[i]相加,所以这道题就成了求不同x[i]的系数 对于X[i],它需要减前面(i ...

  6. hdu 2817 A sequence of numbers(快速幂取余)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2817 题目大意:给出三个数,来判断是等差还是等比数列,再输入一个n,来计算第n个数的值. #inclu ...

  7. python发布包到pypi的踩坑记录

    前言 突然想玩玩python了^_^ 这篇博文记录了我打算发布包到pypi的踩坑经历.python更新太快了,甚至连这种发布上传机制都在不断的更新,这导致网上的一些关于python发布上传到pypi的 ...

  8. UVALIVE 5096 Volume

    This time your job is to calculate the volume of a special object. The object consists of two orthog ...

  9. UVALIVE 3891 The Teacher's Side of Math

    One of the tasks students routinely carry out in their mathematics classes is to solve a polynomial ...

  10. opencv-写入AVI视频文件

    #include <cv.h> #include <highgui.h> int main(int argc, char **argv) { CvCapture* captur ...