堆和优先队列 (C语言实现)
一 引子
相信大家都接触过二叉树,而二叉树中有一个特殊的树——完全二叉树,若我们从上到下,从左到右,将每一个结点从1开始编号,完成后,我们可以得到这样一个规律:若某个结点的编号是 i,那该结点的左孩子的编号是2 * i,右孩子的编号是 2 * i + 1,为此我们可以用数组来存储这棵完全二叉树。
示意图如下:
了解完全二叉树的这个性质之后,我们接着来了解堆排序和优先队列
二 大顶堆和小顶堆
大顶堆:在二叉树中父结点的值比左右孩子的值大,但是左右孩子值彼此间的没有明确的大小关系。
小顶堆:父结点的值小于左右孩子结点的值小,左右孩子之间的值同样没有大小关系。
示意图如下:
显然,不论是大顶堆还是小顶堆都是一棵完全二叉树,所以完全可以用数组来存储。
现在我们来看看在堆的基础上实现优先队列和堆排序!
三 优先队列
优先队列,顾名思义,它是一个队列,但它区别于其他普通队列,普通队列的性质就是“先进先出”,而优先队列,是选择值最大或者是最小的元素优先出队列,故先进不一定先出, 关于普通队列的基本操作实现,请点击我。
将元素插入优先队列中,本质上其实是就是插入到堆中,重要的是我们要根据插入元素值的大小进行调整,把它放到数组的恰当位置,使得整个优先队列的性质不被破坏,每次出队的都是当前队列中优先级最大或最小的元素。
假设元素的值越大则优先级越高,越先出队列 (对应的就是大顶堆)。
首先我们将元素放在数组的最后一个位置,若当前的下标是ind,那它父结点的编号是ind >> 1,此时我们比较该结点和父节点的值,若比父结点的值大,交换该结点和父结点的值,如此循环下去(从后向前),直到父结点的值更大或者当前结点已经是数组中的第一个结点,此时调整完整。
现在将队首元素出队列,输出队首元素后,我们将数组中最后的元素放在原队首元素的位置,此时显然不满足大顶堆的性质,我们要进行调整。首先我们找到左右孩子中值较大的那个结点,将它和父结点的值进行调整,如此循环下去,直到当前结点的值比左右孩子的值都要大,或者该结点已经是叶子结点。
示意图如下:
实现代码为:
typedef struct priority_queue {
int *data;
int size, cnt;
} priority_queue;
//向优先队列p中插入val,成功返回1,失败返回0
int push(priority_queue *p, int val) {
if (!p) return 0;
if (p->cnt == p->size) return 0;
p->cnt++;
p->data[p->cnt] = val; //向队列最后插入val
int tmp = p->cnt;
while (tmp >> 1 && p->data[tmp] > p->data[tmp >> 1]) { //进行调整
swap(p->data[tmp], p->data[tmp >> 1]);
tmp >>= 1;
}
return 1;
}
//删除队首元素,成功返回1,失败返回0
int pop(priority_queue *p) {
if (!p) return 0;
if (empty(p)) return 0;
int ret = p->data[1];
p->data[1] = p->data[p->cnt--]; //将最后一个元素放在队首的位置
int ind = 1;
while (ind << 1 <= p->cnt) { //从前到后进行调整
int tmp = ind, l = 2 * ind, r = 2 * ind + 1;
if (p->data[l] > p->data[tmp]) tmp = l;
if (r <= p->cnt && p->data[r] > p->data[tmp]) tmp = r;
if (tmp == ind) break;
swap(p->data[ind], p->data[tmp]);
ind = tmp;
}
return ret;
}
四 堆排序
假设我们现在要升序排列,那么我们要用的是大顶堆
排序的基本思路很简单:首先将数组构成大顶堆,之后我们可以确定整个数组中最大的元素就在顶端,也就是数组中的第一个位置,现在我们将最后一个元素和队首元素交换,之后再调整堆,得到一个大顶堆和放在数组最后位置的最大的元素,如此不断的循环,不断的将当前最大的数字放在数组的后面并且是从后往前放。直到整个数组完全有序,排序结束。
示意图如下:
关键代码为:
//从下标为ind的地方开始向后调整
void update(int *arr, int n, int ind) {
while ((ind << 1) <= n) { //当左孩子存在的时候
int tmp = ind, lchild = ind * 2, rchild = lchild + 1;
if (arr[tmp] < arr[lchild]) tmp = lchild;
if (rchild <= n && arr[rchild] > arr[tmp]) tmp = rchild; //若右孩子存在,且右孩子的值较大
if (tmp == ind) break;
swap(arr[tmp], arr[ind]);
ind = tmp;
}
}
void heap_sort(int *arr, int n) {
arr--;
for (int i = n >> 1; i >= 1; i--) { //建成大顶堆
update(arr, n, i);
}
for (int i = n; i > 1; i--) { //将最大值交换到数组最后,并调整数组
swap(arr[1], arr[i]);
update(arr, i - 1, 1);
}
}
五 总代码献上
堆排序
#include <iostream>
using namespace std;
void update(int *arr, int n, int ind) {
while ((ind << 1) <= n) {
int tmp = ind, lchild = ind * 2, rchild = lchild + 1;
if (arr[tmp] < arr[lchild]) tmp = lchild;
if (rchild <= n && arr[rchild] > arr[tmp]) tmp = rchild;
if (tmp == ind) break;
swap(arr[tmp], arr[ind]);
ind = tmp;
}
}
void heap_sort(int *arr, int n) {
arr--;
for (int i = n >> 1; i >= 1; i--) {
update(arr, n, i);
}
for (int i = n; i > 1; i--) {
swap(arr[1], arr[i]);
update(arr, i - 1, 1);
}
}
void output(int *arr, int n) {
printf("arr(%d) = [", n);
for (int i = 0; i < n; i++) {
if (i) printf(" ");
printf("%d", arr[i]);
}
printf("]\n");
}
int main() {
srand(time(0));
#define MAX_OP 20
int arr[MAX_OP + 5] = {0};
for (int i = 0; i < MAX_OP; i++) {
int val = rand() % 100;
arr[i] = val;
}
output(arr, MAX_OP);
heap_sort(arr, MAX_OP);
output(arr, MAX_OP);
return 0;
}
/*代码结果输出:
arr(20) = [18 41 93 46 19 94 51 4 7 39 46 65 59 54 53 29 42 38 87 16]
arr(20) = [4 7 16 18 19 29 38 39 41 42 46 46 51 53 54 59 65 87 93 94]
*/
优先队列
#include <iostream>
using namespace std;
#define swap(a, b) {\
__typeof(a) __tmp = a;\
a = b, b = __tmp;\
}
typedef struct priority_queue {
int *data;
int size, cnt;
} priority_queue;
priority_queue *init(int size) {
priority_queue *p = (priority_queue *)malloc(sizeof(priority_queue));
p->data = (int *)malloc(sizeof(int) * (size + 1));
p->size = size;
p->cnt = 0;
return p;
}
int empty(priority_queue *p) {
return p->cnt == 0;
}
int top(priority_queue *p) {
return p->data[1];
}
int push(priority_queue *p, int val) {
if (!p) return 0;
if (p->cnt == p->size) return 0;
p->cnt++;
p->data[p->cnt] = val;
int tmp = p->cnt;
while (tmp >> 1 && p->data[tmp] > p->data[tmp >> 1]) {
swap(p->data[tmp], p->data[tmp >> 1]);
tmp >>= 1;
}
return 1;
}
int pop(priority_queue *p) {
if (!p) return 0;
if (empty(p)) return 0;
int ret = p->data[1];
p->data[1] = p->data[p->cnt--];
int ind = 1;
while (ind << 1 <= p->cnt) {
int tmp = ind, l = 2 * ind, r = 2 * ind + 1;
if (p->data[l] > p->data[tmp]) tmp = l;
if (r <= p->cnt && p->data[r] > p->data[tmp]) tmp = r;
if (tmp == ind) break;
swap(p->data[ind], p->data[tmp]);
ind = tmp;
}
return ret;
}
void output(priority_queue *p) {
if (!p) return ;
printf("priority_queue(%d) = [", p->cnt);
for (int i = 0; i < p->cnt; i++) {
if (i) printf(" ");
printf("%d", p->data[i]);
}
printf("\n");
}
void clear(priority_queue *p) {
if (!p) return ;
free(p->data);
free(p);
}
int main() {
srand(time(0));
#define MAX_OP 20
priority_queue *p = init(MAX_OP);
for (int i = 0; i < MAX_OP; i++) {
int val = rand() % 100;
push(p, val);
printf("push %d into priority_queue\n", val);
}
printf("\n");
printf("遍历输出数组元素:\n");
for (int i = 0; i < MAX_OP; i++) {
printf("%d ", p->data[i]);
}
printf("\n\n");
printf("不断取队首元素并将其弹出队列:\n");
for (int i = 0; i < MAX_OP; i++) {
printf("%d ", top(p));
pop(p);
}
printf("\n");
clear(p);
return 0;
}
/*代码输出结果如下:
push 50 into priority_queue
push 22 into priority_queue
push 32 into priority_queue
push 33 into priority_queue
push 81 into priority_queue
push 80 into priority_queue
push 34 into priority_queue
push 89 into priority_queue
push 47 into priority_queue
push 83 into priority_queue
push 52 into priority_queue
push 59 into priority_queue
push 18 into priority_queue
push 46 into priority_queue
push 3 into priority_queue
push 6 into priority_queue
push 27 into priority_queue
push 86 into priority_queue
push 93 into priority_queue
push 35 into priority_queue
遍历输出数组元素:
0 93 89 80 86 81 59 46 27 83 35 52 32 18 34 3 6 22 47 50
不断取队首元素并将其弹出队列:
93 89 86 83 81 80 59 52 50 47 46 35 34 33 32 27 22 18 6 3
*/
加油,路漫漫其修远兮,我将天天敲代码!与君共勉。