一、总结
稳定排序:
冒泡排序、插入排序、桶排序、计数排序、归并排序、基数排序;
不稳定排序:
选择排序、希尔排序、堆排序、快速排序
分治:
分而治之,将大问题分为多个相同的小问题,一般通过递归来实现。
二、 算法详细
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int nums[N], help[N];
int n;
1. 归并排序
/*归并排序 - 分治思想,左右数组都是排好序的 然后再合并、统一排序
可以用归并思想求解逆序对问题
时间复杂度 最好 平均 最坏 O(nlogn)
空间复杂度 O(n)
稳定的非原地排序
采用分治的思想
1. 将数组一分为二,分别把左右数组排序好;
2. 然后再将左右两个数组合并成一个新的数组。
*/
void mergeSort(int l, int r) {
if(l >= r) return ;
int mid = l+r>>1;
int i = l, j = mid+1, cnts = 0;
mergeSort(l, mid), mergeSort(mid+1, r);
while(i <= mid && j <= r) {
help[cnts++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
}
while(i <= mid) help[cnts++] = nums[i++];
while(j <= r) help[cnts++] = nums[j++];
for(int i = 0; i < cnts; ++i) {
nums[l++] = help[i];
}
}
2. 快速排序
/* 快速排序 —l,j 有序 j+1, r 中 j左边的数都小于等于nums[j]右边的都大于等于nums[j]
可以用快速排序的思想寻找数组中第k大/小的数
时间复杂度 最好 平均 O(nlogn) 最坏O(n^2)
空间复杂度 O(logn)
不稳定的原地排序
1. 基准值:利用分治的思想,先在数组中取一个基准pivot;
2. 分割:根据基准值重新排序数组;
3. 递归:递归排序子数组,递归结束条件为判断子列数组大小是0/1
*/
void quickSort(int l, int r){
if(l >= r) return;
int x = nums[l+r>>1], i = l-1, j = r+1;
while(i < j){
do i++; while(x > nums[i]);
do j--; while(x < nums[j]);
if(i < j)
swap(nums[i], nums[j]);
}
quickSort(l, j), quickSort(j+1, r);
}
3. 选择排序
/*选择排序 - 每轮都选择剩余元素中 最小的 和 第一个 进行交换,不会发生元素移动
时间复杂度 最好 最坏 平均 O(n^2)
空间复杂度 O(1)
不稳定的原地排序
1. 将数组分为已排序、未排序;
2. 不断地将未排序区间中最小的元素放入已排序区间的尾部。
*/
void selectSort() {
for(int i = 0; i < n-1; ++i) {
int cur_min = i;
for(int j = i+1; j < n; ++j) {
if(nums[j] < nums[cur_min]) {
cur_min = j;
}
}
swap(nums[i], nums[cur_min]);
}
}
4. 插入排序
/*插入排序 - 待序元素插入有序元素 因此需要在搜索位置的同时 后移有序元素
与选择排序不同的是,属于数据移动,而非数据交换。
时间复杂度 最好 O(n)最坏平均 O(n^2)
空间复杂度 O(1)
可以是稳定的原地排序
1. 将数组分为已排序、未排序两个动态区间;
2. 将未排序区间中数据插入到已排序区间中,会涉及到数据的移动。
*/
void insertSort() {
//从i开始是默认i == 0已经是有序
for(int i = 1; i < n; ++i) {
auto value = nums[i];//必须备份 因为会覆盖
int j = i-1;
while(j >= 0 && nums[j] > value) {
nums[j+1] = nums[j];
--j;
}
nums[j+1] = value;
}
}
5. 冒泡排序
/*冒泡排序 - 实际上是每轮就确定一个最大的元素(如果按照从小到大排列)
时间复杂度 最好 O(n)最坏平均 O(n^2)
空间复杂度 O(1)
可以是稳定的原地排序
1. 比较相邻两个元素的大小,不满足排序要求则交换。
*/
void bubbleSort() {
for(int i = 0; i < n-1; ++i) {
bool flag = false;//这样写在最佳情况下的时间复杂度才会是O(n)
for(int j = 0; j < n-i-1; ++j) {//优化
if(nums[j] > nums[j+1]) {
swap(nums[j], nums[j+1]);
flag = true;
}
}
if(!flag) break;
}
}
/*
基于元素之间的比较和交换被称为比较类排序。
nlogn时间复杂度的算法称为非线性时间比较类排序;
接下来介绍线性时间比较类排序。
*/
6. 计数排序
/*
计数排序 - 适合数据比较集中的排序,将元素转换成键值
不是基于比较的排序算法
时间复杂度 最好 O(n+k)
空间复杂度 O(k)
稳定的非原地排序
1. 其核心在于将输入的数据值转化为键存储在额外开辟的数组空间里;
2. 在数组空间中记录出现的次数,并对所有计数进行累加;
3. 反向填充数组,按照频数取出,每取出一个元素就将count[i]-1。
个人理解实际上就是 对应第k个就放到下标为k-1的位置
*/
void countSort() {
auto max_value = *max_element(nums, nums+n);
auto min_value = *min_element(nums, nums+n);
vector<int> count(max_value - min_value + 1);
for(int i = 0; i < n; ++i) {
++count[nums[i]-min_value];
}
/*这样写就结束的话 无法保证数据的稳定性 因为覆盖原数组
int indx = 0;
for(int i = 0; i < count.size(); ++i) {
for(int j = 0; j < count[i]; ++j) {
nums[indx++] = i + min_value;
}
}
*/
//因此 -> 从后面 反向填充数组:相等的先填充后面的,然后减少计数值
for(int i = 1; i < count.size(); ++i) {
count[i] = count[i-1] + count[i];
}
int res[n];
for(int i = n-1; i >= 0; --i) {
auto cnts = count[nums[i]-min_value];
res[cnts-1] = nums[i];
--count[nums[i]-min_value];
}
memcpy(nums, res, sizeof res);
}
7. 基数排序
/*基数排序 - 适合给位数较多的数字进行排序 (最大数字多少位就只需要准备几个桶)
适合手机号、较长的英文专业名词排序
时间复杂度:最好最坏平均 O(n*k)
空间复杂度:O(n)
非原地稳定排序
1. 找到数组中最大数字位数,即最大位数bits;
2. 初始化count存储余数0~9的桶;
3. 运用计数排序思想对数组进行排序,循环bits次;
*/void radixSort() {
auto max_value = *max_element(nums, nums+n);
int bit = 0, tmp = max_value;
while(tmp) {
tmp /= 10;
++bit;
}
int radix = 1;
vector<int> count(10);
int res[n];
for(int i = 1; i <= bit; ++i) {
memset(res, 0, sizeof res);
for(int j = 0; j < n; ++j) {
++count[nums[j]/radix%10];
}
for(int j = 1; j < 10; ++j) {
count[j] = count[j-1] + count[j];
}
int indx = 0;
for(int j = n-1; j >= 0; --j) {
int cnts = count[nums[j]/radix%10];
res[cnts-1] = nums[j];
--count[nums[j]/radix%10];
}
memcpy(nums, res, sizeof res);
radix *= 10;
}
}
8. 桶排序
/*桶排序 - 每个桶内部排序后再统一排序
如果桶的个数 等于了数组元素的个数,那么他就是计数排序。
如果数组元素为n,m个桶,那么每个桶的元素个数k=n/m,如果桶内使用快速排序;
时间复杂度 O(nlog(n/m)),具体需要开桶的个数,最好平均 O(k) 最坏O(n^2)
空间复杂度 O(k)
非原地排序 具体稳定性要看使用的内部算法
1. 整体有序:将数组分配到有限量的桶中;
2. 局部有序:对每个桶进行排序;
*/void bucketSort() {
int k = 10;
auto max_value = *max_element(nums, nums+n);
auto min_value = *min_element(nums, nums+n);
int bucket_num = (max_value - min_value) / k + 1;
vector<int> bucket[bucket_num];
for(int i = 0; i < n; ++i) bucket[(nums[i]-min_value)/k].emplace_back(nums[i]);
for(int i = 0; i < bucket_num; ++i) sort(bucket[i].begin(), bucket[i].end());
int indx = 0;
for(int i = 0, j = 0; i < bucket_num; ) {
if(j < bucket[i].size()) nums[indx++] = bucket[i][j++];
else i++, j = 0;
}
}
/* 第二种写法
void bucketsort(vector<int>& arr) {
queue<int> buckets[10];
for (int digit = 1; digit <= 1e9; digit *= 10) {
for (int elem : arr) {
buckets[(elem / digit) % 10].push(elem);
}
int idx = 0;
for (queue<int>& bucket : buckets) {
while (!bucket.empty()) {
arr[idx++] = bucket.front();
bucket.pop();
}
}
}
作者:Coderoger
链接:https://www.acwing.com/blog/content/2064/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
9. 堆排序
/*堆排序 - 和别的不一样 小标从1开始方便维护,每次都维护堆,将维护完成的放到数组末尾
时间复杂度 最好最坏平均 O(nlogn)
空间复杂度 O(1)
不稳定的原地排序
堆总是一个完全二叉树,
任意节点都必须大于等于或者小于等于它的子节点
(分别对应大顶堆、小顶堆
一、建堆:
1. 将数组抽象为一个堆,从下标0~n-1从完全二叉树的根节点开始对应;
2. 从下标n/2位置不断的向前移动,交换元素直至满足完全二叉树定义;
二、维护堆:
3. 堆顶元素就是排序结果,因此每次将堆顶元素与最后一个元素进行交换,
即可取出堆顶元素,同时sz--,再down操作维护堆。
*/
void down(int u, int sz) {
int tmp = u;
while(2*u <= sz && nums[2*u] < nums[tmp]) tmp = 2*u;
while(2*u+1 <= sz && nums[2*u+1] < nums[tmp]) tmp = 2*u+1;
if(tmp != u) {
swap(nums[tmp], nums[u]);
down(tmp, sz);
}
}
void up(int u) {
while(u/2 && nums[u/2] > nums[u]) {
swap(nums[u/2], nums[u]);
u /= 2;
}
}
void heapSort() {
//建堆
for(int i = n/2; i; --i) down(i, n);
for(int i = n; i > 1; --i) {
swap(nums[1], nums[i]);
down(1, i-1);
}
}
10. 希尔排序
/*希尔排序 - 在插入排序一步步移动的基础上优化步长
时间复杂度 最好O(nlogn) 最坏平均 O(nlogn*logn)
空间复杂度 O(1)
不稳定的原地排序
*/
void shellSort() {
for(int gap = n/2; gap; gap /= 2) {
for(int i = gap; i < n; ++i) {
int tmp = nums[i], j = i;
for( ; j >= gap && tmp < nums[j-gap]; j -= gap) {
nums[j] = nums[j-gap];
}
nums[j] = tmp;
}
}
}