堆排序和优先队列

堆和优先队列 (C语言实现)

一 引子

  相信大家都接触过二叉树,而二叉树中有一个特殊的树——完全二叉树,若我们从上到下,从左到右,将每一个结点从1开始编号,完成后,我们可以得到这样一个规律:若某个结点的编号是 i,那该结点的左孩子的编号是2 * i,右孩子的编号是 2 * i + 1,为此我们可以用数组来存储这棵完全二叉树。
  示意图如下:
完全二叉树 数组1完全二叉树 数组存储2
了解完全二叉树的这个性质之后,我们接着来了解堆排序和优先队列

二 大顶堆和小顶堆

  大顶堆:在二叉树中父结点的值比左右孩子的值大,但是左右孩子值彼此间的没有明确的大小关系。
  小顶堆:父结点的值小于左右孩子结点的值小,左右孩子之间的值同样没有大小关系。
  示意图如下:
堆
  显然,不论是大顶堆还是小顶堆都是一棵完全二叉树,所以完全可以用数组来存储。
  现在我们来看看在堆的基础上实现优先队列和堆排序!

三 优先队列

  优先队列,顾名思义,它是一个队列,但它区别于其他普通队列,普通队列的性质就是“先进先出”,而优先队列,是选择值最大或者是最小的元素优先出队列,故先进不一定先出, 关于普通队列的基本操作实现,请点击我
  将元素插入优先队列中,本质上其实是就是插入到堆中,重要的是我们要根据插入元素值的大小进行调整,把它放到数组的恰当位置,使得整个优先队列的性质不被破坏,每次出队的都是当前队列中优先级最大或最小的元素。
  假设元素的值越大则优先级越高,越先出队列 (对应的就是大顶堆)。
 
  首先我们将元素放在数组的最后一个位置,若当前的下标是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
*/


加油,路漫漫其修远兮,我将天天敲代码!与君共勉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值