满二叉树:高度为h,结点数为2*h-1。
完全二叉树:高度为h,1~h-1层结点数满,第h层从右到左缺若干结点。若有N个结点,高度为log2N。
最小堆:在完全二叉树的基础上,所有的父结点都比子结点小。
最大堆:在完全二叉树的基础上,所有的父结点都比子结点大。
问题引入:14个数:99 5 36 7 22 17 46 12 2 19 25 28 1 92,若删除其中最小的元素,并新增k,重复14次
普通的数组遍历:O(n*n)
引入最小堆:
将14个树构造最小堆,用h数组存储,h[1]就是最小的。
将堆顶删除,把k放入堆顶,向下调整,重新构造出最小堆。O(logN)
向下调整:
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
int t,flag=0;/*flag用来标记是否需要继续向下调整*/
while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
{
if(h[i]>h[i*2])/*首先判断它和左儿子的关系,并用t记录值较小的结点编号*/
t=i*2;
else
t=i;
if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
{
if(h[t]>h[i*2+1])
t=i*2+1;
}
if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
{
swap(t,i);
i=t;/*更新i为刚才与它交换的儿子结点的编号*/
}
else
flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
}
}
若新增一个数,插入到末尾,向上调整,使其符合最小堆的特性。
向上调整:
void siftup(int i)/*传入一个需要向上调整的结点编号i*/
{
int flag=0;/*标记是否需要继续向上调整*/
if(i==1)/*堆顶*/
return;
while(i!=1&&flag==0)
{
if(h[i]<h[i/2])
swap(i,i/2);/*交换它和它爸爸的位置*/
else
flag=1;
i=i/2;/*更新编号i为它父结点的编号*/
}
}
如何建立堆?
用数组h存储,把n个数放入完全二叉树中,即放入数组中。
从最后一个节点开始,依次判断以这个结点为根的子树是否满足堆的特性。
若所有的子树都满足堆,那么这个完全二叉树就是最小堆或最大堆。
以叶结点为根的子树其实就是叶结点自己,可以不考虑叶结点。O(N)
二叉树的性质:最后一个非叶结点的编号是N/2。
所以可以从第N/2个结点开始。
建堆及其堆排序(建立最小堆):
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int h[101];
int n;/*堆的大小*/
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
int t,flag=0;/*flag用来标记是否需要继续向下调整*/
while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
{
if(h[i]>h[i*2])/*首先判断它和左儿子的关系,并用t记录值较小的结点编号*/
t=i*2;
else
t=i;
if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
{
if(h[t]>h[i*2+1])
t=i*2+1;
}
if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
{
swap(t,i);
i=t;/*更新i为刚才与它交换的儿子结点的编号*/
}
else
flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
}
}
void creat()
{
int i;
for(int i=n/2; i>=1; i--) /*从最后一个非叶结点到第1个结点依次进行向上调整*/
siftdown(i);
}
int deletemax()
{
int t;
t=h[1];/*记录堆顶点的值*/
h[1]=h[n];/*将堆的最后一个点赋值到堆顶*/
n--;/*堆元素减少1*/
siftdown(1);/*调整*/
return t;
}
int main()
{
int i,num;
scanf("%d",&num);
for(int i=1; i<=num; i++)
scanf("%d",&h[i]);
n=num;
creat();/*建堆*/
for(int i=1; i<=num; i++) /*删除顶部元素,连续删除n次*/
printf("%d ",deletemax());
return 0;
}
建堆及其堆排序(建立最大堆):
思想:建立最大堆,最大元素为h[1],从小到大排序,最大的放在后面,将h[1]和h[n]交换。
交换后,h[n]就是数组中最大的元素。
h[1]向下调整保持堆的特性。
最大元素已经归位,n–。
直至堆的大小变成1为止。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int h[101];
int n;/*堆的大小*/
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
}
void siftdown(int i)/*传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点向下调整*/
{
int t,flag=0;/*flag用来标记是否需要继续向下调整*/
while(i*2<=n&&flag==0)/*当i结点有儿子(其实是至少有左儿子的情况下)并且有需要继续调整的时候,循环就执行*/
{
if(h[i]<h[i*2])/*首先判断它和左儿子的关系,并用t记录值较大的结点编号*/
t=i*2;
else
t=i;
if(i*2+1<=n)/*右儿子存在,再对右儿子进行讨论*/
{
if(h[t]<h[i*2+1])
t=i*2+1;
}
if(t!=i)/*最小的结点不是自己,说明子结点中有比父结点更小的*/
{
swap(t,i);
i=t;/*更新i为刚才与它交换的儿子结点的编号*/
}
else
flag=1;/*父结点已经比两个子结点都小,不需要再进行调整*/
}
}
void creat()
{
int i;
for(int i=n/2; i>=1; i--) /*从最后一个非叶结点到第1个结点依次进行向上调整*/
siftdown(i);
}
void heapsort()
{
while(n>1)
{
swap(1,n);
n--;
siftdown(1);
}
}
int main()
{
int i,num;
scanf("%d",&num);
for(int i=1; i<=num; i++)
scanf("%d",&h[i]);
n=num;
creat();/*建堆*/
heapsort();
for(int i=1; i<=num; i++) /*删除顶部元素,连续删除n次*/
printf("%d ",h[i]);
return 0;
}