初见 | 数据结构 | 二叉堆 + 二叉树
二叉树 & 二叉堆
把这两个放一起的原因很简单:
二叉堆可以看作是一个完全二叉树
这一篇将会比较短,因为这是1.5周前学的了
二叉树
顾名思义,就是度最大为2的一棵树
(感谢GRAPH EDITOR让我不必再用手糊图)
但是二叉树不只是有这一种形式,它还可以是:
- 空二叉树
- 只有根节点的二叉树
- 只有左子树的二叉树
- 只有右子树的二叉树
下图从左到右就依次是上面的第二~四种
那么二叉树作为一棵树,为什么要拿他出来单独讲呢?
那就是因为它有很多的性质,而且比较好研究
于是乎,就让我们来列举一下二叉树的基础性质吧
二叉树的基础性质
(以下二叉树简称BT,即Binary Tree,因为我太懒了,)
- 在第i层上**至多有2^i-1个结点 **
- 深度为k的BT至多有(2^k)-1个结点(这里的根结点的深度为1)
- (先引入一个东西,n0表示度为0的结点的个数和,即叶节点的个数和,n1和n2同理)若总叶数设为n0,则n2与n0之间的关系为:n0=n2+1
- 具有n个结点的BT的深度为floor(log2n)+1
- 对于任意的完全二叉树,编号为i的结点的孩子的编号为(2×i)+1 Or 2×i,它的父结点是i/2(这里是指整除)
- 实际上还有其他的,但是我不常用,就先放这些......
打完之后,突然想起要XXBT
小心冰糖/谢谢冰糖(×)
小心二叉树(√)
果然是我D多了,反思(雾)
至于前文提到的“完全二叉树”,推荐BDFS,这个我不是很会解释,图如下
那我们知道了这些有肾么用呢?没想到吧,下面我基本上用不到,诶嘿~
当然要打初赛的时候还是得知道的
BT的存储结构
这里分为两个类别,所以我直接打出第一个类别:
链式存储
这里又有两种,我直接又双叒叕打出第一个类别:
单链式
顾名思义,单链式就是只能知道自己孩子却忘了他爹的一条道走到家族最后一代的链
typedef struct node;
typedef node* tree;
struct node
{
char data;//当前结点的编号
tree lchild,rchild;//左右孩子
};
tree bt;
双链式
类比上面的单链式可得:
单链式就是能知道自己孩子也能记住他爹的一条道走到家族最后一代但是还能回来查家谱的链
typedef struct node;
typedef node* tree;
struct node
{
char data;//当前结点的编号
tree lchild,rchild,father;//左右孩子和它爹
};
tree bt;
顺序存储(数组+指针/类指针)
实际上这个形式我是真的没用过,声明3或4个数组,表示当前结点(的编号),左孩子,右孩子,当然如果要查族谱的话就再来一个表示父亲
然后用下标保证一一对应即可,这样的话一个指针/类指针大约就能解决
遍历BT
为了下面的方便,我先解释三条语句:
cout<<bt->data;
语句1,表示输出当前结点编号
order(bt->lchild);
语句2,表示遍历左子树
order(bt->rchild);
语句3,同理,表示遍历右子树
对于BT的遍历,这里有三种顺序:
- 先序遍历
- 中序遍历
- 后序遍历
这三种只要调换一下上面的语句顺序就可以实现
- 先序遍历:123
- 中序遍历:213
- 后序遍历:231
二叉堆
堆,是一种数组对象,也就是说,它一般是用数组来实现的
画出图来可以看做一颗完全二叉树
堆,分为两种:大根堆和小根堆
基本的操作就是Put(加入)和Get(取出)两种操作
当然,我们在取出一个元素和加入一个元素的时候,要维护这个堆
要不然你以为堆是一次性的?
话不多说,直接上代码:
void puto(int x)
{
int son,pa;
heap[++heap_size];
son=heap_size;
while(son>1)
{
pa=son>>1;
if(heap[son]>=heap[pa]) break;
//上面这个是求小根堆,若是大根堆就将不等号方向反一下即可
else swap(heap[son],heap[pa]);
son=pa;
}
}
void geto()
{
int pa,son;
heap[1]=heap[heap_size];
heap_size--;
pa=1;
while((pa+pa)<=heap_size)
{
son=pa+pa;
if(son<heap_size && heap[son+1]<heap[son]) son++;
if(heap[pa]<=heap[son]) break;
//和上面那个注释一样
else swap(heap[pa],heap[son]);
pa=son;
}
}
当然了,STL总能给我们带来惊喜
void geto()
{
pop_heap(heap+1,heap+1+heap_size,greater<int>());
//若是要求大根堆,把greater<int>()去掉即可
heap_size--;
}
void putos(int x)
{
heap[++heap_size]=x;
push_head(heap+1,heap+1+heap_size,greater<int>())
}
但是STL带来的惊喜远不止这些
假如我们不取堆里面的某一个数的话,那么.....
优先队列也可以完成堆的任务,即完成堆排序,而且写法很简单
priority_queue <int,vector<int>,greater<int> > pq;//小根堆,若是大根堆只留下第一个int即可
那么,入一个元素只需要pq.push()
出一个只需要pq.pop()
取根节点就只需要pq.top()
至于具体怎么实现,就跟题意来就好喽~
END
可算是水完了这篇
2021.3.24