经典算法之堆排序及优先队列

本文详细介绍了优先队列的概念及其操作,包括有序和无序数组或链表实现、二叉堆实现,并分析了各种实现的最坏时间复杂度。此外,还探讨了堆排序的算法描述和C++实现,以及其时间复杂度。通过学习,读者可以更好地理解和应用这些数据结构与算法。
摘要由CSDN通过智能技术生成

活动地址:CSDN21天学习挑战赛

📚“九层之台,起于垒土”。学习技术须脚踏实地。

☀️你要做冲出的黑马,而不是坠落的星星。
📖这里推荐一款刷题、模拟面试神器,可助你斩获心仪大厂offer:点我免费刷题、模拟面试

优先队列

支持删除最大元素和插入元素的数据类型叫做优先队列

优先队列的操作

  1. 向优先队列中插入一个元素。
  2. 返回最大元素。
  3. 删除并返回最大元素。
  4. 返回队列中元素的个数。
  5. 返回队列是否为空。

优先队列的实现

1. 数组或链表的有序实现

对于数组来说,将最大的元素置于数组尾部,每次插入遍历数组,将所有较大的元素向移动,然后将要插入的元素置于应在的位置,就像插入排序一样。删除则仅需要将最大的元素删去即可。
对于链表,将最大的元素置于表头,每次插入遍历,找到对应结点插入,此过程需要维护两个指针,用于比较的指针与这个指针的前一个指针,用于插入。

2. 数组或链表的无序实现

对于数组来说,每次插入就将元素置于数组尾部,删除时则遍历数组,找到最大的与数组尾部的元素交换,并返回。
对于链表,每次插入将元素置于表头,删除时遍历,同样维护两个指针。

3. 二叉堆实现

当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序
二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存。

算法实现:

  1. 插入节点时,先将其插入数组的尾部,让其上浮(swim),直至到达根节点或小于等于父节点。
  2. 删除节点时,将数组第一个元素与最后一个元素交换,让第一个节点下沉(sink),直至到达叶子节点或大于子节点。

C++实现:

下面的算法将数组索引为 0 的位置空了出来,数组的长度为能存储最大元素数量加一。

template<typename T>
void sink(T* a, int k, size_t size){
    while(2*k <= size){
        int j = 2*k;
        if(j < size && a[j] < a[j + 1])j++;
        if(a[k] > a[j]) break;
        swap(a[k], a[j]);
        k = j;
    }
}

template<typename T>
void swim(T* a, int k){
    while(k > 1 && a[k/2] < a[k]){
        swap(a[k/2], a[k]);
        k = k/2;
    }
}

template<typename T>
void insert(T* a, T t, int &size){
    a[++size] = t;
    swim(a, size);
}

template<typename T>
T delMax(T* a, int &size){
    T max = a[1];
    swap(a[1], a[size--]);
    sink(a, 1, size);
    return max;
}

4.三种实现的最坏复杂度分析

通过上面的分析,不难得出:

数据结构插入元素删除最大元素
有序数组或链表N1
无序数组或链表1N
logNlogN

堆排序的概念

我们可以把优先队列变成一种排序方法,每次选取最大的元素,不断构造堆,就可以得到有序序列。

算法描述

  • 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  • 将根节点与末尾节点交换。
  • 交换后的根节点下沉,直到满足堆定义。

C++实现

template<typename T>
void sink(T* a, int k, size_t size){
    while(2*k <= size){
        int j = 2*k;
        if(j < size && a[j - 1] < a[j])j++;
        if(a[k - 1] > a[j - 1]) break;
        swap(a[k - 1], a[j - 1]);
        k = j;
    }
}

template<typename T>
void heapsort(T *a, size_t N){
    for (int k = N / 2; k >= 1; k--) {
        sink(a, k, N);
    }
    while (N > 1) {
        swap(a[0], a[--N]);
        sink(a, 1, N);
    }
}

复杂度分析

  • 时间复杂度:
    • 比较时间:建堆是通过父节点和子节点两两比较并交换得到的,时间复杂度为O(n)。
    • 交换时间:调整堆需要交换n-1次堆顶元素,并调整堆,调整堆的过程就是满二叉树的深度logn。
      所以时间复杂度为O(nlogn)。
  • 空间复杂度:需要几个变量存储索引,所以为O(1)。

🚩本周的学习就到此结束啦,本周仍以复习经典排序算法为主,学习算法过程中动图有助于我们了解算法执行的过程当然自己画一遍试试坑会理解的更深。
✨要想超越别人的脚步,掌握比别人更多的知识!那就快来刷题吧:点我免费刷题、模拟面试

🌏我是博仔,期待你的关注,让我们一起进步!

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的博仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值