堆排序概念补充
1.堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序, 它的最坏、最好、平均时间复杂度均为 O(nlogn), 它也是不稳定排序。
2.堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值, 这种情况称为大顶堆,注意:没有要求结点的左孩子的值和右孩子的值的大小关系。
3.每个结点的值都小于或等于其左右孩子结点的值, 这种情况称为小顶堆。
大顶堆举例说明:
小顶堆举例说明:
本题可以使用堆排序,构造小顶堆,然后输出堆顶,输出后把堆顶和堆尾交换。重复执行m次即可。
如何手写一个堆?完全二叉树 5个操作
1. 插入一个数 heap[ ++ size] = x; up(size);
2. 求集合中的最小值 heap[1]
3. 删除最小值 heap[1] = heap[size]; size -- ;down(1);
4. 删除任意一个元素 heap[k] = heap[size]; size -- ;up(k); down(k);
5. 修改任意一个元素 heap[k] = x; up(k); down(k);
分析:i为什么从n/2开始down?
首先要明确要进行down操作时必须满足左儿子和右儿子已经是个堆。
开始创建堆的时候,元素是随机插入的,所以不能从根节点开始down,而是要找到满足下面三个性质的结点:
1.左右儿子满足堆的性质。
2.下标最大(因为要往上遍历)
3.不是叶结点(叶节点一定满足堆的性质)
那这个点为什么是n/2?看图。
完整代码:
#include<iostream>
using namespace std;
const int N=100010;
int h[N],siz;
//h[i] 表示第i个结点存储的值,i从1开始,2*i是左子节点,2*i + 1是右子节点
//size 既表示堆里存储的元素个数,又表示最后一个结点的下标
void down(int u){
int t=u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
if(u*2<=siz&&h[u*2]<h[t]) t=u*2;// 左子节点存在并且小于当前结点,更新t的下标
if(u*2+1<=siz&&h[u*2+1]<h[t]) t=u*2+1;//右子节点存在并且小于当前结点,更新t的下标
if(t!=u){//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
swap(h[t],h[u]);
down(t);//交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>h[i];
siz=n;//初始化size,表示堆里有n个元素
for(int i=n/2;i>0;i--) down(i);//把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉
while(m--){
cout<<h[1]<<" ";
h[1]=h[siz];
siz--;
down(1);
}
return 0;
}