二叉堆(堆)
- 堆是一颗完全二叉树:除了底层每个节点都有两个孩子,底层节点从左到右依次填入(不能有间隔)。
- 一颗高为 h h 的完全二叉树有个节点; N N 的节点的完全二叉树的高度为。
- 堆可以用数组实现:如果数组下标从1开始,每个位置 i i 的左孩子下标为,右孩子下标为 2i+1 2 i + 1 ,父亲下标为 ⌊i/2⌋ ⌊ i / 2 ⌋ 。
- 堆中每个节点的孩子都大于它自身,称为小堆。堆中每个节点的孩子都小于它自身,称为大堆。
堆的数据结构(数组实现)
- 基本数据结构:一个存放元素的数组,堆的最大容量,当前堆的大小
typedef struct HeapStruct { // 定义堆的结构(基于数组)
int Capacity; // 堆的最大容量
int Size; // 堆中元素个数
ElemType* arr; // 堆中包含数据元素的数组的头指针
}Heap, *BinHeap;
堆的插入操作(以小堆为例)
- 向堆中插入元素X,首先在下一个空闲位置创建一个“空穴”(满足完全二叉树)。
- 如果X放入该“空穴”不破坏堆序性(X大于“空穴”的父节点),则插入完成。否则把“空穴”的父节点上的元素移到该空穴中。
- 继续该操作,直到“空穴”的父节点小于X(以小堆为例),把X放入空穴。插入完成。
- 这种策略被称为上滤。
void insert(BinHeap H, ElemType x)
{
if (isFull(H)) // 判断队列是否为满
{
cout << "The heap is full" << endl;
exit(1);
}
int i = ++H->Size; // 将队列的大小增1,赋值给i表示要插入的空穴
while (x < H->arr[i / 2]) // 比较插入位置的父节点的值和要插入的元素的大小,元素值较小,则移动父节点到空穴,空穴上移。。。
{
H->arr[i] = H->arr[i / 2];
i /= 2;
}
H->arr[i] = x; // 停止移动的空穴赋值为x
}
堆的删除操作
- 删除堆顶元素(最小值),也叫做优先队列出队。首先返回该元素值,将堆顶(根节点)置为“空穴”。
- 然后选择“空穴”的较小的孩子移动到空穴,“空穴”下移。这里选取堆中最后位置的元素X作为插入的元素。
- 接下来类似于插入操作,当X都大于“空穴”的孩子,小孩子移动“空穴”,“空穴”下移,直到X小于“空穴”的孩子。
- 或者,“空穴”移动到堆最末尾。“空穴”停止移动,将X填入空穴。
- 这种策略被称为下滤。
ElemType DeleteMin(BinHeap H) // 删除优先队列(堆)中的最小值
{
if (isEmpty(H))
{
cout << "The heap is empty" << endl;
return H->arr[0];
}
ElemType minElem = H->arr[1]; // 要返回的最小值:堆中数组的首元素/树形结构的根节点
ElemType lastElem = H->arr[H->Size--]; // 记录最后一个元素的值,且更新H->Size
// 由于删除了根节点,树形结构需要进行调整
int i = 1;
int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求较小的孩子
while (H->arr[priorChild] < lastElem && priorChild <= H->Size) // 将根节点置为空穴
{
H->arr[i] = H->arr[priorChild]; // 选取较小的孩子填充空穴
i = priorChild; // 空穴下移,"下滤"
priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 更新"优先"孩子
}
H->arr[i] = lastElem; // 将原来堆里面最后一个元素的值填入空穴
return minElem;
}
构建堆
- 从数组中直接构建堆,既不需要依次插入元素来构建堆。
- 采用“下滤”的策略,依次从有孩子的节点(堆大小除以2)开始至根节点结束,对这些节点依次执行“下滤”操作。
BinHeap BuildHeap(ElemType* A, int n) // 直接由数组构建堆
{
BinHeap H = initializer(n * 2);
for (int i = 0;i < n;i++)
{
H->arr[i + 1] = A[i];
}
H->Size = n; // 将元素填入堆中
for (int i = n / 2;i > 0;i--) // 从有孩子的节点开始执行下滤操作
{
int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下标
while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size) // 当节点i的小孩子小于其自身且其孩子的下标不超过堆大小时,执行循环
{
ElemType temp = H->arr[i];
H->arr[i] = H->arr[SmallChild];
H->arr[SmallChild] = temp; // 交换节点i与其小孩子的位置
i = SmallChild; // 节点i下移
SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 更新“小孩子”
}
}
return H;
}
附二叉堆实现及相关操作C/C++
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct HeapStruct { // 定义堆的结构(基于数组)
int Capacity; // 堆的最大容量
int Size; // 堆中元素个数
ElemType* arr; // 堆中包含数据元素的数组的头指针
}Heap, *BinHeap;
BinHeap initializer(int Capacity) // 初始化二叉堆,参数为最大容量
{
BinHeap H = new Heap; // 创建一个堆结构
H->Capacity = Capacity;
H->arr = new ElemType[Capacity]; // 为堆中数组分配空间
H->arr[0] = -9999; // 将数组的0元素赋值为无穷小,表示添加一条哑信息,保证该位置元素小于任何堆中元素
H->arr[1] = 50;
H->Size = 0;
return H;
}
bool isEmpty(BinHeap H)
{
return H->Size == 0;
}
bool isFull(BinHeap H)
{
return H->Size == H->Capacity;
}
void insert(BinHeap H, ElemType x)
{
if (isFull(H)) // 判断队列是否为满
{
cout << "The heap is full" << endl;
exit(1);
}
int i = ++H->Size; // 将队列的大小增1,赋值给i表示要插入的空穴
while (x < H->arr[i / 2]) // 比较插入位置的父节点的值和要插入的元素的大小,元素值较小,则移动父节点到空穴,空穴上移。。。
{
H->arr[i] = H->arr[i / 2];
i /= 2;
}
H->arr[i] = x; // 停止移动的空穴赋值为x
}
ElemType DeleteMin(BinHeap H) // 删除优先队列(堆)中的最小值
{
if (isEmpty(H))
{
cout << "The heap is empty" << endl;
return H->arr[0];
}
ElemType minElem = H->arr[1]; // 要返回的最小值:堆中数组的首元素/树形结构的根节点
ElemType lastElem = H->arr[H->Size--]; // 记录最后一个元素的值,且更新H->Size
// 由于删除了根节点,树形结构需要进行调整
int i = 1;
int priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求较小的孩子
while (H->arr[priorChild] < lastElem && priorChild <= H->Size) // 将根节点置为空穴
{
H->arr[i] = H->arr[priorChild]; // 选取较小的孩子填充空穴
i = priorChild; // 空穴下移,"下滤"
priorChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 更新"优先"孩子
}
H->arr[i] = lastElem; // 将原来堆里面最后一个元素的值填入空穴
return minElem;
}
BinHeap BuildHeap(ElemType* A, int n) // 直接由数组构建堆
{
BinHeap H = initializer(n * 2);
for (int i = 0;i < n;i++)
{
H->arr[i + 1] = A[i];
}
H->Size = n; // 将元素填入堆中
for (int i = n / 2;i > 0;i--) // 从有孩子的节点开始执行下滤操作
{
int SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 求“小孩子”下标
while (H->arr[SmallChild] < H->arr[i] && SmallChild<H->Size) // 当节点i的小孩子小于其自身且其孩子的下标不超过堆大小时,执行循环
{
ElemType temp = H->arr[i];
H->arr[i] = H->arr[SmallChild];
H->arr[SmallChild] = temp; // 交换节点i与其小孩子的位置
i = SmallChild; // 节点i下移
SmallChild = H->arr[2 * i] < H->arr[2 * i + 1] ? 2 * i : 2 * i + 1; // 更新“小孩子”
}
}
return H;
}
int main()
{
const int rawdata[] = { 19, 13, 9, 8, 23, 39, 4, 2, 75, 100, 43, 58 };
int tablesize = 12;
BinHeap myHeap = initializer(tablesize*2);
for (int i = 0;i < sizeof(rawdata) / sizeof(int);i++)
{
insert(myHeap, rawdata[i]); // 向堆中插入给定数据
}
cout << "Prior Deque from the Heap : \n"; // 依次优先出队列
while (!isEmpty(myHeap))
{
cout << DeleteMin(myHeap) << " ";
}
cout << endl;
int newdata[] = { 9, 8, 23, 39, 2, 75, 100, 43, 58 };
BinHeap newHeap = BuildHeap(newdata, 9);
cout << "Build a new Heap directly from an array...\n";
cout << "Prior Deque from the new Heap : \n"; // 依次优先出队列
while (!isEmpty(newHeap))
{
cout << DeleteMin(newHeap) << " ";
}
cout << endl;
delete myHeap;
delete newHeap;
system("pause");
return 0;
}
- 操作运行结果
Prior Deque from the Heap :
2 4 8 9 13 19 23 39 43 58 75 100
Build a new Heap directly from an array...
Prior Deque from the new Heap :
2 8 9 23 39 43 58 75 100
请按任意键继续. . .
参考资料
Mark Allen Weiss: 数据结构与算法分析