update information
2019.12.19 总结 多一句口诀;补充了十大排序动画链接
总结
所属策略 | 排序策略 | 一句话总结 | 时间复杂度 | 稳定性 |
---|---|---|---|---|
插入 | 直接插入排序 | 哨兵;后面往前面插 | O(n^2) | 稳定 |
插入 | 希尔排序 | 子表排序;设置增量 | O(n^2)【最坏情况下】 | 不稳定 |
交换 | 冒泡排序 | 竖着写;底部开始依次交换 | O(n^2) | 稳定 |
交换 | 快速排序 | 分治,基准找位置 | O(n^2) | 不稳定 |
选择 | 简单选择排序 | 无序中找最小的和基准交换 | O(n^2) | 不稳定 |
选择 | 堆排序 | 大小根堆,反复调整 | O(nlog2n) | 不稳定 |
归并 | 归并排序 | 几种组合,最后合并 | O(nlog2n) | 稳定 |
基数 | 基数排序 | LSD,MSD,桶排序,筛选 | O(d(n分配 + r收集)) | 稳定 |
背诵口诀
稳定性
不稳定: 快选堆希(快速排序,选择排序,堆排序,希尔排序)
稳定: 插冒归基(插入排序,冒泡排序,归并排序,基数排序(桶排序))
具体细节
演示细节:动画展示各种排序算法
插入
插入排序
原理: 设置一个哨兵,然后往后循环挨个往前插入。
稳定: 插入前面的时候是从后往前插的肯定稳定。
void InsertSort(ElemType A[] , int n){
for(int i = 2 ; i <= n ; i ++){
if(A[i].key < A[i - 1].key){
A[0] = A[i]; // 先把数字放在A[0]里面
for(int j = i - 1 ; A[0].key < A[j].key ; j --){ // 从后往前查找,如果有数字比它大,那就把数字往后移//
A[j + 1] = A[j];
}
A[j + 1] = A[0]; // 在这个位置上把数字插进去//
}
}
}
希尔排序
原理: 分组;增量
稳定性: 分组不同,顺序不同,所以不稳定。
void ShellSort(ElemType A[] , int n){
for(int d = n / 2 ; d >= 1 ; d /= 2){ // 每次d的数值都是原先的1/2,但是最后一次必须得是1
for(int i = d + 1 ; i <= n ; i ++){ // 依次循环
if(A[i].key > A[i - d].key){
A[0] = A[i]; // 相当于一个temp
int j;
for(j = i - d ; j > 0 && A[0].key < A[j].key ; j -= d){ // 确定移动的范围和长度
A[j + d] = A[j];
}
A[j + d] = A[0]; // 多减了一个d的值
}
}
}
}
交换
冒泡排序
原理: 竖着写,底部累次交换.
稳定性: 底部交换,满足条件才换,所以稳定。
void BubbleSort(ElemType A[] , int n){
for(int i = 0 ; i < n - 1 ; i ++){ // 只用循环n-1次,每次必有一个已经有序
bool flag = false;
for(int j = n - 1 ; j > i ; j --){ // [a , b]的数字个数是 b - a + 1
if(A[j - 1].key > A[j].key){
ElemType temp = A[j].key;
A[j].key = A[j - 1].key;
A[j - 1].key = temp;
flag = true; // 出现过变动
}
}
if(flag == false) // 未出现过变动,则已经有序
return ;
}
}
快速排序
原理: 分治思想
稳定性: 中间涉及到交换,肯定不稳定。
必看文章:【普及一下】【实例讲解】
int Part(ElemType A[] , int low , int high){
ElemType pivot = A[low];
while(low < high){
while(low < high && A[high] >= pivot){
high --; // 从右边开始,碰到比轴小的就和轴换位置
}
A[low] = A[high];
while(low <high && A[low] <= pivot){
low ++; // 从左边开始,碰到比轴大的就和轴换位置
}
A[high] = A[low];
}
A[low] = pivot; // 当轴位置确定好之后,再去将数字放进去
return low;
}
void QuickSort(ElemType A[] , int low , int high){
int p = Part(A , low , high);
QuickSort(A , low , p - 1); // 前半部分
QuickSort(A , p + 1 , low); // 后半部分
}
选择
简单选择
原理: 无须列表中找有序的往前依次交换。
稳定性: 这个地方是交换,使得可能会把同样的换回来。(交换的时候不检查)
void SelectSort(ElemType A[] , int n){
for(int i = 0 ; i < n - 1 ; i ++){ // 与冒泡排序类似,最后一个值最后自然可以确定
min = i;
for(int j = i + 1 ; j < n ; j ++){ // 从当前排序的后面找到一个最小值进行交换
if(A[j] < min)
min = j; // 先将下标保存
}
if(min != i) // 如果不是当前的数值,则swap
swap(A[i] , A[min]);
}
}
堆排序
原理: 作业调度,自下往上,检查从上至下。
特点: 堆排序最坏的情况下,时间复杂度也是O(nlogn)。而且对排序仅需要一个记录大小提供交换的辅助存储空间。
归并
归并排序
原理: 几组之间,互相组合,最后排序。
稳定性: 合并的时候顺序合并,使得可以稳定。
ElemType *B = (ElemType *)malloc((n + 1) * sizeof(*ElemType));
void Merge(ElemType A[] , int low , int mid , int high){
for(int i = 0 ; i < high ; i ++){ // 将所有的数据放在这个辅助数组中
B[i] = A[i];
}
for(int i = low , j = mid + 1 , k = i ; i <= mid && j <= high ; k ++){ // i和j分头开始搜索,当比另外一个大的时候就切换
if(B[i] <= B[j]){
A[k] = B[i ++];
}
else{
A[k] = B[j ++];
}
}
// 未填满的部分赋值下来
while(i <= mid) A[k ++] = A[i ++];
while(j <= high) A[k ++] = A[j ++];
}
void MergeSort(ElemType A[] , int low , int high){
if(low < high){
int mid = (low + high) / 2;
MergeSort(A , low , mid); // 先对前半部分做排序
MergeSort(A , mid + 1 , high); // 后半部分做排序
Merge(A , low , mid , high); // 再将两个部分合并
}
}