博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
二叉搜索树(BST)学习笔记
阅读量:5289 次
发布时间:2019-06-14

本文共 2973 字,大约阅读时间需要 9 分钟。

BST调了一天,最后遍历参数错了,没药救了……

本文所有代码均使用数组+结构体,不使用指针!

前言——BST是啥

BST 二叉搜索树是基于二叉树的一种树,一种特殊的二叉树。

二叉搜索树要么是一颗空树,要么满足一下特点(性质)的二叉树:

  1. 它的左子树要么为空,要么它(左子树)的所有节点均小于它的根节点。
  2. 它的右子树要么为空,要么它(右子树)的所有节点均大于它的根节点。
  3. 它的左、右子树也分别是二叉搜索树。

直观的说,如果中序遍历一棵二叉搜索树,则会产生一个有序数列。

如:1.PNG,中序遍历会产生序列:1 2 5 6 8 9

No.1——算法复杂度分析

二叉搜索树是"排过序"的二叉树,并非"用于排序"的二叉树。

它的优势在于"有序性",而且其插入和查找的时间复杂度均为\(O(h)\),一般情况下\(h=O(\log_{2}n)\),n代表节点数,h代表树的高度。堆的插入算法虽然时间复杂度为\(O(\log_{2}n)\),但并不具有有序性。

No.2——使用范围

对比上述的的分析,发现BST的特点是:

  1. 有序
  2. 插入、查找等算法高效

因此,BST使用范围是:要经常对有序数列进行"动态的"插入或查找等工作

No.3——基本操作

BST的基本操作很多,一时半会也讲不过来,就从易到难的讲吧。

No.3-1——三种遍历方式

BST的遍历与二叉树和树一样,有三种:先序遍历、中序遍历、后续遍历。

三种遍历的方式:

  1. 先序遍历:根→左→右 (DLR)
  2. 中序遍历:左→根→右 (LDR) (结合定义,想一想,为什么中序遍历就是有序的??)
  3. 后续遍历:左→右→根 (LRD)

想必大家都知道了吧,上代码。

/*========遍历========*/void bl(int how,int now){    if(how==1){        //先序遍历        cout<
<<" "; if(a[now].l!=-1) bl(1,a[now].l); if(a[now].r!=-1) bl(1,a[now].r); } if(how==2){ //中序遍历 if(a[now].l!=-1) bl(2,a[now].l); cout<
<<" "; if(a[now].r!=-1) bl(2,a[now].r); } if(how==3){ //后续遍历 if(a[now].l!=-1) bl(3,a[now].l); if(a[now].r!=-1) bl(3,a[now].r); cout<
<<" "; }}

No.3-2——建树与插入

强烈建议使用父亲孩子表示法!!!

所谓建树,就是构建一颗树,建树的时候必然涉及到插入。

也没有什么好说的,根据定义走,他怎么说,你怎么做。

  1. 第一个为根节点
  2. 比根大,往右对比
  3. 比根小,往左对比
  4. 如果当前为空,插入成功。

就这四步,代码来了

/*========插入========*/void into(int sum,int now,int tot){    if(sum
a[now].data) if(a[now].r!=-1) into(sum,a[now].r,tot); else { a[now].r=tot; a[a[now].r].data=sum; a[a[now].r].fa=now; }}/*========构建========*/void init(){ cin>>n; int x,tot; for(tot=1;tot<=n;tot++){ scanf("%d",&x); if(tot==1) a[tot].data=x; else into(x,1,tot); }}

No.3-3——查找

查找也是二叉搜索树必不可少的一个操作,我这里find()返回了是第几个数,方便删除。

实现很简单,只用熟练掌握二叉搜索树的性质,便可轻易打出以下代码:

/*========查找========*/int find(int now,int sum){    cout<
<
a[now].data) if(a[now].r!=-1) return find(a[now].r,sum); else return 0;}void init2(){ cin>>n; int i,x; for(i=1;i<=n;i++){ cin>>x; if(find(ROOT,x)) printf("Yes\n"); else printf("No\n"); }}

No.3-4——求前驱后继

前驱后继就是在中序遍历时他的前一个与后一个,如图:

5cbad7361312f.png

如图,八的前驱是六,后继是九。

这太简单了!

放到树上说,前驱就是该节点左儿子的最右节点,后继则是该节点右儿子的最左节点

放一个前驱的代码,后继自己推!

/*========前驱========*/int pred(int now){    if(a[now].r!=-1) return pred(a[now].r);    else return now;}

No.3-5——删除

本操作有一定难度,请务必弄懂

删除要分一些情况讨论,见下:

  1. 没有孩子
  2. 只有一个孩子
    3-1. 有两个孩子
    3-2. 删除根

第一种情况好办,删掉就行了。

第二种情况也行,删掉后接上左/右孩子。

第三种情况有些复杂,需要用到前驱/后继(用哪个没有一定要求),用它的前驱/后继代替它,同时删除(一个递归过程,又一次调用删除函数)它的前驱/后继。

看一组图吧(有点大):

Delete.png这是一颗二叉搜索树,我们要删掉3。

Delete2.png找到前驱。
Delete3.png替换。
Delete4.png最后变成这个样子。

最后讲一讲如何删根节点,其实与其他删除差不多,只需在开个ROOT变量存储根是谁就行了(根默认为1)

因为一些原因,不放代码。(其实是我太懒了)

No.4——写在最后

其实个人认为二叉搜索树的实用价值不大,主要用于练手与预备知识,比方说Treap、平衡树、堆……都需要用到二叉搜索树的性质。

综合代码算了,删除你们自己打吧!

No.4-1——练习题

但,算法之路,才刚刚开始!

转载于:https://www.cnblogs.com/Garbage-Only-one/p/10741349.html

你可能感兴趣的文章
v-text和v-html的区别
查看>>
_self.$scopedSlots.default is not a function报错
查看>>
['1', '2', '3'].map(parseInt) what & why ?
查看>>
43 道检验基础的 JavaScript 面试题
查看>>
Webstorm轻松部署项目至服务器
查看>>
ueditor的初始化赋值
查看>>
点击导航平滑滚动到指定锚点
查看>>
CF387B 【George and Round】
查看>>
CF450A 【Jzzhu and Children】
查看>>
CF171C 【A Piece of Cake】
查看>>
CF39H 【Multiplication Table】
查看>>
CF235A 【LCM Challenge】
查看>>
centos7 安装lamp
查看>>
centos7搭建ftp
查看>>
vsftp多个用户公享同一个文件,但是权限不同
查看>>
第十五章、Python多线程同步锁,死锁和递归锁
查看>>
第十五章、Python多线程之信号量和GIL
查看>>
第十一章、特性property
查看>>
第十三章、面向过程高阶
查看>>
第十二章、类和对象的绑定方法及非绑定方法
查看>>