1: 如图所示,(二叉)堆是一个
数组,它可以被看成一个近似的
完全二叉树
表示堆的数组A有两个属性:
A.length 给出数组元素个数
A.heap-size 表示有多少个堆元素存储在该数组中
也就是说 A[1..length]可能都存放有数据,但只有A[1..A.heap-size]中存放的是堆的有效元素。
2. 树的根节点是A[i],这样给定一个结点的下标i,我们很容易计算得到它的父结点、左孩子、右孩子的下标
3. 二叉堆有两种形式: 最大堆和最小堆
最大堆: 除根结点外,所有的结点 i 都要满足:
A[PARENT[i]] >= A[i]
最小堆: 除根结点外,所有结点 i 都要满足:
A[PARENT(i)] <= A[i]
4. 维护堆的性质( 以最大堆为例)
MAX_HEAPIFY 是用于维护最大堆性质的重要过程。它输入为一个数组A和一个下标i。
5. 建堆
我们用自顶向上的方法利用过程 MAX_HEAPIFY 把一个大小为 A.length 的数组 A[1..n] 装换为最大堆
首先,从完全二叉树的性质可知, 子数组[n/2 + 1, ...n] 都是树的叶结点。每个叶结点可以看成是一个
堆元素,由于它们没有孩子结点,它们自然满足最大堆的性质。
所以,需要我们处理的是 子数组[1...n/2] 这些非叶结点。
算法如下:
6. 堆排序算法
初始时候,堆排序算法利用 BUILD_MAX_HEAP 将输入数组 A[1...n]建成最大堆,其中 n = A.length.
因为数组中的最大元素总在根结点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的
位置。这时候,如果我们从堆中去掉结点n(这操作可以通过减少A.heap-size的值来实现),剩余的
结点中,原来的根的孩子仍然是最大堆,而新结点可能会违背最大堆的性质。为了维护最大堆的性质,
我们要做的是调用 MAX_HEAPIFY(A,1),从而在A[1...n-1] 上构造一个新的最大堆。
堆排序会 不断重复这个过程,直到堆的大小从 n-1 降到2
优先队列
优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中每一个元素都有一个相关的值,称为关键字。
一个最大优先队列支持一下操作:
INSERT(S,x): 把元素X插入集合S中。这一操作等价于S=SU{X}。
MAXIMUM(S) : 返回S中具有最大关键字的元素
EXTRACT_MAX(S): 去掉并返回S中的具有最大关键字的元素。
INCREASE_KEY(S,x,k): 将元素X的关键字值增加到k,这里假设k的值不小于x的原关键字
代码实现(不包括)
表示堆的数组A有两个属性:
A.length 给出数组元素个数
A.heap-size 表示有多少个堆元素存储在该数组中
也就是说 A[1..length]可能都存放有数据,但只有A[1..A.heap-size]中存放的是堆的有效元素。
这里 0 <= A.heap-size <= A.length
2. 树的根节点是A[i],这样给定一个结点的下标i,我们很容易计算得到它的父结点、左孩子、右孩子的下标
PARENT(i)
return i/2;
LEFT(i)
return 2*i;
RIGHT(i)
return 2*i+1;
(可用
宏定义实现)
3. 二叉堆有两种形式: 最大堆和最小堆
最大堆: 除根结点外,所有的结点 i 都要满足:
A[PARENT[i]] >= A[i]
最小堆: 除根结点外,所有结点 i 都要满足:
A[PARENT(i)] <= A[i]
4. 维护堆的性质( 以最大堆为例)
MAX_HEAPIFY 是用于维护最大堆性质的重要过程。它输入为一个数组A和一个下标i。
在调用 MAX_HEAPIFY 的时候,我们假定根结点为 LEFT(i) 和 RIGHT(i) 的二叉树都是最大堆(这个是必须的假设。也就是说,在调整堆的某个结点 i 时,
必须保证 结点 i 的孩子结点都满足最大堆性质)
MAX_HEAPIFY(A,i)
l = LEFT(i); //获得左孩子结点
r = RIGHT(A,i); //获得右孩子结点
if l<=A.heap-size and A[l]>A[i] // 如果做孩子结点是堆元素,并且左孩子结点的关键值大于父结点的关键值
largest = l; //记录结点关键字最大的下标
else largest = i;
if r<=A.heap-size and A[r] > A[largest]
largest = r;
if largest ≠ i
exchange A[i] width A[largest]
MAX_HEAPIFY(A,largest) // 调整堆
5. 建堆
我们用自顶向上的方法利用过程 MAX_HEAPIFY 把一个大小为 A.length 的数组 A[1..n] 装换为最大堆
首先,从完全二叉树的性质可知, 子数组[n/2 + 1, ...n] 都是树的叶结点。每个叶结点可以看成是一个
堆元素,由于它们没有孩子结点,它们自然满足最大堆的性质。
所以,需要我们处理的是 子数组[1...n/2] 这些非叶结点。
算法如下:
BUILD_MAX_HEAP(A)
A.heap-size = A.length
for i = [A.length/2] downto 1
MAX_HEAPIFY(A,i);
6. 堆排序算法
初始时候,堆排序算法利用 BUILD_MAX_HEAP 将输入数组 A[1...n]建成最大堆,其中 n = A.length.
因为数组中的最大元素总在根结点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的
位置。这时候,如果我们从堆中去掉结点n(这操作可以通过减少A.heap-size的值来实现),剩余的
结点中,原来的根的孩子仍然是最大堆,而新结点可能会违背最大堆的性质。为了维护最大堆的性质,
我们要做的是调用 MAX_HEAPIFY(A,1),从而在A[1...n-1] 上构造一个新的最大堆。
堆排序会 不断重复这个过程,直到堆的大小从 n-1 降到2
HEAPSORT(A)
BUILD_MAX_HEAP(A) // 建堆
for i = A.length downto 2
exchange A[1] with A[i]
A.heap-size = A.heap-size - 1;
MAX_HEAPIFY(A,1)
优先队列
优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中每一个元素都有一个相关的值,称为关键字。
一个最大优先队列支持一下操作:
INSERT(S,x): 把元素X插入集合S中。这一操作等价于S=SU{X}。
MAXIMUM(S) : 返回S中具有最大关键字的元素
EXTRACT_MAX(S): 去掉并返回S中的具有最大关键字的元素。
INCREASE_KEY(S,x,k): 将元素X的关键字值增加到k,这里假设k的值不小于x的原关键字
EXTRACT_MAX(S)
if A.heap-size < 1
error "heap underflow"
max = A[1]
A[1] = A[A.heap-size]
A.heap-size = A.heap-size - 1
MAX_HEAPIFY(A,1)
return max
INCREASE_KEY(S,x,k)
if key < A[i]
error "new key is smaller than current key"
A[i] = key
while i > 1 and A[PARENT(i)] < A[i]
exchange A[i] with A[PARENT(i)]
i = PARENT(i);
INSERT(S,x)
A.heap-size = A.heap-size + 1;
A[A.heap-size] = -无穷
INCREASE_KEY(S,A.heap-size,x)
代码实现(不包括)
#include "iostream"
#define PARENT(i) (i/2)
#define LEFT(i) (2*i)
#define RIGHT(i) (2*i+1)
#define Heapswap(a,b) (a = a+b, b=a-b, a= a-b)
struct Heap {
int A[256] = {0}; // 0位不要,下标从1开始
unsigned length = 0; // 数组有效位数
unsigned heapSize = 0; // 堆元素
unsigned const MaxSize = 256; //数组容量
};
void InitialHeap(Heap& hp, int A[], int Arrlength, int HeapLength);//初始化堆
void Print(Heap hp); // 打印 堆元素
void MaxHeapify( Heap &hp, int i); // 维护堆的性质
void BuildMaxHeap(Heap &hp); // 建堆
void HeapSort(Heap &hp); // 堆排序算法
int main(void)
{
#if 1 //测试
Heap hp;
int A[] = {9,10,18,1,12,6,9,11,13,2};
//初始化
InitialHeap(hp, A, 10, 0);
//建堆
BuildMaxHeap(hp);
//输出数据
Print(hp);
std::cout << std::endl;
//堆排序
HeapSort(hp);
for (size_t i = 1; i <= hp.length; i++)
{
std::cout << hp.A[i] << " ";
}
std::cout << std::endl;
system("pause");
#endif
return 0;
}
void InitialHeap(Heap & hp, int A[], int Arrlength, int HeapLength)
{
for (int i = 0; i < Arrlength; i++)
hp.A[i+1] = A[i];
hp.length = Arrlength;
hp.heapSize = HeapLength;
}
void Print(Heap hp)
{
for (size_t i = 1; i <= hp.heapSize; i++)
{
std::cout << hp.A[i] << " ";
}
}
void MaxHeapify(Heap &hp, int i)
{
int left = LEFT(i);
int right = RIGHT(i);
int largest = -1;
// 是堆元素并且是孩子结点大于父子结点
if (left <= hp.heapSize && hp.A[left] > hp.A[i])
largest = left;
else
largest = i;
if (right <= hp.heapSize && hp.A[right] > hp.A[largest])
largest = right;
if (largest != i)
{
Heapswap(hp.A[largest], hp.A[i]);
MaxHeapify(hp, largest);
}
}
void BuildMaxHeap(Heap &hp)
{
hp.heapSize = hp.length;
for (int i = hp.length / 2; i >= 1; i--)
MaxHeapify(hp, i);
}
void HeapSort(Heap & hp)
{
BuildMaxHeap(hp); //建堆
for (int i = hp.length; i >= 2; i--)
{
Heapswap(hp.A[1], hp.A[i]);
hp.heapSize = hp.heapSize - 1;
MaxHeapify(hp, 1);
}
}