堆的定义(以大顶堆为例)
一棵完全二叉树,父节点的值大于或等于左右子节点。
约定:
堆用数组存储,数组下标从1开始,从上到下、从左到右标记完全二叉树的节点。
堆下标的性质:
设父节点下标为pos,左子节点下标为lson,右子节点下标为rson。
pos=lson/2 , pos=rson/2 (向下取整的整数除法)
lson=pos*2 , rson=pos*2+1
堆排序的思路
1.将待排序序列输入到数组a中
2.建立初始大顶堆
3.设pos为最后一个节点的下标,将pos和下标1的节点元素互换(swap),达到将堆顶最大元素置于pos节点的目的,然后维护1 ~ pos-1节点所构成的堆,pos执行到第二个节点(第一个节点已经排序好了,没必要再来一次)。
维护堆
void heapify(int pos,int range) //维护堆,pos为起始节点下标,range控制维护范围
{
int max=pos;
int lson=2*pos;
int rson=2*pos+1;
if(lson<=range and a[max]<a[lson]) max=lson; //与左子节点的元素作比较
if(rson<=range and a[max]<a[rson]) max=rson; //与右子节点的元素作比较
if(max!=pos) //判断是否需要元素交换
{
swap(a[max],a[pos]);
heapify(max,range); //若发生了父节点与子节点的交换,则递归维护其子树
}
}
建立大顶堆
将pos从最后的节点下标向前移至第一个节点下标,每个pos都进行一次“维护堆”(heapify()),这一步可以优化,pos初始值改为最后一个拥有子节点的节点位置,即 pos=n/2。
for(int i=n/2;i>=1;i--) heapify(i,n);
总过程
结合“思路”,得出以下函数
void heap_sort()
{
for(int i=n/2;i>=1;i--) heapify(i,n);
for(int i=n;i>=2;i--)
{
swap(a[i],a[1]);
heapify(1,i-1);
}
}
例题
给定一个整数序列,请按非递减序输出采用堆排序的各趟排序后的结果。
输入格式:
测试数据有多组,处理到文件尾。每组测试数据第一行输入一个整数n(1≤n≤100),第二行输入n个整数。
输出格式:
对于每组测试,输出若干行,每行是一趟排序后的结果,每行的每两个数据之间留一个空格。
输入样例:
4
8 7 2 1
输出样例:
7 1 2 8
2 1 7 8
1 2 7 8
代码实现
#include<bits/stdc++.h>
using namespace std;
int n;
int a[101];
void output()
{
for(int i=1;i<=n;i++)
{
if(i!=1) cout<<" ";
cout<<a[i];
}
cout<<endl;
}
void heapify(int pos,int range) //维护堆
{
int max=pos;
int lson=2*pos;
int rson=2*pos+1;
if(lson<=range and a[max]<a[lson]) max=lson; //与左字节点的元素作比较
if(rson<=range and a[max]<a[rson]) max=rson; //与又字节点的元素作比较
if(max!=pos) //判断是否需要元素交换
{
swap(a[max],a[pos]);
heapify(max,range); //若发生了父节点与子节点的交换,则递归维护其子树
}
}
void heap_sort()
{
for(int i=n/2;i>=1;i--) heapify(i,n);
for(int i=n;i>=2;i--)
{
swap(a[i],a[1]);
heapify(1,i-1);
output();
}
}
int main()
{
while(cin>>n)
{
for(int i=1;i<=n;i++) cin>>a[i];
heap_sort();
}
return 0;
}
代码时间复杂度
非递增规则排序
思路一致,创建小顶堆,注意判断元素大小时符号也要跟着改变。