您的位置:首页 > 其它

堆及堆排序算法(算法导论)

2018-02-25 16:47 931 查看
堆是一种二叉树,分为最大堆和最小堆。

最大堆:所有的父亲节点都大于等于其儿子节点,所以根节点最大;

最小堆:所有的父亲节点都小于等于其儿子节点,所以根节点最小;

在这里仅仅考虑最大堆。

如图是一个最大堆:



绿色数字为角标 i,而圆圈里的数字为A[ i ]的值。

堆的几个性质:

A[ i ]的父亲节点是A[i/2],左儿子节点是A[2 * i],右儿子节点是A[2 *i+1];

除了最底层外,该树是全满的;

堆的建立与排序均为原址排序,不占用多余空间;

堆的排序复杂度为:O(nlgn),也就是说,堆排序同时具有插入排序和归并排序的优点而没有它们的缺点。

堆的修正

自上而下,如果有儿子节点比父节点大,将最大的儿子节点与父节点互换,并更新父节点重复这个过程直到没有比父节点大的儿子节点。



建立堆

由于修正的过程是自上而下,所以堆的建立,也就是堆全树的修正,需要自下而上,才能保证一次到位。也只有自下而上的修正,才能使堆成为一棵全满的树。



而这个起点“下”,正好是(int)(n/2),不论n是奇数还是偶数。

自下而上能够保证每一次修正之后该点之下的结构都符合最大堆的性质。

堆排序

由于最大堆只能确定最大值(根节点),因此我们把根节点提出来,对其余的点再做一次一次修正,就得到了第二大的值(新最大堆的根节点),以此反复,最终完成全部排序。





完成这个排序的关键是约束Heapsize的大小,从N到2(当最后只剩下一个数时,它必定是最小数).

堆排序的代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;

#define Left(x) 2*x
#define Right(x) 2*x+1

int N,Heap_size;

void EXCHANGE(int*Heap,int x,int y)
{
int key=Heap[y];
Heap[y]=Heap[x];
Heap[x]=key;
}

void MAX_HEAPIFY(int*Heap,int x)//修正函数,将角标为x的那个节点移动到合适的位置
{
int L=Left(x),R=Right(x),LGst=x;
if(L<=Heap_size && Heap[L]>Heap[x]) LGst=L;
if(R<=Heap_size && Heap[R]>Heap[LGst]) LGst=R;
if(LGst!=x && LGst<=Heap_size)
{
EXCHANGE(Heap,x,LGst);
MAX_HEAPIFY(Heap,LGst);
}
}

void BUILD_MAX_Heap(int*Heap)//建立堆的函数,即对所有小于N/2的节点依此做一次修正
{
for(int i=(N/2);i>=1;i--)
MAX_HEAPIFY(Heap,i);
}

void Heap_Sort(int*Heap)//排序,关键是控制好新堆的大小Heap_size从N到2
{
Heap_size=N;
for(int i=Heap_size;i>=2;i--)
{
EXCHANGE(Heap,1,i);
Heap_size--;
MAX_HEAPIFY(Heap,1);
}
}

void OUTPUT
4000
(int*Heap)
{
for(int i=1;i<=N;i++)
printf("%d  ",Heap[i]);
printf("\n");
}

int main()
{
while(scanf("%d",&N)!=EOF && N)
{
int*Heap=(int*)malloc(sizeof(int)*(2*N+3));//申请两倍多的空间是为了防止在访问儿子节点时越界
fill(Heap,Heap+(2*N+3),0);
for(int i=1;i<=N;i++)
scanf("%d",&Heap[i]);
Heap_size=N;
BUILD_MAX_Heap(Heap);
OUTPUT(Heap);
Heap_Sort(Heap);
OUTPUT(Heap);
}
return 0;
}


有什么建议或疑问欢迎留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: