数据结构-二叉搜索树(BST binary search tree)

本文由@呆代待殆原创,转载请注明出处: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++)

 1 //节点结构
 2 struct Node{
 3     Node(int k=0):key(k){}
 4     Node* parent = nullptr;
 5     Node* left = nullptr;
 6     Node* right = nullptr;
 7     int key;
 8 };
 9 //我把二叉搜索相关的基本方法放到了一个类里
10 class MyBST{
11 private:
12     Node* root=nullptr;
13 public:
14     MyBST(){};
15     MyBST(vector<int> v);
16     Node* getRoot(){ return root; }
17     void insertNode(int k);
18     void deleteNode(int k);
19     Node* findByKey(int k);
20     Node* findSuccessor(int k);//寻找后继节点
21     Node* findPredecessor(int k);//寻找前驱节点
22     void traversal(Node* root, void(*f)(int k) = [](int k)->void{cout <<k << " "; });//遍历输出所有节点
23
24     void insertNode(Node* n);
25     void deleteNode(Node* n);
26     Node* findSuccessor(Node* n);
27     Node* findPredecessor(Node* n);
28 };

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

1 MyBST::MyBST(vector<int> v):MyBST(){
2     for (auto n : v){
3         insertNode(n);
4     }
5 }

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

查找操作

查找具有特定key的节点

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

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

查找特定节点的前驱

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

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

查找特定节点的后继

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

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

遍历操作

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

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

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

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

1 void MyBST::traversal(Node* root, void(*f)(int k)){
2     if (root == nullptr)
3         return;
4     traversal(root->left);
5     f(root->key);
6     traversal(root->right);
7 }

插入操作

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

 1 void MyBST::insertNode(int k){
 2     Node* n = new Node(k);
 3     insertNode(n);
 4 }
 5
 6 void MyBST::insertNode(Node* n){
 7     Node* temp = root;
 8     if (temp==nullptr){//树为空就设置根节点
 9         root = n;
10         return;
11     }
12     while (true){//这个循环里有一个大的if-else结构,用来决定插入到左子树还是右子树
13         if (temp->key > n->key){
14             if (temp->left != nullptr)//里层各有一个if-else结构判断是否已经到达要插入的地方,到达则替换,没有则深入
15                 temp = temp->left;
16             else{
17                 temp->left = n;//因为我们用null代替了NIL节点,所以替换时只需要修改两个指针
18                 n->parent = temp;
19                 return;
20             }
21         }
22         else{
23             if (temp->right != nullptr)
24                 temp = temp->right;
25             else{
26                 temp->right = n;
27                 n->parent = temp;
28                 return;
29             }
30         }
31     }
32 }

删除操作

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

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

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

3,被删除的节点x有两个孩子时,我们就要查找x节点的后继y节点,注意y节点一定在x节点的右子树中而且y节点没有左孩子,此时,我们先用y节点的右孩子代替y节点原先的位置,然后再用y节点代替x节点位置即可完成删除操作。

 1 void MyBST::deleteNode(int k){
 2     deleteNode(findByKey(k));
 3 }
 4
 5 void MyBST::deleteNode(Node* n){
 6     Node* temp;//用来存取代n节点位置的节点,下面的if分三种情况确定取代n节点位置的节点temp的取值
 7     if (n->left == nullptr&&n->right == nullptr)//情况一:n没有孩子节点
 8         temp = nullptr;
 9     else if (n->left == nullptr || n->right == nullptr){//情况二:n有一个孩子节点
10         temp = (n->left == nullptr) ? n->right : n->left;//我们用这个孩子节点当做取代n的节点
11         temp->parent = n->parent;//因为temp要取代n,所以要用temp复制n的属性,因为temp的孩子节点一个是nullptr一个是n,这里可以省略对孩子节点信息的复制
12     }
13     else{//情况三:n有两个孩子节点
14         temp = findSuccessor(n);//我们用n节点的后继y当做取代n的节点
15         Node* successor_right;//y只可能有一个右孩子或者没有孩子,我们先要让y的右孩子取代y,再让y取代n
16         if (temp->right == nullptr)//如果y没有孩子,则用nullptr取代y
17             successor_right = nullptr;
18         else{
19             successor_right = temp->right;
20             successor_right->parent = temp->parent;//y有右孩子的时候要处理右孩子的父节点
21         }
22         if (temp->parent->left == temp)//这个if用来让y的父节点指向取代y的节点
23             temp->parent->left = successor_right;
24         else
25             temp->parent->right = successor_right;
26         //接下来要让y复制n的属性了,因为temp与n的位置关系不确定,而且y此时已经被y的右孩子取代脱离了二叉树的结构,所以所有的属性都有复制的必要。
27         temp->parent = n->parent;
28         temp->left = n->left;
29         temp->right = n->right;
30     }//到此为止取代n节点的temp已经确定,而且当temp不是nullptr的时候,temp也已经复制了必要的n的属性,剩下的就是让n的父节点指向temp了
31     //注意被删除的节点有可能是根节点,所以当我们发现被删除的是根节点时,不需要让n的父节点指向temp,因为n没有父节点了,但是这个时候必须修改root指针的指向
32     if (n->parent != nullptr){
33         if (n->parent->left == n)
34             n->parent->left = temp;
35         else
36             n->parent->right = temp;
37     }
38     else
39         root = temp;//修改root指针的指向
40     delete n;
41 }

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

