🐣 测试用例
#include<iostream>
#include<vector>
using namespace std;
// 省略排序算法...
void output(vector<int>& array) {
for (int i = 0; i < array.size(); i++) {
cout << array[i] << " ";
}
cout << "\n";
}
int main() {
vector<int> array = { 49,38,65,97,76,13,27 };
output(array);
// 这里调用排序算法...
output(array);
return 0;
}
插入排序
直接插入排序
- 假设
array[0]~array[i-1]已经排好 - 比较
array[i]与其前面的元素, 边比较边后移元素(即把array[i]插入到array[0]和array[i]之间)
void insertSort(vector<int>& array) {
int temp;
// 假设 array[0] 已经排好
for (int i = 1; i < array.size(); i++) {
// 依次将 array[1]~array[n] 插入前面已排好序列
if (array[i] < array[i - 1]) {
temp = array[i];
// 从前一位开始后移元素
int j = i - 1;
// 直到 array[j] 比 temp 小才停止
while (j >= 0 && array[j] > temp) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = temp;
}
}
}
空间复杂度:
时间复杂度:
最好的情况下(本来就 有序 , 只进行 次比较):
因为
array[0]~array[i-1]是有序的, 因此array[i]与array[i-1]比较一次就可以知道是否需要插入
最坏的情况下(完全逆序, 需进行 次比较, 移动 次)
折半插入排序
- 假设
array[0]~array[i-1]已经排好 - 在序列
array[0]~array[i-1]中使用折半查找 - 找到
array[i]插入位置high+1 - 把
high+1后面的元素后移
void insertSort(vector<int>& array) {
int temp, low, high, mid;
// 假设 array[0] 已经排好
for (int i = 1; i < array.size(); i++) {
temp = array[i];
// 在 array[0] ~ array[i-1] 中折半查找
low = 0; high = i - 1;
while (low <= high) {
mid = (low + high) / 2;
if (array[mid] > temp) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
// 找到位置(hegh + 1)后把元素后移一位
for (int j = i - 1; j >= high + 1; j--) {
array[j + 1] = array[j];
}
array[high + 1] = temp;
}
}
只能减少找元素位置时的比较次数
不能减少移动次数, 因此时间复杂度:
希尔排序
不稳定排序
按照一定的步长 dk 将数组分成 ⌈n/dk⌉ 组, 并按照步长 dk 进行直接插入排序, 随后逐渐减小 dk , 最终 dk=1, 整个数组变成一组
void shellSort(vector<int>& array) {
int temp, len = array.size();
for (int dk = len / 2; dk >= 1; dk = dk / 2) {
// 直接插入排序
for (int i = dk; i < len; i++) {
// 按步长 dk 进行排序
if (array[i] < array[i - dk]) {
temp = array[i];
int j = i - dk;
while (j >= 0 && array[j] > temp) {
array[j + dk] = array[j];
j -= dk;
}
array[j + dk] = temp;
}
}
}
}
空间复杂度:
温馨提示☃️
按照理论来说, 应该是下面这种代码, 但是存在 4 层循环, 不够优雅
void shellSort(vector<int>& array) {
int temp, len = array.size();
for (int dk = len / 2; dk >= 1; dk = dk / 2) {
// 共有 dk 列需要直接插入排序
for(int j = 0; j < dk; j++) {
// 直接插入排序, 这里步长是 dk
for (int i = dk + j; i < len; i += dk) {
// 按步长 dk 进行排序
if (array[i] < array[i - dk]) {
temp = array[i];
int j = i - dk;
while (j >= 0 && array[j] > temp) {
array[j + dk] = array[j];
j -= dk;
}
array[j + dk] = temp;
}
}
}
}
}
交换排序
冒泡排序
从后往前, 比较相邻的两个数, 如果 array[j] 小于 array[j-1] , 就交换两个数, 一次“冒泡”结束后, 最小的数会移到最左边
void bubbleSort(vector<int>& array) {
int len = array.size();
int temp;
for (int i = 0; i < len - 1; i++) {
bool flag = false;
// 最后一个元素移动到正确位置
for (int j = len - 1; j > i; j--) {
if (array[j - 1] > array[j]) {
// 交换两个数
temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
flag = true;
}
}
// 如果没有发生交换, 说明已经有序
if (flag == false) return;
}
}
空间复杂度:
时间复杂度:
最好的情况下(数组有序, 只比较 次就退出, 不交换):
最坏的情况下(数组逆序):
需要比较 次, 每次交换 3 次, 共交换 次, 故时间复杂度:
平均时间复杂度:
快速排序
以数组中一个数为基准(又称轴点), 将数组分为左右两部分, 左边全部小于基准, 右边全部大于基准, 再对左右两边进行快速排序, 直到左右两边只剩一个数
分治法(递归实现)
每次递归结束, 能保证一个元素(轴点)处于正确位置
void quickSort(vector<int>& array, int low, int high) {
if (low < high) {
int i = low, j = high,
x = array[low]; // 基准
while (i < j) {
// 从后往前, 大于基准就跳过
while (i < j && array[j] >= x) {
j--;
}
if (i < j) array[i++] = array[j];
// 从前往后
while (i < j && array[i] < x) {
i++;
}
if (i < j) array[j--] = array[i];
}
array[i] = x;
// 递归对左右两部分进行快速排序
quickSort(array, low, i - 1);
quickSort(array, i + 1, high);
}
}
非递归(栈实现)
#include<stack>
// 记录左右子序列位置的类
struct Record {
int left;
int right;
Record(int left, int right) : left(left), right(right) {};
};
void quickSort(vector<int>& array) {
stack<Record> s;
s.push(Record(0, array.size() - 1));
while (!s.empty()) {
// 获取边界
// i, j 要变化
// low, high 不能变, 用于子序列定位
int i, low;
i = low = s.top().left;
int j, high;
j = high = s.top().right;
s.pop();
int x = array[i];
while (i < j) {
// 从后往前, 大于基准就跳过
while (i < j && array[j] >= x) {
j--;
}
if (i < j) array[i++] = array[j];
// 从前往后
while (i < j && array[i] < x) {
i++;
}
if (i < j) array[j--] = array[i];
}
array[i] = x;
// 不能越界
if (i - 1 > low) s.push(Record(low, i - 1));
if (i + 1 < high) s.push(Record(i + 1, high));
}
}
空间复杂度:
最好情况
最坏情况
时间复杂度:
最好情况
满足递推公式
表示每次划分都是对半划分, 表示每次都要进行 次比较确定轴点的实际位置
最坏情况
满足递推公式
若序列本来就有序, 这样扫描全部序列后就会分成一个元素为
0个一个元素为n-1个的两个分组, 同样分组前需要进行 次比较
选择排序
简单选择排序
从左往右遍历, 每次都找一个最小值与 array[i] 交换
void selectSort(vector<int>& array) {
int len = array.size(), temp;
for (int i = 0; i < len - 1; i++) {
int min = i; // 最小元素位置
// 往后查找最小元素
for (int j = i + 1; j < len; j++) {
if (array[j] < array[min]) min = j;
}
// 如果后面存在最小元素, 就交换
if (min != i) {
temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
}
堆排序
一维数组可以看作一颗二叉树
初始 i=3, 判断子节点 2 × i + 1 = 7 超过数组范围, 直接跳过
第二次 i = i - 1 = 2 , 父节点与最大子节点比较, 大于子节点, 直接跳过
第三次 i=1, 发现小于子节点, 交换
继续判断子节点的子节点, 即 k=i, i = 2 × i + 1, 发现超过数组范围了, 直接跳过
第四次 i=0, 小于子节点, 交换
子节点小于其子节点, 交换
完成大根堆的构建
从最后一个开始, 与第一个交换, 再重新调整为大根堆, 这样最大值就跑到后面去了
void headAdjust(vector<int>& array, int k, int len) {
int i = 2 * k + 1; // 第一个子节点索引
while (i <= len) {
// 比较两个子节点, 选最大的
if (i < len && array[i] < array[i + 1]) {
i++;
}
// 父节点大于子节点, 退出循环
if (array[k] >= array[i]) break;
else {
// 子节点和父节点交换
int temp = array[k];
array[k] = array[i];
array[i] = temp;
// 继续交换子节点
k = i;
i = 2 * i + 1;
}
}
}
// 建立大根堆
void buildMaxHeap(vector<int>& array) {
for (int i = array.size() / 2; i >= 0; i--) {
headAdjust(array, i, array.size() - 1);
}
}
// 堆排序
void heapSort(vector<int>& array) {
buildMaxHeap(array);
for (int i = array.size() - 1; i > 0; i--) {
int temp = array[i];
array[i] = array[0];
array[0] = temp;
// 重新建堆
headAdjust(array, 0, i - 1);
}
}
时间复杂度
归并排序
// 合并两个有序数组(这里方法不唯一, 甚至空间复杂度O(1)的也有)
void merge(vector<int>& array, int low, int mid, int high) {
// 临时数组
vector<int> temp(array.size());
for (int k = low; k <= high; k++) {
temp[k] = array[k];
}
// 将数组分成两段
int i = low, j = mid + 1, k = i;
for (; i <= mid && j <= high; k++) {
// 比较临时数组的两段数据, 谁小放入数组中
if (temp[i] <= temp[j]) {
array[k] = temp[i++];
}
else {
array[k] = temp[j++];
}
}
// 剩下的没放入的直接放入(因为本来就是有序的)
while(i <= mid) array[k++] = temp[i++];
while(j <= high) array[k++] = temp[j++];
}
// 归并排序
void mergeSort(vector<int>& array, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
// 递归到 low = high + 1 停止(即两两排序)
mergeSort(array, low, mid);
mergeSort(array, mid + 1, high);
merge(array, low, mid, high);
}
}
非递归版本
void mergeSort(vector<int>& o) {
for (int step = 2; step < o.size(); step *= 2) {
for (int i = 0; i < o.size(); i += step) {
merge(o, i, i + step - 1);
}
}
merge(o, 0, o.size()); // 最后再归并一次
}
时间复杂度
最好最坏均为
与快速排序相比, 归并排序完全满足递推公式
空间复杂度