堆排序算法
可以将堆作为一种数据结构,利用这个数据结构的特点实现排序
堆的性质(最大堆)
- 堆的结构与二叉树类似,可以用数组或者Vector实现其结构
- 最大堆的根元素比左右儿子都大,且任意子堆都满足该性质
- 左右儿子的查找同二叉树,假设以数组的第一个数
arr[0]
作为根结点,
arr[i]
的父结点和左右儿子的位置:
parent(i)=(i−1)/2left(i)=2i+1right(i)=2i+2
算法描述
- 堆排序思想略微复杂。
假设堆长度为 n ,按照最大堆的性质,根结点比左右儿子都大,则堆建成之后即可得到堆内最大元素。将最大元素与堆末尾元素交换,对前n−1 个元素再一次建堆即可得到次大元素…依次递推可实现排序。
建堆过程是本算法的难点。 - 保持堆性质。
交换最大元素之后,唯有新堆根元素可能不满足最大堆条件。保持函数只是对根和左右儿子操作:根结点若不满足要求,与其左右儿子较大者互换。交换之后的新位置上可能又不满足最大堆条件,则递归调用自己继续与下一层左右儿子互换。直到完成新堆的维护。 - 建堆。
先对部分规则的子堆进行操作保持子堆性质,接下来才进一步看如何从乱序列实现建堆。从最后一个内结点到根结点自底向上逐一调用保持函数即可:先维护子堆,进一步维护上一层堆,直到完成建堆。 - 排序。
需要执行的操作为:每次换出最大的元素,换入原堆末尾元素;并且指定新堆的起始和终止,注意堆大小会逐次减1。
代码实现
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void maxheapify(vector<double> & A, int i, int heapsize)
{
int lar;
int l = i*2 + 1;
int r = i*2 + 2;
if ((l < heapsize) && (A.at(l) > A.at(i)))
{
lar = l;
}
else
{
lar = i;
}
if ((r < heapsize) && A.at(r) > A.at(lar))
{
lar = r;
}
cout<<i<<" "<<lar<<endl;
if (lar != i)
{
double temp = A.at(lar);
A.at(lar) = A.at(i);
A.at(i) = temp;
maxheapify(A, lar, heapsize);
}
}
void buildmax(vector<double> & A)
{
int heapsize = A.size()-1;
for (int i=heapsize/2-1; i>=0; --i)
{
maxheapify(A, i, heapsize);
}
}
void heapsort(vector<double> & A)
{
buildmax(A);
int heapsize = A.size()-1;
if (A.size()>1)
{
for (int i=A.size()-1; i>=1; --i)
{
double temp = A.at(0);
A.at(0) = A.at(i);
A.at(i) = temp;
--heapsize;
maxheapify(A, 0, heapsize);
}
}
else
{
cout<< A.at(0)<<endl;
}
}
int main()
{
double arr[] = {0.1, 24, 1.8, 3.5, 10, 14, 8, 7, 1, 9, 2, 4, 3, 6};
int count = (int)(sizeof(arr) / sizeof(arr[0]));
vector<double> A(arr, arr+count);
for (int i=0; i<A.size(); ++i)
{
cout<<A[i]<<" __ ";
}
cout<<endl;
int p = 0;
//int r=A.size()-1;
heapsort(A);
for (int i=0; i<A.size(); ++i)
{
cout<<A[i]<<" ";
}
cout<<endl;
return 0;
}
复杂度分析
- 保持函数维护堆所需时间复杂度为 Θ(lg(n))
- 从无序建堆的过程,随着所处里的堆高度
h
增加,每层复杂度也不一样,总复杂度为
∑h=0⌊lg(n)⌋⌈n2h+1⌉O(h)=O(n) - 堆排序算法总体的复杂度 O(nlg(n))