堆是一种灵巧的、部分有序的数据结构,它尤其适合用来实现优先队列。
优先队列是元素的一个集合,其中每个元素都包含一个被称为元素优先级的可排序属性。优先队列支持下面的操作:
- 找出一个具有最高优先级的元素(即最大元素);
- 删除一个具有最高优先级的元素;
- 添加一个元素到集合中。
通过采用堆这种数据结构可以高效实现这些操作。
下文分两部分:第一部分介绍堆;第二部分讲解堆排序。
【第一部分:堆的基本概念】
堆的定义
堆可以定义为一棵二叉树,树的节点中包含键(每个节点一个键),并且满足下面两个条件:
- 树的形状要求--这棵二叉树是基本完备的(或者简称完全二叉树),这意味着,树的每一层都是满的,除了最后一层最右边的元素有可能缺位。
- 父母优势要求--每一个节点的键都要大于或等于它子女的键(对于任何叶子我们认为这个条件都是自动满足的)。(最大堆)
在堆中,键值是从上到下排序的。在任何从根到某个叶子的路径上,键值的序列式递减的。键值之间不存在从左到右的次序。在树的同一层的节点之间,不存在任何关系。在同一节点的左右子树之间也没有任何关系。
堆的重要特征:
- 只存在一棵n个节点的完全二叉树。它的高度等于log2n。
- 堆的根总是包含了堆的最大元素。
- 堆的一个节点以及该节点的子孙也是一个堆。
- 可以用数组来实现堆,方法是用从上到下、从左到右的方式来记录堆的元素。为了方便起见,可以在这种数组从1到n的位置上存放堆元素,留下H[0](可以在其中放一个限位器)。在这种表示法中:
b. 在数组中,对于一个位于父母位置i(1<=i<=[n / 2])的键来说,它的子女将会位于2i和2i+1,相应地,对于一个位于i(2<=i<=n)的键来说,它的父母将会位于[i/2]。
我们可以将堆定义为一个数组H[1…n],其中,数组前半部分中每个位置i上的元素,总是大于等于位置2i和2i+1中的元素。
对于i=1,…,[n / 2], H[i]>=max{H[2i], H[2i+1]}
自底向上堆构造算法
从最后的父母节点开始,到根为止,检查这些节点的键是否满足父母优势要求。如果该节点不满足,则把节点的键K和它子女的最大键进行交换,然后再检查在新位置上,K是不是满足父母优势要求。这个过程一直持续到,对K的父母优势要求满足为止。对于以当前父母节点为根的子树,在完成了它“堆化”以后,该算法对于该几点的直接前趋进行同样的操作,直到对树的根完成了这种操作以后,该算法就停止。
算法 HeapBottomUp(H[1..n])
//用自底向上算法,从给定数组的元素中构建一个堆
//输入:一个可排序元素的数组H[1..n]
//输出:一个堆H[1..n]
for i <- [n / 2] downto 1 do
k <- i; v <- H[k]
heap <- false
while not heap and 2 * k <= n do
j <- 2 * k
if j < n
if H[j] < H[j+1] j <- j + 1
if v >= H[j]
heap <- ture
else H[k] <- H[j]; k <- j
H[k] <- v
从堆中删除最大的键
第一步:根的键和堆的最后一个键K做交换。
第二步:堆的规模减一。
第三步:严格按照自底向上堆构造算法把K沿着树向下删选,来对这棵较小的树进行“堆化”。验证K是否满足父母优势:如果它满足,操作完成;如果不满足,K和它较大的子女做交换;然后重复这个操作,直到K在新位置中满足了父母优势要求为止。
【第二部分:堆排序】
代码实现
#include <stdio.h>
#include <stdlib.h>
//数组从1到n的位置上存放堆元素,a[0]不参与构造堆。
//可以将a[0]空着,也可以在其中放一个限位器。
/**********自底向上构造最大堆**************/
void HeapBottomUp(int a[], int n)
{
int i, j, k;
int tmp;
bool heap;
for(i = n / 2; i > 0; i--)
{
tmp = a[i];
k = i; //父结点
heap = false;
while(!heap && 2 * k <= n)
{
j = 2 * k;
if(j < n)
if(a[j] < a[j + 1])
j = j + 1;
if(tmp >= a[j])
heap = true;
else
{
a[k] = a[j]; //较小父结点往下移
k = j;
}
}
a[k] = tmp;
}
}
/**********自底向上调整最大堆**************/
void FixHeapBottomUp(int a[], int n)
{
int i, j;
int tmp;
bool flag;
tmp = a[n];
i = n;
j = i / 2;
flag = true;
while(flag && j > 0)
{
if(a[j] >= tmp)
flag = false;
else
{
a[i] = a[j]; //较小父结点往下移
i = j;
j = i / 2;
}
}
a[i] = tmp;
}
/**********自顶向下调整最大堆**************/
void FixHeapUpBottom(int a[], int n)
{
int tmp;
int i, j;
int flag;
tmp = a[1];
flag = true;
i = 1;
j = 2 * i;
while(flag && j <= n)
{
if(j + 1 <= n && a[j + 1] > a[j] )
j = j + 1;
if(a[j] <= tmp)
flag = false;
else
{
a[i] = a[j]; //较大子结点往上移
i = j;
j = 2 * i;
}
}
a[i] = tmp;
}
/**********插入一个数**************/
void InsertNumber(int a[], int n, int num)
{
int i, j;
a[n] = num;
FixHeapBottomUp(a, n);
}
/**********删除最大键**************/
int DeleteNumber(int a[], int n)
{
int tmp;
tmp = a[1];
a[1] = a[n];
a[n] = tmp;
FixHeapUpBottom(a, n - 1);
return tmp;
}
/**********堆排序**************/
void HeapSort(int a[], int n)
{
int i;
HeapBottomUp(a, n);
for(i = n; i > 1; i--)
DeleteNumber(a, i);
}
int main(int argc, const char *argv[])
{
int a[7] = {0, 2, 9, 7, 6, 5, 8 };
int n = sizeof(a) / sizeof(int);
HeapSort(a, n - 1);
for(int i = 1; i < 7; i ++)
printf("%d ", a[i]);
return 0;
}