堆排序的基本原理我就不讲了,网上有很多,大家可以移步这个链接学习,值得注意的是:
堆排序的总体复杂度为O(nlogn),初始建堆的复杂度为O(n),每次重建堆的时间复杂度为O(logn);
堆排序不适合排序数目较少的情况,且是不稳定排序;
堆排序是建立在大顶堆和小顶堆基础之上的,而大顶堆和小顶堆是建立在完全二叉树之上的(虽然不需要建树,但隐式建树)。
好了,直接上代码(以下为大顶堆排序):
#include<iostream>
#include<vector>
using namespace std;
//交换
void swap(vector<int> &array, int index1, int index2)
{
if(index1 == index2)
return;
array[index1] = array[index1] ^ array[index2];
array[index2] = array[index1] ^ array[index2];
array[index1] = array[index1] ^ array[index2];
}
//递归形式调整堆
void maxheap_adjust(vector<int> &array, int currentNode, int size)
{
if(currentNode >= size) //根节点定义序号为0,故退出条件是大于等于size
return;
int maxindex = 2 * currentNode + 1; //设置当前最大值为当前节点的左孩子
//如果右节点存在(右孩子存在则根据完全二叉树性质,左孩子必然存在,反之不成立,故这里的‘&&’前后顺序不能对调),且为最大值,则更新
if(maxindex + 1 < size && array[maxindex + 1] > array[maxindex])
++maxindex;
if(maxindex < size && array[currentNode] < array[maxindex])
{
swap(array, currentNode, maxindex);
maxheap_adjust(array, maxindex, size); //递归
}
}
void build_maxheap(vector<int> &array)
{
//从第一个不是叶子节点的节点开始(叶子节点不需要考虑,可以减少循环次数)
for(int i = array.size() / 2 - 1; i >= 0; --i)
maxheap_adjust(array, i, array.size());
}
void printarray(vector<int> array, int i)
{
if(i == 0)
cout << "原数组为:";
else
cout << "第" << i << "遍调整后结果:";
for(auto it : array)
cout << it << ' ';
cout << endl;
}
//k可以用于指定找到给定数组中的前k大(当k == array.size()时,就是从小到大排序)
void maxheap_sort(vector<int> &array, int k)
{
//先建大顶堆
build_maxheap(array);
//再将大顶堆首位(也就是当前数组中的最大值)与数组尾部替换,并调整大顶堆获得次大值
int size = array.size();
for(int i = k; i > 0; --i)
{
swap(array, 0, size - 1);
printarray(array, k - i + 1);
maxheap_adjust(array, 0, --size); //注意设定根节点序号为0,故从0开始调整
}
}
int main()
{
int n; //数组中元素个数
while(cin >> n) //循环输入每一组数组,同时按下control + C键退出
{
vector<int> input;
int temp;
for(int i = 0; i < n; ++i)
{
cin >> temp; //接收键盘输入
input.push_back(temp);
}
printarray(input, 0);
maxheap_sort(input, input.size());
}
return 0;
}
输入输出如下(只测试了几个例子,要是有错,欢迎大家指出哈):
除了递归形式来调整堆外,还可以采用循环形式实现,如下(只是修改了maxheap_adjust函数):
#include<iostream>
#include<vector>
using namespace std;
//交换
void swap(vector<int> &array, int index1, int index2)
{
if(index1 == index2)
return;
array[index1] = array[index1] ^ array[index2];
array[index2] = array[index1] ^ array[index2];
array[index1] = array[index1] ^ array[index2];
}
//循环形式调整堆
void maxheap_adjust(vector<int> &array, int currentNode, int size)
{
if(currentNode >= size) //根节点定义序号为0,故退出条件是大于等于size
return;
while(2 * currentNode + 1 < size)
{
int maxindex = 2 * currentNode + 1; //设置当前最大值为当前节点的左孩子
//如果右节点存在(右孩子存在则根据完全二叉树性质,左孩子必然存在,反之不成立,故这里的‘&&’前后顺序不能对调),且为最大值,则更新
if(maxindex + 1 < size && array[maxindex + 1] > array[maxindex])
++maxindex;
if(array[currentNode] > array[maxindex]) //不需要再后续调整了
break;
swap(array, currentNode, maxindex);
currentNode = maxindex;
}
}
void build_maxheap(vector<int> &array)
{
for(int i = array.size() / 2 - 1; i >= 0; --i)
maxheap_adjust(array, i, array.size());
}
void printarray(vector<int> array, int i)
{
if(i == 0)
cout << "原数组为:";
else
cout << "第" << i << "遍调整后结果:";
for(auto it : array)
cout << it << ' ';
cout << endl;
}
//k可以用于指定找到给定数组中的前k大(当k == array.size()时,就是从小到大排序)
void maxheap_sort(vector<int> &array, int k)
{
//先建大顶堆
build_maxheap(array);
//再将大顶堆首位(也就是当前数组中的最大值)与数组尾部替换,并调整大顶堆获得次大值
int size = array.size();
for(int i = k; i > 0; --i)
{
swap(array, 0, size - 1);
printarray(array, k - i + 1);
maxheap_adjust(array, 0, --size); //注意设定根节点序号为0,故从0开始调整
}
}
int main()
{
int n; //数组中元素个数
while(cin >> n) //循环输入每一组数组,同时按下control + C键退出
{
vector<int> input;
int temp;
for(int i = 0; i < n; ++i)
{
cin >> temp; //接收键盘输入
input.push_back(temp);
}
printarray(input, 0);
maxheap_sort(input, input.size());
}
return 0;
}
当然对于小顶堆,只需要改几处大于小于符号即可,如下:
递归形式
//递归形式调整堆
void minheap_adjust(vector<int> &array, int currentNode, int size)
{
if(currentNode >= size) //根节点定义序号为0,故退出条件是大于等于size
return;
int minindex = 2 * currentNode + 1; //设置当前最大值为当前节点的左孩子
//如果右节点存在(右孩子存在则根据完全二叉树性质,左孩子必然存在,反之不成立,故这里的‘&&’前后顺序不能对调),且为最大值,则更新
if(minindex + 1 < size && array[minindex + 1] < array[minindex])
++maxindex;
if(minindex < size && array[currentNode] > array[minindex])
{
swap(array, currentNode, minindex);
minheap_adjust(array, minindex, size); //递归
}
}
循环形式
//循环形式调整小顶堆
void minheap_adjust(vector<int> &array, int currentNode, int size)
{
if(currentNode >= size) //根节点定义序号为0,故退出条件是大于等于size
return;
while(2 * currentNode + 1 < size)
{
int minindex = 2 * currentNode + 1; //设置当前最小值为当前节点的左孩子
//如果右节点存在(右孩子存在则根据完全二叉树性质,左孩子必然存在,反之不成立,故这里的‘&&’前后顺序不能对调),且为最大值,则更新
if(minindex + 1 < size && array[minindex + 1] < array[minindex])
++minindex;
if(array[currentNode] < array[minindex]) //不需要再后续调整了
break;
swap(array, currentNode, minindex);
currentNode = minindex;
}
}
还有要注意的是,大顶堆实际上用于从小到大排序,或者用于获取前K小;而小顶堆用于从大到小排序,或者用于获取前K大。