参考资料:

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

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

时间: 2024-10-13 16:42:32

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

【算法导论学习-24】二叉树专题2:二叉搜索树(Binary Search Tree,BST)

一.   二叉搜索树(Binary SearchTree,BST) 对应<算法导论>第12章.相比一般二叉树,BST满足唯一的条件:任意节点的key>左孩子的key,同时<右孩子的key. 1.     节点类: public class BinarySearchTreesNode<T> { private int key; private T satelliteData; private BinarySearchTreesNode<T> parent, l

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

目录 1.二分法引言 2.二叉搜索树定义 3.二叉搜索树的CRUD 4.二叉搜索树的两种极端情况 5.二叉搜索树总结 前言 在[算法04]树与二叉树中,已经介绍过了关于树的一些基本概念以及二叉树的前中后序遍历,而这篇文章将是在二叉树的基础上来展开讲解的二叉搜索树,也就是说二叉搜索树建立在树的基础之上.至于博主为何要花一整篇文章来讲这个二叉搜索树呢?原因很简单,红-黑树是基于二叉搜索树的,如果对二叉搜索树不了解,那还谈何红-黑树?红-黑树的重要性我想各位没吃过佩奇肉也肯定看过宜春跑....是的,j

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

1.简述 二叉搜索树树(Binary Search Tree),它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值: 它的左.右子树也分别为二叉排序树. 2.代码说明 首先先创建一个辅助节点类Node,它初始化了三个属性:节点值,左孩子,有孩子. class Node { constructor(value) { this.value = value; this.left =

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

Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing its structure. Example 1: Input: [1,3,null,null,2]   1   /  3     2 Output: [3,1,null,null,2]   3   /  1     2 Example 2: Input: [3,1,4,null,null,2]

二叉搜索树(Binary Search Tree)--C语言描述

一:硬着头皮就是上 数据结构中有个东西一直不愿意去面对,就是二叉搜索树,以及平衡二叉树.想想就耗脑细胞 马上开学了,就要学C++了,还有其他的事,估计更没有时间搞数据结构了,于是狠下心,把二叉搜索树和平衡二叉树给拿下!! 啊啊啊啊,算法很枯燥无聊,不过搞明白了收获多多,不过目前好像没有什么用. 反正安慰自己,这都是内功,修炼好了,以后放大招威力无比啊,嘿嘿. 二:图解二叉搜索树概念 二叉树呢,其实就是链表的一个二维形式,而二叉搜索树,就是一种特殊的二叉树,这种二叉树有个特点:对任意节点而言,左孩

二叉搜索树(Binary Search Tree)--C语言描述(转)

图解二叉搜索树概念 二叉树呢,其实就是链表的一个二维形式,而二叉搜索树,就是一种特殊的二叉树,这种二叉树有个特点:对任意节点而言,左孩子(当然了,存在的话)的值总是小于本身,而右孩子(存在的话)的值总是大于本身. 下面来介绍在此种二叉树结构上的查找,插入,删除算法思路. 查找:因为这种结构就是为了来方便查找的,所以查找其中的某个值很容易,从根开始,小的往左找,大的往右找,不大不小的就是这个节点了: 代码很简单,这里就不写了. 插入:插入一样的道理,从根开始,小的往左,大的往右,直到叶子,就插入.

[数据结构]二叉搜索树(BST) VS 平衡二叉排序树(AVL) VS B树(平衡多路搜索树) VS B+树 VS 红黑树(平衡二叉B树)

1 二叉排序树/二叉查找树/Binary Sort Tree 1种对排序和查找都很有用的特殊二叉树 叉排序树的弊端的解决方案:平衡二叉树 二叉排序树必须满足的3条性质(或是具有如下特征的二叉树) 若它的左子树不为空,则:左子树上所有结点的值< 它根结点的值 若它的右子树不为空,则:右子树上所有结点的值 > 它根结点的值 它的左子树.右子树也分别为二叉排序树(递归性) (按照如上定义,即: 1 无键值相等的结点 2 中序遍历一颗二叉树时,可得一个结点值递增的有序序列) 2 平衡二叉排序树/Bal

数据结构——二叉搜索树、B树、B-树

数据结构——二叉搜索树.B树.B-树 1. 综述 二叉排序树(Binary Sort Tree),又叫二叉查找树(Binary Search Tree),也叫二叉排序树. 二叉搜索树满足以下性质: 1. 若根节点左子树不为空,则左子树上的所有节点均小于根节点: 2. 若根节点右子树不为空,则右子树上的所有节点均大于根节点: 3. 其左右子树也是二叉搜索树(递归定义): 4. 没有键值相等的点. B树就是B-树.B树/B-树英文叫B-Tree,可能被不小心翻译成了B-树.

BST(Binary Search Tree)

原文链接:http://blog.csdn.net/jarily/article/details/8679280 1 /****************************************** 2 数据结构: 3 BST(Binary Search Tree),二叉查找树; 4 5 性质: 6 若结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 7 若结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 8 该结点的左.右子树也分别为二叉查找树; 9 10 遍