堆及其操作
一、定义
定义:堆是特殊的队列,从堆中取出元素是按照元素的优先级大小,而不是按照元素进入的先后顺序。
储存方式:不管是用数组还是链表,插入删除最坏的情况计算复杂度都会是
O
(
N
)
O(N)
O(N)。而二叉搜索数不管插入还是删除计算复杂度都为
O
(
l
o
g
N
)
O(log N)
O(logN),所以利用数来存储这种特殊的队列,一般不特指就是完全二叉树存储。
上述表示方式有2个特性:
- 结构性:用数组表示的完全二叉树
- 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
根据结点关键字是最大最小分为:最大堆,最小堆。
最大堆(Maxheap):
不管选择那条路径,都是从大到小:路径56,19,18;路径56,40,3等。
最小堆(Minheap):
不管选择那条路径,都是从小到大:路径5,16,49;路径17,19,33等。
不是堆:
前两幅图不是完全二叉树所以不是堆。后两幅图满足二叉树,但不满足顺序结构。
二、堆的抽象数据类型描述
分为最大最小堆处理’
数据名称:最大堆(MaxHeap)
数据对象集:完全二叉树,每个结点的元素值不小于其子结点的元素值
操作集:最大堆 H ∈ MaxHeap,元素 item ∈ ElementType
主要操作有:
MaxHeap Create(int MaxSize)
:创建一个空的最大堆
Boolean IsFull(MaxHeap H)
:判断最大堆 H 是否已满
Boolean Insert(MaxHeap H,ElementType item)
:将元素 item 插入最大堆 H
Boolean IsEmpty(MaxHeap H)
:判断最大堆 H 是否为空
ElementType DeleteMax(MaxHeap H)
:返回 H 中最大元素(高优先级)
1、最大堆操作实现
# include<iostream>
# include<malloc.h>
# define ERROR -1
using namespace std;
typedef int ElementType;
typedef struct HeapStruct *MaxHeap;
struct HeapStruct
{
int * Data; // 存储堆元素的数组
int Size; // 存储当前元素个数
int Capacity; // 堆的最大容量
};
MaxHeap Create(int Maxsize); // 建堆
bool IsFull(MaxHeap H); // 判断是否是满堆
bool Insert(MaxHeap H, ElementType X); // 堆的插入操作
bool IsEmpty(MaxHeap H); // 判断是否是空堆
ElementType DeleteMax(MaxHeap H); // 删除并返回最大元素
void LevelOrderTraversal(MaxHeap H); // 层序遍历输出
// 建堆
MaxHeap Create(int Maxsize)
{
MaxHeap H = (MaxHeap)malloc(sizeof(struct HeapStruct)); // 创建结点
H->Data = (ElementType *)malloc((Maxsize + 1) * sizeof(ElementType));
H->Size = 0;
H->Capacity = Maxsize;
H->Data[0] = 10000; // "哨兵"大于堆中所有可能的值
return H;
}
// 是否满堆
bool IsFull(MaxHeap H)
{
return (H->Size == H->Capacity);
}
// 插入操作 从完全二叉树的最后一个位置插入
bool Insert(MaxHeap H, ElementType X)
{
int i; // 元素插入的位置
// 判断是否是满堆
if (IsFull(H))
{
cout << "堆已经满了" << endl;
return false;
}
i = ++H->Size; // 将元素安排在最后一个位置
// 新增结点元素与(新节点到整颗完全二叉树根结点)链路上所有结点元素比较,将数值小的放于下层
for (; H->Data[i / 2] < X; i /= 2) // 由于设置了哨兵,在[0]位置设置了一个不会背超出的超大的值,以保证循环可以跳出
{
H->Data[i] = H->Data[i / 2];
}
H->Data[i] = X; // 已经找到X合适位置
return true;
}
// 判断是否是空堆
bool IsEmpty(MaxHeap H)
{
return (H->Size == 0);
}
// 删除操作(删除最大值) 明显只删除根结点(因为根节点最大)
ElementType DeleteMax(MaxHeap H)
{
if (IsEmpty(H))
{
cout << "空堆,没有元素可以删除" << endl;
return ERROR;
}
int parent; // 最后一个结点元素的存放位置
int child; // 永远指向最大的结点元素
ElementType Max, X; // 存放最大值,最后一个结点元素值
Max = H->Data[1]; // 去除最大值存在Max中
X = H->Data[H->Size]; // 选定最后一个元素
--H->Size; // 因为取出根结点的元素,所以此时堆规模要变小
for (parent = 1; parent * 2 <= H->Size; parent = child) // parent = child继续向下层过滤
{
child = parent * 2; // child指向左二子的位置
if ((!child == H->Size) && (H->Data[child] < H->Data[child + 1])) // !child == H->Size左二子不等于当前个数,说明一定存在又儿子
{
// 右二子存在且比左二子数值大
++child; // child指向最大值
}
if (X > H->Data[child])
break; // 不管是左二子还是右儿子都没替代的元素X大所以直接跳出
else // child指向的元素值大
H->Data[parent] = H->Data[child];
}
H->Data[parent] = X;
return Max;
}
// 层序遍历打印的结果
void LevelOrderTraversal(MaxHeap H)
{
cout << "层序遍历结果: " << endl;
for (int i = 1; i <= H->Size; ++i)
{
cout << H->Data[i] << " ";
cout << endl;
}
}
int main()
{
// 创建最大堆实例对象
MaxHeap H;
int MaxSize = 100;
H = Create(MaxSize); // 创建好堆并初始化
// 插入数值
Insert(H, 55);
Insert(H, 66);
Insert(H, 44);
Insert(H, 33);
Insert(H, 11);
Insert(H, 22);
Insert(H, 88);
Insert(H, 99);
/*
99
/ \
88 66
/ \ / \
55 11 22 44
/
33
*/
LevelOrderTraversal(H);
DeleteMax(H);
LevelOrderTraversal(H);
DeleteMax(H);
LevelOrderTraversal(H);
return 0;
}
2、最小堆操作实现
只需将根节点的换做最小值,并且保证每条链路是从小到大。和上述最大堆的操作基本相似。不做赘述。
三、堆的建立
1、定义
是指将存在的N个元素按照堆的方式存放在一维数组中。
分析:1、假如使用最原始的方式。将元素一个一个相继插入到初始为空的堆中。每次插入的时间复杂度为 O ( l o g N ) O(log N) O(logN),一共 N N N个数,所以计算复杂度为 O ( N l o g N ) O(Nlog N) O(NlogN)。
2、新的方式:
- 先将N个元素按照输入顺序存入二叉树中,只需要满足完全二叉树的结构特性,不需要满足有序性
- 最后调整各点元素满足最小最大堆的有序特性。
2、最小堆的建立
# include <iostream>
# include <malloc.h>
using namespace std;
typedef struct HeapNode * Heap;
// 堆结点构造
struct HeapNode
{
int * Data; // 存放数值的数组
int Size; // 当前元素的个数
int Capacity; // 最大容量
};
// 初始化堆
Heap HeapCreate(int MaxSize)
{
// 申请结点
Heap H;
H = (Heap)malloc(sizeof(struct HeapNode));
// 结点元素初始化
H->Data = (int *)malloc((MaxSize + 1) * sizeof(int));
H->Size = 0;
H->Capacity = MaxSize;
return H;
}
// 子树调成为有序的完全二叉树(堆)
void PerDown(Heap H, int p)
{
int Parent; // 存放当前树的最大值
int child; // 永远指向最小值 最小值的下标
int X, N;
N = H->Size;
X = H->Data[p]; // 取当前根结点的值
for (Parent = p; Parent * 2 <= N; Parent = child)
{
child = Parent * 2; // child指向左儿子
if ((child != N) && (H->Data[child] > H->Data[child + 1])) // 假如右二子比左儿子小
++child; // child指向右儿子
if (X <= H->Data[child]) // 发现根结点值最小
break;
else // 若根结点不是最小,那就是两儿子
H->Data[Parent] = H->Data[child];
}
H->Data[Parent] = X;
}
// 将整棵树调整(一棵树分为好多个子树)
void BuildHeap(Heap H)
{
for (int i = H->Size / 2; i > 0; --i)
{
PerDown(H, i);
}
}
// 层序遍历打印
void LevelOrderTraversal(Heap H)
{
for (int i = 1; i <= H->Size; ++i)
{
cout << H->Data[i] << " ";
}
cout << endl;
}
int main()
{
Heap H;
H = HeapCreate(100); // 初始化堆
// 手动输入元素
int N;
cout << "输入元素个数 " << endl;
cin >> N;
/*for (int i = 1; i <= N; ++i)
{
cin >> H->Data[i];
++H->Size;
}*/
// 数据输入
cout << "数据输入" << endl;
for (int i = 0; i < N; ++i)
{
cin >> H->Data[++H->Size];
}
LevelOrderTraversal(H);
// 调整为最小堆
BuildHeap(H);
LevelOrderTraversal(H);
return 0;
}
可以看出一个无序的完全二叉树进行结点向下过滤操作是构建最小堆的主要工作。最大的比较次数就说树中各个结点高度的和。
一个高度为 h h h的完全二叉树最多包含 2 h − 1 2^h -1 2h−1个结点,所以结点的高度和为: 2 h + 1 − 1 − ( h + 1 ) 2^{h+1} -1 -(h+1) 2h+1−1−(h+1)。一个完全二叉树的结点个数 N N N是在 2 h − 1 2^{h -1} 2h−1和 2 h − 1 2^h -1 2h−1之间。所以结点高度和 O ( N ) O(N) O(N)。堆建立的复杂度与结点个数呈现线性关系。