#二叉查找树
###开篇前的一水
顺着书本上的顺序,,看完哈希表,算是复习了 二叉搜索树……
一开始还在想这个东西思想这么简单的实现应该不难,结果睡了一觉起来发现有些操作还真写不来,,果断重新学习……
还真发现了好多快忘光的东西了
比如各种遍历顺序……要不是这次还真不一定能想起来
言归正传。。。
###什么是二叉搜索树
首先这东西是一棵二叉树,但是需要注意的是,这只是一棵最普通的二叉树,并不一定是某些特例,比如完全二叉树之类的,,
和二叉堆类似,这玩意也是从二叉树上延申过来的,,简单说就是对其中的节点加上了某种限制,然后可以完成某种功能,因为每个节点都需要满足,所以这棵树就会有了这么一种性质。
二叉搜索树,要求每个节点的左儿子的值都要小于自己,右儿子的值要大于自己,然后大家可以用递归的思想去模拟模拟,你就会发现,以某棵树(或者子树)的根节点来看,左儿子为根的子树上的所有值都小于根节点的值,右儿子为根的子树的值都要大于根节点的值;;还有就是存储了最小值的节点一定是树中最靠左的叶子节点,存储了最大值的节点一定是树中最靠右的叶子节点。
当然,二叉搜索树之所以起这个名字,是因为可以根据它的特点进行类似二分一样的查找某一个值是否存在,不过需要注意,有序数组上的二分查找,每次都是从中间开始,但是二叉搜索树我们每次是从根节点开始,然而我们不能保证根节点就是这个序列的中间,更有特殊情况,二叉搜索树会退化称一根链,这也就引出了之后的平衡树等等……
###先说说查找
之前所说的问题平衡树可以很好的处理,但是很多平衡树又是二叉搜索树的扩展(加上各种旋转伸展操作……),所以为了以后的学习,我们先把这个二叉搜索树学好。。
第一个功能,当然要说最最简单无脑最好实现的了,,,(:з」∠)
最简单的,可以说是找最大最小值了,之前说过的,就是顺着树的左、右一直走,一直到叶子节点就好
struct node {
int v;
node *l, *r;
}
node* find_min (node *root) {
if (root) {
while (root->l) root = root->l;
return root;
}
return NULL;
}
node* find_max (node *root) {
if (root) {
while (root->r) root = root->r;
return root;
}
return NULL;
}
可以看出来代码十分对称,一个往左,一个往右。。
但是要单独注意一下根节点为空的情况……返回一个NULL
接下来我们来扩展到普通的搜索操作
bool find_ (node *root, int val) {
while (root) {
if (val == root->v) return true;
else if (val < root->v) root = root->l;
else root = root->r;
}
return false;
}
这里我返回的是bool型,找到返回真,没找到返回假,代码也比较直观,带入二叉搜索树的特性,还是很容易理解的
###插入
搜索写好了,但是首先得要有一棵树才能搜索,于是我们开始介绍插入操作
插入操作,简单说就是根据搜索树的性质,找到能插入节点的第一个位置,然后新建一个节点就好,显然,这个位置所指一定是空,,并且,如果插入的值之前已经存在,那么就不用操作了,,可以想想为什么
所以代码如下:
node* new_node (int val) {
node* ret = new node;
ret->l = ret->r = 0;
ret->v = val;
return ret;
}
void insert_ (node *&root,int val) {
if (!root) {
root = new_node (val);
}
if (val == root->val)
return;
else if (val < root->val)
insert_ (root->l, val);
else
insert_ (root->r, val);
}
注意,上面insert_的参数root是一个引用指针变量,是为了能把新建的节点正确的保存。
有了插入,那么肯定也要有删除,接下来就说说删除操作
###删除
如果说前面的还直观,好像一些,那么删除操作就是有那么一些难度的一个操作了
首先要明白,如果我们真的把一个树中有两个儿子的节点删了,那么,,这个节点剩下的俩个儿子如何链接,如果两个儿子都是叶子节点可能还方便一些,但是,如果两个儿子都代表了一棵子树,那么接下来操作的复杂度可想而知。。
那么反过来想想,什么情况下删除节点是最方便的。。。
- 首先,如果要删除的节点只有一个儿子,那么只需要把该节点删除,让儿子接上自己的位置即可
- 其次就是普通情况,也就是有两个儿子的节点了:我们细细考虑
我们想象一下,该**节点(del)所代表的子树其实也是一棵二叉搜索树,那么其(中序遍历)肯定是代表一个有序序列,而这个节点(del)**也只是这个序列中的某个值
比如这棵二叉搜索树所代表的序列为:
1 2 3 4 5 6 7 ,其中要删除的节点(del)是4
如果把这个节点(del)删除,那么序列肯定就变成了1 2 3 5 6 7
再回想二叉搜索树节点的特性:节点的左儿子都比节点小,节点的右儿子都比节点大
我们完全可以从(找前驱和后继)*左儿子代表的子树中找到一个最大的数,或者从右儿子里面找到一个最小的数所代表的节点-1*(注意这里是普通情况,一定有左右两个儿子),把这个节点-1的值代替该节点(del)点,代替之后,我们只需要再删除找到的这个节点-1即可
这里代码演示为从右儿子找到一个最小的代替节点(del),大家要是想写从左儿子的,那也没啥,看个人习惯咯、、
代码:
void delete_ (node *&root, int val) {
if (!root) return;
if (val < root->v) {
delete_ (root->l, val);
} else if (val > root->v) {
delete_ (root->r, val);
} else {
node *p = NULL;
if (root->l && root->r) {
p = find_min (root->r);
root->v = p->v;
delete_ (root->r, root->val);///现在要删除的节点不再是 x 而是 p->v
} else {
p = root;
root = root->l?root->l:root->r;
delete p;///释放节点
}
}
}
可以看到代码还是有点长的……
###前驱与后继
前驱和后继,其实就是以序列的顺序来找某个节点的上一个节点和下一个节点,
比如序列1,2,3,4,5,6,7,,,,4的前驱就是3,后继就是5
看到这,不知你会不会问一个问题:上面的删除不是已经有提示是查找前驱和后继么,当然要是你没问就算了
之前说了,那种情况是对于一个有两个儿子的节点,那么他的前驱自然就是左子树中的最大值,后继自然就是右子树中的最小值。。。
如果换一种情况比如一个节点没有左儿子,那么他的前驱是什么?【滑稽】
先就前驱来说,我们看看一个节点的前驱都有哪些可能,知道了然后if大法解决各种不同情况即可
- 这个节点有左儿子!那么毫无疑问前驱肯定在左子树里面,,这个比较简单,大家自己想想
- 这个节点没有左儿子,但是他是他爹的右儿子,那么他的前驱就是他爹了,这里可能有点不直观,下面会简单解释(感觉直观的往下看即可)
- 这个节点没有左儿子,但是他是他爹的左儿子,,,那我们就先看看他爹,如果他爹是他爷爷的右儿子,那么前驱就是他爷爷,如果不是那么继续看他爷爷是不是他祖爷爷的右儿子……依次往上找(坑)
首先,没有什么是自己画一张图不能理解的,如果有,那么在画一张。。。
(2)中如果这个节点是他爹的右儿子,那么他肯定比他爹大,无论他爹是他爷爷的左儿子还是右儿子,都可得知他爹就是这个节点的前驱
至于三,其实就是在不断重复2……
为了实现前驱,这里struct要加上一个父亲节点的指针:
struct node {
int val;
node *ls, *rs, *pa;
};
node* queryPre (node *rt) {
if (rt->ls) return find_max (rt->ls);
node* pa = rt->pa;
while (pa && rt == pa->ls) {
rt = pa;
pa = rt->pa;
}
return pa;
}
node* queryFol (node *rt) {
if (rt->rs) return find_min (rt->rs);
node* pa = rt->pa;
while (pa && rt == pa->rs) {
rt = pa;
pa = rt->pa;
}
return pa;
}
这里加上了后继,可以看到这两个操作的代码比较对称,,,因为他们的过程,步骤其实是基本相同的,下面是总代码:稍微做过修改
#include <iostream>
using namespace std;
struct node {
int val;
node *ls, *rs, *pa;
} *root;
node* new_nd (int val, node *pa) {
node *r = new node;
r->val = val;
r->ls = r->rs = 0;
r->pa = pa;
return r;
}
node* find_max (node* rt) {
while (rt && rt->rs) rt = rt->rs;
return rt;
}
node* find_min (node* rt) {
while (rt && rt->ls) rt = rt->ls;
return rt;
}
node* find_ (node *rt, int val) {
if (!rt) return NULL;
if (rt->val == val) return rt;
else if (rt->val > val) return find_ (rt->ls, val);
return find_ (rt->rs, val);
}
void insert_ (node *&rt, node *pa, int val) {
if (rt == NULL) rt = new_nd (val, pa);
if (rt->val == val) return;
if (rt->val > val) insert_ (rt->ls, rt, val);
else insert_ (rt->rs, rt, val);
}
void delete_ (node *&rt, int val) {
if (!rt) return;
if (rt->val > val) delete_ (rt->ls, val);
else if (rt->val < val) delete_ (rt->rs, val);
else {
node *pn;
if (rt->ls && rt->rs){
pn = find_min (rt->rs);
rt->val = pn->val;
delete_ (rt->rs, rt->val);
} else {
pn = rt;
rt = rt->ls?rt->ls:rt->rs;
rt->pa = pn->pa;
delete pn;
}
}
}
node* queryPre (node *rt) {
if (rt->ls) return find_max (rt->ls);
node* pa = rt->pa;
while (pa && rt == pa->ls) {
rt = pa;
pa = rt->pa;
}
return pa;
}
node* queryFol (node *rt) {
if (rt->rs) return find_min (rt->rs);
node* pa = rt->pa;
while (pa && rt == pa->rs) {
rt = pa;
pa = rt->pa;
}
return pa;
}
int main () {
}
当然改的匆忙,如果有什么错误请指出,万分感谢