B站左神算法课学习笔记(P3):详解桶排序以及排序内容大总结

目录

一、快速排序的空间复杂度

二、堆

1、完全二叉树

2、堆的分类

3、堆的基本操作

添加堆

出堆

修改堆

4、时间复杂度

5、堆排序

代码实现

优化:

6、堆排序扩展题目

代码实现:

三、比较器

作用:

四、桶排序和基数排序

1、桶排序

桶排序的基本步骤:

桶排序的时间复杂度:

代码实现

2、基数排序

定义

代码实现


一、快速排序的空间复杂度

最差情况,每次都取到最大/最小值,一共展开 n 层递归,需要的空间复杂度为 O\left ( N \right )

最好情况,每次都取到中点值,相当于完全二叉树展开,需要的空间复杂度为O\left ( logN \right )

注意:用迭代代替递归无法节省空间!因为节省不下来!

递归实现了对于中点位置的存储,每层递归结束后才会释放相应资源,因为有了中点位置,快排才能借助递归实现。但是,若使用迭代,也需要手动记录中点位置,并使用栈结构,告诉计算机返回什么位置,以及该位置左右各是什么数。(自己的理解还是不太清楚,见谅)

二、堆

逻辑概念上,堆是一个完全二叉树结构。

1、完全二叉树

若某个树为满二叉树,或其为从上往下从左往右依次含有节点的树,则称之为完全二叉树。

任意数组从下标为 0 出发的任意一段都可以转化为完全二叉树。

例:如上左图,取数组前 7 位构成完全二叉树。

在上述数组中任取 i 位置,其左孩子下标为 2 * i + 1,其右孩子下标为 2 * i + 2,父节点为\frac{i-1}{2}.

2、堆的分类

大根堆:任意子树的最大值为头节点的值

小根堆:任意子树的最小值为头节点的值

3、堆的基本操作

添加堆

给出一个新的数字,将新的数字插入已有的大根堆 / 小根堆中。

方法:插入节点后,与其父节点 \frac{i-1}{2} 的比较大小。

例:

注意\frac{i-1}{2}需要向下取整,且 i = 0 时,运算结果为 0 .

代码中使用 heapInsert 函数实现:

while 循环反复检查是否符合大根堆的条件,若不符合,令子节点与父节点处的数值交换,并更新 index 为其父节点,再次进入循环判断。

出堆

在已有的大根堆 / 小根堆中,弹出堆顶元素,并做相应调整。

方法:大根堆中,先将末尾元素补至堆顶,令 heapSize - 1,然后判断是否还有左右孩子,若有,求其左右孩子的最大值并与之比较,若其最大,则停止;否则,交换两者位置并重复上述步骤。

代码中使用 heapify 函数实现:

修改堆

在堆中,修改某一位置的元素,要求仍然保持大根堆 / 小根堆。

方法:检查元素变大 / 变小,若变大,则调用 heapInsert 函数;若变小,则调用 heapify 函数。

4、时间复杂度

完全二叉树的高度

已知有 N 个节点,则完全二叉树的高度为 logN 。

相应地,堆操作中,无论是插入、弹出、调整,时间复杂度都是 O\left ( logN \right ) 级别。

5、堆排序

先建堆,然后进行堆排序操作。

堆排序中,每次将堆顶与堆末数字交换,然后在 heapSize-- 的空间上整理为有序堆,再重复上述操作。当 heapSize == 0 时,排序完成。

演示

代码实现

其中 heapInsert() 函数和 heapify() 函数如前文所示,前者负责建堆,后者负责整理堆为有序堆。

时间复杂度 O\left (N logN \right ) 。

优化:

heapInsert() 函数中,若一次性给出全部数组,则可使用该方法:

思想:从右往左从下往上做 heapify ,每次只需解决对应子树中的问题

6、堆排序扩展题目

注意:Java中,优先队列就是小根堆!

