7.2 二叉树与堆
二叉树与堆
二叉树是一种特殊的、常见的树
简介
二叉树的特点在于每个结点最多只有两个儿子
如果要使用更严格的递归定义,则是:
二叉树要么为空,要么由根结点、左子树、右子树组成
而左子树、右子树分别是一棵二叉树
二叉树是使用范围极广的树,一棵多叉树也可以转换为二叉树
二叉树类型
满二叉树:如果二叉树中每个内部结点都有两个儿子,这样的二叉树叫做满二叉树
完全二叉树:如果一棵二叉树除了最右边的位置上有一个或几个叶节点缺少外,其他都是丰满的,那么它就是完全二叉树
我们可以把满二叉树理解为一种特殊的,极其完美的完全二叉树
我们只需要用一个一维数组就能存储完全二叉树
我们将完全二叉树进行从上到下,从左到右的编号,可以得到下面这个结论:
如果已知子结点的编号是x,那么它父结点的编号就是x/2
堆
符合所有父结点都比子结点要小的树,就是最小堆
符合所有父结点都比子结点要大的树,就是最大堆
那么想要向堆中加入一个数据,需要怎么做呢?
比如说我们想要把23加入到一个最小堆中
1.我们把23放入堆顶,检查是否合适
2.如果不合适,我们就将这个数和它的两个儿子比较,并且选择较小者交换位置
3.继续向下交换,检查。重复1-2直到符合条件
当新增加一个数被放置到堆顶时,如果此时不符合最小堆的特性,则需要将这个数向下调整,直到找到合适的位置为止
代码实例_向下调整
void siftdown(int i)
{
//传入一个需要向下调整的结点编号i
//这里传入1,即从堆的顶点开始向下调整
int t,flag = 0 ;
//flag用于标记是否需要继续向下调整
//当i结点有儿子(其实至少是有左儿子的情况下)并且有需要调整的时候,循环就执行
while( i*2 <= n && flag == 0 ){
//先判断它和左儿子的关系,用t记录值比较小的结点编号
if(h[i] > h[i*2])
t = i*2 ;
//子节点是较小的
//和/2得到父节点相对,*2自然得到子节点(左)
else
t = 1 ;
//1是较小值
//如果有右儿子,再对右儿子(*2+1)进行讨论
if(i*2+1 <= n)
{
//如果右儿子的值更小,更新较小的结点编号
if(h[t] > h[i*2 + 1])
t = i*2+1 ;
}
//如果发现最小的结点编号不是自己,那么就说明子结点中,有比父结点更小的
if(t!=i)
{
swap(t,i);//交换它们,这里记得自己写一个swap函数
i = t ;
}
else{
falg = 1 ;
}
}
return ;
}
如果只是想新增一个值,而不要删除掉最小值,那么该怎么做呢?
直接将新元素插入到末尾,再根据情况判断元素是否需要上移,直到满足堆的特性为止
堆排序
与快速排序一样,,堆排序的时间复杂度是O(NlogN)
进行堆排序,需要我们建立对应的堆,每次删除顶部元素并将顶部元素输出或者放到一个数组中,直到堆空
最后输出的或者存放在新数组中的数,就是已经排序好的
下面以从小到大排序为例
代码实例_从小到大堆排序
//先整个删除最小元素的
int deletemin()
{
int t ;
t = h[1];//用一个临时变量记录堆顶点的值
h[1] = h[n];//将堆的最后一个点赋值到堆顶
n--;//使堆的元素减少1
siftdown(1) ; //向下调整
return t ; //返回在之前记录的堆的顶点的最小值
}
完整的代码组成如下:
#include <stdio.h>
int h[101];//用来存放堆的数组
int n ; //用来存储堆中元素的个数,实质上就是堆的大小
//先整个交换函数
void swap(int x ,int y)
{
int t ;
t = h[x];
h[x] = h[y];
h[y] = t ;
return
}
//向下调整的函数,上文已经写过了
void siftdown(int i)
{
//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整
int t,flag = 0 ;
//flag用于标记是否需要继续向下调整
//当i结点有儿子(其实至少是有左儿子的情况下)并且有需要调整的时候,循环就执行
while( i*2 <= n && flag == 0 ){
//先判断它和左儿子的关系,用C记录值比较小的结点编号
if(h[i] > h[i*2])
t = i*2 ;
else
t = 1 ;
//如果有右儿子,再对右儿子进行讨论
if(i*2+1 <= n)
{
//如果右儿子的值更小,更新较小的结点编号
if(h[t] > h[i*2 + 1])
t = i*2+1 ;
}
//如果发现最小的结点编号不是自己,那么就说明子结点中,有比父结点更小的
if(t!=i)
{
swap(t,i);//交换它们,这里记得自己写一个swap函数
i = t ;
}
else{
falg = 1 ;
}
}
return ;
}
//建立堆的函数
void creat()
{
int i ; //从最后一个非叶结点到第一个结点依次进行向下调整
for(i=n/2 ; i>=1;i--)
{
siftdown(i);
}
return ;
}
//删除最大的元素
int deletmax()
{
int t ;
t = h[1];
h[1] = h[n];
b-- ;
siftdown(1);//向下调整
return t ;
}
int main(){
int i , num ;
//读入要排序的数字个数
scanf("%d",&num);
//读入数字
for(i=1;i<num;i++)
scanf("%d",&h[i]);
n = num ;
//建堆
creat();s
//删除顶部元素,连续删除n次,实际上就是从小到大把数输出
for(i=1;i<=num;i++)
printf("%d",deletemax());
getchar();getchar();
return 0 ;
}
小结
像这种,支持插入元素和寻找最值元素的数据结构被称为优先队列
堆就是一种优先队列的实现