数据结构:堆
(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。除最底层外,该树是完全充满的,而且是从左向右充满。二叉堆可以分两种形式:最大堆和最小堆。这两种堆中,结点的值都要满足堆的性质。最大堆要满足,除了根以外的所有结点都要满足:所有的父结点都大于或等于他的子结点
Arr[parent(i)]>=Arr[i]
也就是说,每个结点的值,最多与其父结点一样大,并且在任一子树中,该子树包含的所有结点的值都不大于该子树根结点的值(如下图所示为一个最大堆)。最小堆的组织方式正好相反:最小堆是所有结点的值都大于或等于他的父结点的值
Arr[parent(i)] <=Arr[i]
在堆排序算法中,我们使用的是最大堆。而最小堆通常用于构造优先队列。
堆的维护
我们先看使用伪代码写的max_heapify()的过程:
max_heapify(Arr[],i)
l=left(i) //2i
r=right(i) //2i+1
if l<=Arr.size and Arr[l]>Arr[i]
largest=l
else largest=i
if r<=Arr.size and Arr[r]>Arr[largest]
largest=r
if largest != i
exchange Arr[i] with Arr[largest]
max_heapify(Arr,largest)
max_heapify()是用于维护堆最大堆的重要过程,他输入一个数组Arr和一个下标:i。我们假定:结点i的所有子结点都满足最大堆的性质,只有i结点不满足最大堆的性质,因为Arr[i]可能小于其孩子节点,这样就违背了最大堆的性质。调用max_heapify()过程后能使结点i在最大堆中逐级下降,从而使以下标i为根结点的子树重新遵守最大堆的性质。
在图示(a)中,Arr[2]违背了最大堆的性质。我们可以执行max_heapify(Arr,2)来恢复堆的性质。在执行程序的每一步中,从Arr[i]、Arr[left(i)]、Arr[right(i)]中选出最大的,并将其下标储存在largest中。如果Arr[i]是最大的,那么以i为根结点的子树已经是最大堆,程序结束。否则,最大元素是i的某个孩子节点,则交换Arr[i]和Arr[largest]的值。在交换之后Arr[i]都会大于它两个孩子结点的值,从而满足了最大堆的性质。在交换之后,下标largest的结点是原来的Arr[largest]的值,于是以该结点为根的子树又有可能会违反最大堆的性质。因此,需要对孩子树递归调用max_heapify();
维护堆的C语言代码:
void max_heapify(int arr[], int i, int size)
{
int largest;
int l = i * 2;
int r = (i * 2) + 1;
if (l<=size && arr[l]>arr[i]){
largest= l;
}
else {
largest= i;
}
if (r<=size && arr[r]>arr[largest]) {
largest= r;
}
if (largest!= i){
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
max_heapify(arr, largest, size);
}
}
建堆
我们可以用自顶向上的方法利用过程max_heapify把一个大小为n=Arr.length的数组Arr[1...n]转换为最大堆。不难证明,Arr[(n/2)+1...n]的元素都是页结点,即他们没有子结点,他们都可以看成只包含一个元素的堆。我们可以利用build_max_heapify过程对树中的其他结点都调用一次max_heapify。伪代码实现过程如下:
build_max_heapify(Arr)
for i=(Arr.size)/2 downto 1
max_heapify(Arr,i)
因此,我们可以在线性时间内,把一个无序素组构造成为一个最大堆。
堆排序算法
讲了那么久,终于到重点了,我要开始装逼了:请大家拿好笔!!!!
上面我们讲到,我们建立了一个大顶堆,大顶堆的的特点就是,数组中最大的元素总是在Arr[1]中。我们可以把Arr[1]和Arr[n]互换,让最大的元素和最后一个元素互换位置,然后我们从堆中拿出最后一个元素(也就是最大的元素)。做法很简单,我们只需要把数组的Arr.size属性减去1即可。在剩余的结点中,只有一个元素不满足最大堆的性质,那就是我们刚刚换上去的Arr[1],这时候我们只需要调用一次max_heapify(Arr,1)维护堆,剩下的结点又编程了一个n-1的大顶堆了。这时我们又可以重新从Arr[1]取出最大值了,然后一直重复这个过程就能得到一个排好序得数组了,是不是感觉很简单?
伪代码实现过程:
heapsort(Arr)
build_max_heap(Arr) //建立一个大顶堆
for i=Arr.length downto 2
exchange Arr[1] with Arr[i]
Arr.length=Arr.length-1
max_heapify(Arr,1)
总体测试代码:
#include <iostream>
using namespace std;
void max_heapify(int arr[], int i, int size)
{
int largest;
int l = i * 2;
int r = (i * 2) + 1;
if (l <= size && arr[l] > arr[i]) {
largest = l;
}
else {
largest = i;
}
if (r <= size && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
max_heapify(arr, largest, size);
}
}
void build_max_heapify(int arr[], int size)
{
for (int i = size / 2; i >= 1; i--) {
max_heapify(arr, i, size);
}
}
void sort_head(int Arr[], int size)
{
build_max_heapify(Arr, size);
int len = size;
int temp;
for (int i = size; i >= 2; i--)
{
temp = Arr[1];
Arr[1] = Arr[i];
Arr[i] = temp;
max_heapify(Arr, 1, --len);
}
}
int main()
{
int Arr[11] = { 0,16,14,10,8,7,9,3,2,4,1 };//0号元素我们是不用得
sort_head(Arr, 10); //堆排序
for (int i = 1; i <= 10; i++) {
cout << i << ":" << Arr[i] << endl;
}
return 0;
}