关于堆的实际使用,补充以下几点:

  1. 扩容问题:以数组存储堆结构时,每次耗尽都成倍扩容。故数组长度为2,4,6,8...,每次扩容需要O(N) ,扩容次数为O(logN),故整体扩容代价为\frac{O\left ( NlogN \right )}{N}=O\left ( logN \right ) !
  2. *黑盒理念*:编系统提供的堆结构是一个”黑盒“,只能高效执行输入一个(add)和输出一个(poll)数。缺点:在已有的堆结构上改变某个数,并令其重新调整为堆结构,此时调整代价很高!但是自己手写的堆结构可以实现修改后的高效调整。
    若需要实现高效调整,只能手写堆!

代码实现

建堆 -> 加入&弹出 -> 弹出剩余

三、比较器

例:

准备数组:

下图中,第一个参数为对应数组,第二个参数则是设置比较器。

比较器实现举例:

上图注释说明了比较器的返回规则:

  • 负数——第一个参数在前
  • 正数——第二个参数在前
  • 0——任意在前

上图中绿色部分其实等价于:

用上图更清晰,而用一句 return 则更简洁。

同理也可依照此对其他参数进行排序,如:

作用:

        减少代码量!!

四、桶排序和基数排序

特点:不是基于”比较“的排序,而是基于数据状况产生的排序!

实际使用需要根据数据状况进行定制。

1、桶排序

桶排序(Bucket Sort)是一种分配-合并型的排序算法,其基本思想是将数组元素分到有限数量的桶中,每个桶内的元素再单独排序,最后将每个桶内的元素按顺序合并成一个有序的数组。桶排序通常适用于元素均匀分布在某个范围内的情况。

桶排序的基本步骤:

  1. 分配: 将数据分配到若干个桶中。每个桶内的元素会根据某些规则被划分(例如根据数值范围或者其他条件)。

  2. 排序: 对每个桶内的元素进行排序。桶内的排序可以使用其他排序算法(如插入排序)。

  3. 合并: 将所有桶中的元素按顺序合并成一个最终的有序序列。

桶排序的时间复杂度:

  • 最优情况:O(n + k),其中 n 是数据的个数,k 是桶的数量。
  • 最差情况:O(n^2),当所有数据都集中在同一个桶时,退化为类似插入排序的情况。

代码实现

#include <iostream>
#include <vector>
#include <algorithm>  // For std::sort
#include <cmath>      // For std::floor

using namespace std;

void bucketSort(vector<int>& arr) {
    if (arr.empty()) return;

    // Step 1: Find the maximum value in the array
    int maxVal = *max_element(arr.begin(), arr.end());

    // Step 2: Create empty buckets
    int bucketCount = sqrt(arr.size());  // Bucket count can be square root of number of elements
    vector<vector<int>> buckets(bucketCount);

    // Step 3: Place elements into buckets based on their value
    for (int num : arr) {
        int index = (num * bucketCount) / (maxVal + 1);  // Find the appropriate bucket index
        buckets[index].push_back(num);
    }

    // Step 4: Sort each bucket
    for (auto& bucket : buckets) {
        sort(bucket.begin(), bucket.end());  // Sort the elements inside the bucket using standard sort
    }

    // Step 5: Concatenate the sorted buckets back into the original array
    arr.clear();
    for (const auto& bucket : buckets) {
        for (int num : bucket) {
            arr.push_back(num);  // Append each sorted bucket to the result array
        }
    }
}

int main() {
    vector<int> arr = {42, 32, 33, 52, 37, 47, 51};
    
    cout << "Original Array: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    bucketSort(arr);

    cout << "Sorted Array: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

2、基数排序

定义

补充:计数排序 —— 基于类型进行统计、计数后排序

代码实现

相关函数实现:

tips:仅对于非负值!

解释:使用 count 数组完成分片!(或者说使用计数排序的结果模拟”桶“的概念)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值