目录
1 二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而
完全二叉树更适合使用顺序结构存储。
现实中我们通常把
堆(一种二叉树)
使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
2 堆的概念及结构
总结:
1.首先要是完全二叉树
2.完全二叉树中的结点必须满足:
a 如果任意节点都比其孩子节点小,将其称作小堆(小根堆)
b 如果任意节点都比其孩字节点大,将其称作大堆(大根堆)
(根节点要不是最小的,要不是最大的)
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
练习
1.
下列关键字序列为堆的是:()A
A
100
,
60
,
70
,
50
,
32
,
65
B
60
,
70
,
65
,
50
,
32
,
100
C
65
,
100
,
70
,
32
,
50
,
60
D
70
,
65
,
100
,
32
,
50
,
60
E
32
,
50
,
100
,
70
,
65
,
60
F
50
,
100
,
70
,
65
,
60
,
32
2.
已知小根堆为
8
,
15
,
10
,
21
,
34
,
16
,
12
,删除关键字
8
之后需重建堆,在此过程中,关键字之间的比较次数是(3)。
15比10, 12比10, 12比16
A
1
B
2
C
3
D
4
3.
一组记录排序码为
(
5 11 7 2 3 17
),
则利用堆排序方法建立的初始堆为 C
A
(
11 5 7 2 3 17
)
B
(
11 5 7 2 17 3
)
C
(
17 11 7 2 3 5
)
D
(
17 11 7 5 3 2
)
E
(
17 7 11 3 5 2
)
F
(
17 7 11 3 2 5
)
4.
最小堆
[
0
,
3
,
2
,
5
,
7
,
4
,
6
,
8
],
在删除堆顶元素
0
之后,其结果是(C)
自己画图
A
[
3
,
2
,
5
,
7
,
4
,
6
,
8
]
B
[
2
,
3
,
5
,
7
,
4
,
6
,
8
]
C
[
2
,
3
,
4
,
5
,
7
,
8
,
6
]
D
[
2
,
3
,
4
,
5
,
6
,
7
,
8
]
3 堆的实现
堆的基本操作有许多,建堆、删除、插入、获取堆顶元素、元素个数、判空、销毁等,具体看vs上的代码
3.1 堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整
初始 27为parent 15为child
向下调整法
void AdjustDown(int array[], int size, int parent)
{
int child = parent * 2 + 1; //优先让child标记左孩子
//举要检测parent是否满足堆的特性
//与其孩子进行比较,如果不满足则交换;一定要对两个孩子中较小的孩子进行交换
while(child < size)
{
if(child + 1 < size && array[child + 1] < array[child])
child = child + 1;
//检测右孩子有无,假设如果右孩子更下
//将右孩子定为child
//再去检测双亲和较小孩子的关系,不满足情况则交换
if(array[child] < array[parent]) //孩子比双亲小
{
Swap(&array[child], &array[parent]);
//大的元素往下移动会导致子树不满足情况
//往下定义
parent = child;
child = parent * 2 + 1;
}
}
//依次将子树这边全部用上述方法进行调整成为堆
}
3.2堆的创建
拿到一个数组,逻辑上是一颗完全二叉数,但不是堆,需要把它构建一个堆
3.3 建堆时间复杂度
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明
(
时间复杂度本来看的就是近似值,多几个节点不影响最终结果)
:
所以时间复杂度为 o(n)
3.4 堆的插入
先插入一个
10
到数组的尾上,再进行向上调整算法,直到满足堆
// 堆的插入
void HeapPush(Heap* hp, DataType x)
{
CheckHeapCapacity(hp);
hp->array[hp->size] = x;
hp->size++;
AdjustUp(hp, hp->size - 1);
}
3.5 堆的删除
堆的删除,删除的是堆顶的元素
// 堆的删除
void HeapPop(Heap* hp)
{
if (HeapEmpty(hp))
{
return;
}
// 堆中有元素
// 1. 将堆顶元素与堆中最后一个元素进行交换
Swap(&hp->array[0], &hp->array[hp->size - 1]);
// 2. 将堆中有效元素个数减少1个
hp->size--;
// 3. 将堆顶元素往下调整
AdjustDown(hp, 0);
}
3.6堆的优化
借助函数指针方式让用户自己可以选择创建大堆还是小堆
4 堆的应用
4.1堆排序
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
要升序:建大堆
要降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序
删除的那个元素就是最大的元素,放在数组中,依次放入,直到堆中剩余最后2个元素
下图中蓝色就代表已经删除了,也就是排序成功了
调整堆的代码
void AdjustHeap(int array[], int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
//右孩子存在,找两个中较大的孩子
if (child + 1 < size && array[child + 1] > array[child])
{
child = child + 1;
}
if (array[child] > array[parent])
{
Swap(&array[parent], &array[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
return;
}
}
}
void HeapSort(int array[], int size)
{
//1.建堆:升序--》建大堆
//找到倒数第一个非叶子节点,siez-1 是最后一个节点,
//注意:是非叶子节点,是最后一个节点的双亲
int lastNotLeaf = (size - 2) / 2;
for (int root = lastNotLeaf; root >= 0; --root)
{
AdjustHeap(array, size, root);
}
//2.利用堆删除的思想进行排序
int end = size - 1;
while(end)
{
Swap(&array[0], &array[end]);
AdjustHeap(array, end, 0);
end--;
}
}
4.2 TOP-K问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于
Top-K
问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了
(
可能 数据都不能一下子全部加载到内存中)
。
用堆来解决,基本思路如下:
1.
用数据集合中前
K
个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2.
用剩余的
N-K
个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余
N-K
个元素依次与堆顶元素比完之后,堆中剩余的
K
个元素就是所求的前
K
个最小或者最大的元素
时间复杂度“
1.用前K个元素建堆 --》建堆的时间复杂度 o(k)
2.用剩余的n-k 元素 和堆顶元素比较 o ((n-k)logk)
总时间复杂度: o(k + (n-k)logk)