一.堆的概念及结构
1.堆的概念
一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值。
2.堆的结构
堆在物理层面上,表现为一组连续的数组区间,将整个数组看作是堆。
堆在逻辑结构上,一般被视为是一颗完全二叉树。满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;反之,则是小堆,或者小根堆,或者最小堆。当一个堆为大堆时,它的每一棵子树都是大堆。
3.堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树
这里有一点需要注意,堆上的节点的值只需要和其父亲节点和孩子节点上的值进行比较,对于邻居的值是不做要求的。
4.堆的相关练习题
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
解析:就以A为例,把其写成堆的形式:100 | 60 70 | 50 32 65,这是一颗二叉树形式。
2.
已知小根堆为
8
,
15
,
10
,
21
,
34
,
16
,
12
,删除关键字
8
之后需重建堆,在此过程中,关键字之间的比较次数是(C)。
A
1
B
2
C
3
D
4
解析:对于该问题,先将8和12交换位置,然后删掉8,这样就是12 | 15 10 | 21 34 16,这样我们adjustdown(向下调整建堆),15先和10比较,然后12和10比较,然后12和16比较,一共三次
3.
一组记录排序码为
(
5 11 7 2 3 17
),
则利用堆排序方法建立的初始堆为
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
之后,其结果是()
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
]
二.堆的实现
1.头文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
bool HeapEmpty(HP* php);
int HeapSize(HP* php);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
2.初始化
因为堆在物理结构上其实就是一个数组,所以我们初始化的时候按照顺序表那样初始化就可以
void HeapInit(HP* php)
{
assert(php);
php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);
if (php->a == NULL)
{
perror("malloc fail");
return;
}
php->size = 0;
php->capacity = 4;
}
3.建堆方法
(都是以大根堆为例)
(1).向上调整建堆
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while(parent > 0)
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向上调整建堆是相对简单的,因为一个节点只有一个父节点,所以就一直往上走就行。
(2).向下调整建堆
向下调整建堆是有条件的,那就是左右必须都是大堆/小堆。
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
// 选出左右孩子中大的那一个
if (child + 1 < n && a[child+1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
4.插入
void HeapPush(HP* php, HPDataType x)
{
assert(php);
//先把数据插入数组内部
if (php->size == php->capacity)
{
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity*2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity *= 2;
}
php->a[php->size] = x;
php->size++;
//把新插入的数据排好
AdjustUp(php->a, php->size - 1);
}
5.删除
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
// 因为堆顶数据一定是最大的(最小的),所以删除数据的话一定是把堆顶数据删除
// 删除数据
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
6.杂项
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
三.堆排序
堆排序是几大排序中的重要一环,我们就在本篇中说一下堆排序
1.基本思想
堆排序
(Heapsort)
是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
2.代码实现
// 左右子树都是大堆/小堆
void AdjustDown(int* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
// 选出左右孩子中大的那一个
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
// 建堆 -- 向下调整建堆 -- O(N)
//这里这样写可以不用写adjustup来建堆,堆已经被自动建好了
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
// 自己先实现 -- O(N*logN)
int end = n - 1;
while (end > 0)
{
Swap(&a[end], &a[0]);
AdjustDown(a, end, 0);
--end;
}
}
3.向上调整,向下调整对比
对于堆排序,我们选择向上排序还是向下排序是有说法的,请看下面:
向上调整建堆:
向下调整建堆:
通过对比,我们发现当N个数相对大一点的时候,向上调整建堆比向下调整建堆的建堆次数要多的多,从效率上讲,向下调整建堆绝对是更好的选择。
从时间复杂度上讲,向下是更佳的,但是从最后结果上看,向上和向下对堆排序整体的时间复杂度是没有影响的。
4.堆排序特点
1.
堆排序使用堆来选数,效率就高了很多。
2.
时间复杂度:
O(N*logN)
3.
空间复杂度:
O(1)
4.
稳定性:不稳定