目录
一、快速排序的空间复杂度
最差情况,每次都取到最大/最小值,一共展开 n 层递归,需要的空间复杂度为 ;
最好情况,每次都取到中点值,相当于完全二叉树展开,需要的空间复杂度为
注意:用迭代代替递归无法节省空间!因为节省不下来!
递归实现了对于中点位置的存储,每层递归结束后才会释放相应资源,因为有了中点位置,快排才能借助递归实现。但是,若使用迭代,也需要手动记录中点位置,并使用栈结构,告诉计算机返回什么位置,以及该位置左右各是什么数。(自己的理解还是不太清楚,见谅)
二、堆
逻辑概念上,堆是一个完全二叉树结构。
1、完全二叉树
若某个树为满二叉树,或其为从上往下从左往右依次含有节点的树,则称之为完全二叉树。
任意数组从下标为 0 出发的任意一段都可以转化为完全二叉树。
例:如上左图,取数组前 7 位构成完全二叉树。
在上述数组中任取 i 位置,其左孩子下标为 2 * i + 1,其右孩子下标为 2 * i + 2,父节点为.
2、堆的分类
大根堆:任意子树的最大值为头节点的值
小根堆:任意子树的最小值为头节点的值
3、堆的基本操作
添加堆
给出一个新的数字,将新的数字插入已有的大根堆 / 小根堆中。
方法:插入节点后,与其父节点 的比较大小。
例:
注意:需要向下取整,且 i = 0 时,运算结果为 0 .
代码中使用 heapInsert 函数实现:
while 循环反复检查是否符合大根堆的条件,若不符合,令子节点与父节点处的数值交换,并更新 index 为其父节点,再次进入循环判断。
出堆
在已有的大根堆 / 小根堆中,弹出堆顶元素,并做相应调整。
方法:大根堆中,先将末尾元素补至堆顶,令 heapSize - 1,然后判断是否还有左右孩子,若有,求其左右孩子的最大值并与之比较,若其最大,则停止;否则,交换两者位置并重复上述步骤。
代码中使用 heapify 函数实现:
修改堆
在堆中,修改某一位置的元素,要求仍然保持大根堆 / 小根堆。
方法:检查元素变大 / 变小,若变大,则调用 heapInsert 函数;若变小,则调用 heapify 函数。
4、时间复杂度
完全二叉树的高度
已知有 N 个节点,则完全二叉树的高度为 logN 。
相应地,堆操作中,无论是插入、弹出、调整,时间复杂度都是 级别。
5、堆排序
先建堆,然后进行堆排序操作。
堆排序中,每次将堆顶与堆末数字交换,然后在 heapSize-- 的空间上整理为有序堆,再重复上述操作。当 heapSize == 0 时,排序完成。
演示:
代码实现
其中 heapInsert() 函数和 heapify() 函数如前文所示,前者负责建堆,后者负责整理堆为有序堆。
时间复杂度 。
优化:
heapInsert() 函数中,若一次性给出全部数组,则可使用该方法:
思想:从右往左从下往上做 heapify ,每次只需解决对应子树中的问题
6、堆排序扩展题目
注意:Java中,优先队列就是小根堆!
关于堆的实际使用,补充以下几点:
- 扩容问题:以数组存储堆结构时,每次耗尽都成倍扩容。故数组长度为2,4,6,8...,每次扩容需要O(N) ,扩容次数为O(logN),故整体扩容代价为
!
- *黑盒理念*:编系统提供的堆结构是一个”黑盒“,只能高效执行输入一个(add)和输出一个(poll)数。缺点:在已有的堆结构上改变某个数,并令其重新调整为堆结构,此时调整代价很高!但是自己手写的堆结构可以实现修改后的高效调整。
若需要实现高效调整,只能手写堆!
代码实现:
建堆 -> 加入&弹出 -> 弹出剩余
三、比较器
例:
准备数组:
下图中,第一个参数为对应数组,第二个参数则是设置比较器。
比较器实现举例:
上图注释说明了比较器的返回规则:
- 负数——第一个参数在前
- 正数——第二个参数在前
- 0——任意在前
上图中绿色部分其实等价于:
用上图更清晰,而用一句 return 则更简洁。
同理也可依照此对其他参数进行排序,如:
作用:
减少代码量!!
四、桶排序和基数排序
特点:不是基于”比较“的排序,而是基于数据状况产生的排序!
实际使用需要根据数据状况进行定制。
1、桶排序
桶排序(Bucket Sort)是一种分配-合并型的排序算法,其基本思想是将数组元素分到有限数量的桶中,每个桶内的元素再单独排序,最后将每个桶内的元素按顺序合并成一个有序的数组。桶排序通常适用于元素均匀分布在某个范围内的情况。
桶排序的基本步骤:
-
分配: 将数据分配到若干个桶中。每个桶内的元素会根据某些规则被划分(例如根据数值范围或者其他条件)。
-
排序: 对每个桶内的元素进行排序。桶内的排序可以使用其他排序算法(如插入排序)。
-
合并: 将所有桶中的元素按顺序合并成一个最终的有序序列。
桶排序的时间复杂度:
- 最优情况: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 数组完成分片!(或者说使用计数排序的结果模拟”桶“的概念)