【408精华知识】速看!各种排序的大总结!_408快速排序算法-CSDN博客
(99+ 封私信 / 82 条消息) 考研408数据结构第八章------排序 - 知乎
目录
优化三---置换选择排序---减少初始归并段的个数r---生成初始归并段(大小不等的归并段)
优化四---最佳归并树(m叉哈夫曼树)---组织长度不等的初始归并段的归并顺序
比较次数与初始序列有关:快排、直接插入、冒泡、堆、希尔排序
"快速插入冒泡堆,希尔比较看初始"
比较次数与初始序列无关:归并、简单选择、基数
"归并选择基数,比较次数固定"
排序趟数与初始序列有关:冒泡"冒泡排序趟数,随序列变化"
排序趟数与初始序列无关:直接插入、折半插入、希尔、简单选择、归并、基数
"直插折半希尔选,归并基数趟固定"
不稳定:选择、希尔、堆排序、快速排序选择希尔堆快速,排序不稳定
哪些排序每一趟可以至少确定一个位置:冒泡、快排、堆排序、简单选择"泡快排堆选择,每趟定一位"
依赖随机访问的排序算法:折半插入、希尔、快速排序、堆"折半希尔快速堆,随机访问不可少"
直接插入:顺序访问和插入冒泡元素:只访问相邻元素
简单选择:顺序遍历查找最小元素
归并排序:在链式存储中非常高效(链表可以轻松分割和合并,,只需改变指针)
外部排序:顺序访问和块处理
- 直接插入排序
- 折半插入排序
- 希尔排序
- 冒泡排序
- 快速排序
- 简单选择排序
- 堆排序
- 归并排序
- 基数排序
- 外部排序
外部排序和内部排序
内部排序指排序期间元素全部存放在内存的排序
外部排序指排序期间元素无法存放在内存,必须在排序的过程中根据要求不断地在内、外存之间移动的排序
插入排序
直接插入排序
每遍历到一个元素,就拿这个元素一直向左比较,直到比左边元素小或者相等直到数组下标为0时终止
时间复杂度O(n)~O(n^2) 平均O(n^2)
空间复杂度O(1)
稳定:每次插入元素总是从后向前比较再移动,所以不会出现相同元素相对位置发生变化的情况
最好情况
最坏情况
折半插入排序
与直接插入排序唯一的区别是:直接插入排序向左比较的时候是逐个比较的,而折半插入排序是借助折半查找完成比较的
所以该算法的比较次数少一些,但由于每次循环都要把元素右移,导致直接插入排序与折半插入排序的时间复杂度差不多
时间复杂度O(n^2)
空间复杂度O(1)
稳定
希尔排序
时间复杂度O(n)~O(n^2) 平均O(n*log2n)或O(n^1.5)
空间复杂度O(1)
不稳定:当相同关键字记录被划分到不同子表时,可能会改变他们的相对次序
先将原表分割成若干子表,对各个子表进行直接插入排序
冒泡排序
"简陋版的选择排序",选择排序与冒泡唯一的区别就是,找到较大元素时不直接交换,先标记,看有没有更大的,直到循环结束才交换,因此算法效率比冒泡排序高,且是稳定
遍历所有元素,比如遍历到了第一个元素
从1到n两两比较相邻元素,若逆序就交换位置
比较完第一轮之后进行第二轮,遍历到第二个元素时,两两比较第二个元素与除第一、二元素的所有元素,若逆序则交换元素值,则第二轮遍历结束后,第二个元素上就是第二大的元素
....
时间复杂度O(n^2)
空间复杂度O(1)
稳定
最好情况
最坏情况
每个小趟移动3次,中间变量
快速排序
"分治"
在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列
时间复杂度O(nlogn)~O(n^2) 平均O(nlogn)
空间复杂度O(logn)~O(logn)
不稳定:在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左端区间后,他们的相对位置会发生变化
再分别对两个子表进行相同的操作
快速排序的"最好情况"指的是每次选择的关键字(枢轴)都能将当前序列均匀地划分为两个长度尽可能相等的子序列
最好情况
每一趟排序后都可以将记录序列均匀分割成两个长度大致相等的子表
最坏情况
初始序列有序
简单选择排序
每次循环中都取出剩余数组中最小的元素,并采用交换的策略使其交换出剩余数组(不稳定的源头)
时间复杂度O(n^2)
空间复杂度O(1)
不稳定
Best:不移动元素
Worst:移动3(n-1)
无论初始序列如何,所需比较次数均为
堆排序
堆是具有特殊性质的完全二叉树
不断地输出堆顶元素
时间复杂度:建堆O(n)、堆排序O(nlog2n)
空间复杂度:O(1)
不稳定
归并排序
思想
"分治",把两个有序表合并为一个有序表
时间复杂度O(nlog2n)
空间复杂度O(n)
稳定
基数排序
个位、十位、百位...
最高位优先(MSD)
最低位优先(LSD)
每趟排序包含两种操作,第一种是分配,第二种是收集
分配需要O(n)的时间复杂度,收集需要O(r),故每趟需要O(n+r)的时间复杂度
时间复杂度: O(d(n+r)) 其中r代表基数,d代表趟数,也代表权重
空间复杂度: O(r) 需要r个辅助队列
稳定
总结

