本文由@呆代待殆原创,转载请注明出处: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. [Leetcode] Same tree判断是否为相同树

    Given two binary trees, write a function to check if they are equal or not. Two binary trees are con ...

  2. mybatis的mapper文件的大于号特殊符号使用

    第一种方法: 用了转义字符把>和<替换掉,然后就没有问题了. SELECT * FROM test WHERE 1 = 1 AND start_date  <= CURRENT_DA ...

  3. lvm扩容

    111 mkfs -t xfs /dev/sda3 112 pvcreate /dev/sda3 113 vgs 114 vgextend cl /dev/sda3 115 lvscan 116 vg ...

  4. Docker Community Edition for CentOS

    Docker CE for CentOS Docker CE for CentOS distribution is the best way to install the Docker platfor ...

  5. javaScript中的this关键字解析

    this是JavaScript中的关键字之一,在编写程序的时候经常会用到,正确的理解和使用关键字this尤为重要.接下来,笔者就从作用域的角度粗谈下自己对this关键字的理解,希望能给到大家一些启示, ...

  6. C# 序列化原因 (转)

         1.什么是序列化 序列化是将对象状态转换为可保持或传输的格式的过程,在序列化过程中,对象的公共字段和私有字段以及类的名称(包括包含该类的程序集)都被转换为字节流,然后写入数据流.与序列化相对 ...

  7. Hadoop之计数器与自定义计数器及Combiner的使用

    1,计数器: 显示的计数器中分为四个组,分别为:File Output Format Counters.FileSystemCounters.File Input Format Counters和Ma ...

  8. bzoj4759 [Usaco2017 Jan]Balanced Photo

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4759 [题解] 排序,从大到小插入,树状数组统计. # include <vector ...

  9. Django【进阶】

    FBV和CBV http://www.cnblogs.com/lucaq/p/7565560.html 中间件 http://www.cnblogs.com/lucaq/p/7581234.html ...

  10. [Leetcode Week10]01 Matrix

    01 Matrix 题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/01-matrix/description/ Description Given a ...