(二叉)堆是一个数组,可以被看成一个近似的完全二叉树。加入数的根节点是i(从1开始计算),那么它的父节点、左孩子和右孩子的下标则为i/2,2i,2i+1。需要注意,在编程语言中,一般是从0开始计算,因此此时某个根节点的父节点、左孩子和右孩子则为(i-1)/2,2i+1,2i+2。
二叉堆可以分为最大堆和最小堆。最大堆是指除了根以外的所有节点均小于根节点,即最上层的节点是最大值,根节点一定大于其子节点。最小堆则反之。
上述二者有着不同的用途:在堆排序算法中用最大堆;在构造优先队列中使用最小堆。本文介绍的是使用最大堆实现堆排序算法。
堆排序算法集合了先前所介绍的归并排序与插入排序二者的优点。一方面,堆排序的时间复杂度与归并排序相同,为O(nlogn);另一方面,堆排序与插入排序一样具有空间原址性,即任何时候都只需要常数个额外元素空间存储临时的数据。
在最大堆排序中,包含以下三种函数:
1、max_heapify(A,i):维护堆,维护最大堆的性质。
思路:从一个给定的下标i开始,让i节点符合最大堆的性质(在根节点和子结点中找出更大的值),逐级往下,直到子节点达到(或超过)堆元素的上限时停止。
2、build_max_heap(A):建堆,自底向上构建一个堆。
思路:一般从(n-1)/2(数组从0开始计数,此时该节点是从下往上的第一个根节点,往后全都是叶节点)开始到0,每次都执行一次维护堆函数。
3、heapsort(A):堆排序算法。
思路:完成建堆后,从最右下角的叶节点开始,与0号根节点交换位置。此时最右下角的元素是最大的元素,将其删除(pop),对0号根节点使用维护堆函数,新的0号根节点又是最大的数。一直循环直到只剩一个元素为止,此时将删除的元素排列起来,即为从大到小的排序。
实现代码如下:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//维护堆:维护最大堆的性质
void max_heapify(vector<int> &A,int i) {
int l = 2 * (i+1)-1;
int r = 2 * (i+1);
int largest;
if (l < A.size() && A[l] > A[i])
{
largest = l;
}
else largest = i;
if (r < A.size() && A[r] > A[largest])
{
largest = r;
}
if (largest != i)
{
int temp;
temp = A[i];
A[i] = A[largest];
A[largest] = temp;
max_heapify(A, largest);
}
}
//建堆,从下向上第一个非叶节点开始,对堆进行进行维护
void build_max_heap(vector<int>& A)
{
int heap_size = A.size();
for (int i = (heap_size-1) / 2; i >= 0; --i)
{
max_heapify(A, i);
}
}
//堆排序算法
void heapsort(vector<int>& A, vector<int>& B)
{
build_max_heap(A);
int heapsize = A.size();
for (int i = heapsize - 1; i >= 1; --i)
{
int temp;
temp = A[0];
A[0] = A[i];
A[i] = temp;
B.push_back(A[i]);
A.pop_back();
max_heapify(A, 0);
}
B.push_back(A[0]);
}
int main()
{
int num;
cout << "输入子数组的元素数目" << endl;
cin >> num;
vector<int> vector_test(num);
vector<int> answer;
cout << "依次输入子数组的各元素" << endl;
for (int i = 0; i < num; ++i)
{
cin >> vector_test[i];
}
cout << "以下是堆排序算法后的结果" << endl;
heapsort(vector_test,answer);
for (int i = 0; i < answer.size(); ++i) { cout << answer[i] << endl; }
return 0;
}
注意事项:
1、维护堆的if语句中要先判断l或者r是否超出A的范围,再进行两元素的大小比较,否则会因为内存溢出的现象,导致不存在A[l/r]而报错!!