希尔排序的最好情况与最坏情况无法判断
比较次数与序列初态有关的算法*
快速排序
冒泡排序
直接插入排序
希尔排序
堆排序
快速排序 的排序趟数就是它的递归深度。当 快排 的数据是有序时候,会退化为冒泡排序,所以快排趟数也与初始序列顺序有关了
冒泡排序:其主要优化就是记录了前一趟是否冒泡,如果没有产生冒泡就说明数组已经有序,直接
return。如果产生了冒泡,才继续执行
比较次数与序列初态无关的算法
二路归并排序
简单选择排序
基数排序
排序趟数与初态有关的算法*
冒泡排序
快速排序
都是交换排序
排序趟数与初态无关的算法
直接插入排序
折半插入排序
希尔排序
简单选择排序
归并排序
基数排序
算法稳定性
选择排序、希尔排序、快速排序、堆排序都是不稳定的
速记: 堆选希块
外部排序

将待排序的记录存储在外存上,排序时再把数据一部分一部分地调入内存进行排序,在排序过程中需要多次进行内存和外存之间的交换的方法称为外部排序。
外部排序一般使用归并算法
外部排序的总时间=内部排序所需的时间+外存信息读写的时间+内部归并所需的时间
一般来说,外存信息读写的时间远大于内部排序和内部归并的时间,因此应着力减少I/O次数。
下面我们先介绍归并方法,再介绍如何进行优化
外部排序的方法
以二路归并为例:
优化一---增加归并路数k
显然,外存信息读写的时间远大于内部排序和内部归并的时间,因此应着力减少/O次数。由于外存信息的读/写是以"磁盘块"为单位的,可知每一趟归并需进行16次"读"和16次"写",3趟归并加上内部排序时所需进行的读/写,使得总共需进32×3+32=128次读写
若改用4路归并排序,则只需2趟归并,外部排序时的总读/写次数便减至32×2+32=96。因此,增大归并路数,可减少归并趟数,进而减少总的磁盘I/O次数,
优化二---败者树---消除增加k(归并路数)带来的副作用
优化三---置换选择排序---减少初始归并段的个数r---生成初始归并段(大小不等的归并段)
优化四---最佳归并树(m叉哈夫曼树)---组织长度不等的初始归并段的归并顺序
外部排序时间 = 内部排序所需的时间+外存信息读写的时间+内部归并所需的时间理想状态下的外部排序= 较大的k路归并 + 较少的初始归并个数r
败者树(减少多路k中内部排序的时间)+置换选择排序(减少r) + 哈夫曼树(优化比较顺序)
败者树是完全二叉树
最佳归并树是哈夫曼树的一种,它是"最优的" 哈夫曼树
代码
直接插入排序
void InsertSort(ElemType A[ ],int n)
{
int i,j;
for(i=2;i<=n;i++)
if(A[i]<A[i-1])
A[0]=A[i];
for(j=i-1;A[0]<A[j];j--)
A[j+1]=A[j];
A[j+1]=A[0];
}
折半插入排序void InsertSort(ElemType A[ ],int n)
{
int i,j,low,high,mid;
for(i=2;i<=n;i++)
A[0]=A[i];
low=1;high=i-1;
while(low<=high)/2;
if(A[mid]>A[0]) high=mid-1;
else low=mid+1;
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j];
A[high+1]=A[0];
}
希尔排序
void ShellSort(ElemType A[ ],int n)
{
for(dk=n/2;dk>=1;dk=dk/2)
for(i=dk+1;i<=n;++i)
A[0]=A[i];
for(j=i-dk;j>0&&A[0]<A[j];j-=dk)
A[j+dk]=A[j];
A[j+dk]=A[0];
}
冒泡排序
void BubbleSort(ElemType A[ ],int n)
{
for(i=0;i<n-1;i++)
bool flag=false;
for(int j=n-1;j>i;j--)
if(A[j-1]>A[j])
swap(A[j-1],A[j]);
flag=true;
if(flag==false)
return;
}
快速排序
void QuickSort(ElemType A[ ],int low,int high)
{
if(low<high)
int pivotpos=Partition(A,low,high);
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
int Partition(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 SelectSort(ElemType A[ ] ,int n)
{
for(i=0;i<n-1;i++)
min=i;
for(j=i+1;j<n;j++)
if(A[j]<A[min]) min=jl
if(min!=i) swap(A[i],A[min]);
}
堆排序
void BuildMaxHeap(ElemType A[ ],int len)
{
for(int i=len/2;i>0;i--)
HeapAdjust(A,i,len);
}
void HeapAdjust(ElemType A[ ],int k,int len)
{
A[0]=A[k];
for(i=2*k;i<=len;i*=2)
if(i<len&&A[i]<A[i+1])
i++;
if(A[0]>=A[i]) break;
else
A[k]=A[i];
k=i;
A[k]=A[0];
}
void HeapSort(ElemType A[ ],int len)
{
BuildMaxHeap(A,len);
for(int i=len;i>1;i--)
swap(A[i],A[1]);
HeapAdjust(A,1,i-1);
}
归并排序
ElemType *B=(ElemType*)malloc((n+1)*sizeof(ElemType));
void Merge(ElemType A[ ],int low,int mid,int high)
{
for(int k=low;k<=high;k++)
B[k]=A[k];
for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++)
if(B[i]<=B[j])
A[k]=B[j++];
while(i<=mid) A[k++]=B[i++];
while(j<=high) A[k++]=B[j++];
}







































