堆排序
堆的介绍
堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素。同时,除了最底层外,该树是完全充满的,而且是从左向右填充
最大堆与最小堆
二叉堆分为两种,即最大堆与最小堆。在最大堆中,最大堆性质是指除了根以外的所有结点i都要满足:A[PARENT(i)] >= A[i],即除根结点外的每个结点都大于或等于其子结点,下图则为一个最大堆。(图源自《算法导论》)
而最小堆则与之相反。
最大堆的维护
维护最大堆,即判断某个结点是否符合最大堆的性质,如若不符合,通过将其与子结点交换生成的新堆符合性质。同时,因为为除根结点外的结点,故不需对根结点进行维护,下面给出代码。
void max_heapify(int a[], int i, int n){ //n为结点数
int l = 2 * i, r = 2 * i + 1, largest; // l,r分别为左孩子和右孩子
if(l <= n && a[i] < a[l])
largest = l;
else
largest = i;
if(r <= n && a[largest] < a[r])
largest = r;
if(largest != i){
swap(a, i, largest); //交换值以维护
max_heapify(a, largest, n);
}
}
建堆
建堆过程,就是对一个不符合最大堆(最小堆)的堆进行堆维护的过程,同时,对于根结点无需进行维护,故给出如下代码。
void build_max_heap(int a[], int n){
for(int i = n / 2; i >= 1; i--)
max_heapify(a, i, n);
}
堆排序
在建完堆后,就可进行堆排序的过程了。堆排序的主要原理:由最大堆的性质,根结点(即第一个结点)的值必为最大,所以可将其排至末尾,即将其与末尾结点的元素进行交换,同时将堆的总元素减一,这时再对堆首元素进行一轮维护,之后反复执行此过程,可实现堆排序。具体图示过程如下(图源自《算法导论》)。
接下来给出总代码。
#include<bits/stdc++.h>
#include<stdio.h>
#define maxn 1005000
using namespace std;
void swap(int a[], int i, int j){
if(i != j){
int cmp = a[i];
a[i] = a[j];
a[j] = cmp;
}
}
void max_heapify(int a[], int i, int n){ //n为结点数
int l = 2 * i, r = 2 * i + 1, largest; // l,r分别为左孩子和右孩子
if(l <= n && a[i] < a[l])
largest = l;
else
largest = i;
if(r <= n && a[largest] < a[r])
largest = r;
if(largest != i){
swap(a, i, largest); //交换值以维护
max_heapify(a, largest, n);
}
}
void build_max_heap(int a[], int n){
for(int i = n / 2; i >= 1; i--)
max_heapify(a, i, n);
}
void heapsort(int a[], int n){ //堆排序
build_max_heap(a, n);
for(int i = n; i >= 2; i--){
swap(a, 1, i);
//cout << a[i] << endl;
n -= 1;
max_heapify(a, 1, n);
}
}
int main()
{
int b[11] = {0, 1, 14, 16, 9, 10, 2, 3, 8, 4, 7};
heapsort(b, 10);
for(int i = 1; i <= 10; i++)
cout << b[i] << " ";
return 0;
}