目录
概述
排序
- 使关键字有序
- 设计排序算法指标:时间复杂度,空间复杂度
1、算法的稳定性
- 关键字相同的元素
- 相对位置不变
- 不能衡量算法优劣,需要根据特定的场景,
2、内排外排
- 排序时数据元素在内存中
- 排序时数据元素无法同时在内存中,需要内外移动,数据太多放不下
- 外排需要关注如何读写次数最少,因为需要读进去内存,
- 内排方法:比较移动
- 基数排序:不比较
推荐一个有用的网站
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
3、 注意点
- 同一线性表不同算法排序,结果可能不同
- 链表也可以排序
- 对任意序列基于比较的排序,求最少次数应该考虑最坏的情况
- 对任意n个关键字排序的次数至少log2(n!)向上取整
2、插入排序
- 基本思想:将待排序的元素根据大小插入有序序列
- 分为:直接插入,折半,希尔
2、1直接插入
2.1.1 过程概述
- 某一时刻状态:有序:L[i]:无序,
- 要将li插入有序序列:1、查找出li在有序序列中应该插入的位置k,2、将k到i-1的元素全部后移一个。3、将li赋值到lk
- 过程:l1看作有序,从l2开始插入
- 空间复杂度为O1
2.1.2代码:
void InsertSort(Element A[],int n){
int i,j;
for(i=2;i<=n;++i){//从第二个元素开始排序,0下标做了哨兵
if(A[i].key<A[i-1].key){//i小于前驱,需要往前找,插入到合适的位置
A[0]=A[i];//带插入元素赋值为哨兵,避免后移时被覆盖了
for(j=i-1;A[0].key<A[j].key;--j)//从后往前查找带插入的位置
A[j+1]=A[j];//向后挪
A[j+1]=A[0];//赋值到插入位置
}
}
2.2.3算法注意点
- 设置排序变量i从2开始,到n结束
- 第一个if语句时比较i和i-1的大小
- 移动前要复制到哨兵
- 这里你看到了哨兵的作用,没有哨兵还需呀判断j>0
- 只有带插入元素比前面的元素小才需要移动
- 移动过程:设置移动变量j,j从i-1开始,一直到带插入的哨兵大于j对应的元素,就把j后面一个元素的位置j+1腾出来,插入
2.2.4性能分析
2.2.4.1空间效率:
仅仅使用了常数个辅助单元,和问题规模没有关系
复杂度是O1
2.2.4.1时间效率:
- 向有序表中插入元素进行了n-1次
- 最好的情况:表中元素有序,每次插入,只需对比一次关键字不用移动,时间复杂度是On
- 最坏的情况:表中元素逆序:总的比较次数i从2到n相加求和,总的移动次数:i从三到n+1求和,
- 最坏时间复杂度On2
- 平均:n的平方除以4
- 与折半插入的时间复杂度相同On2,但是对于数量小的排序表,折半插入更好
- 排序稳定,
- 适用于顺序存储和链式存储的线性表,
- 链式存储时,可以从前往后查找元素的位置
2.2折半插入排序
2.2.1思路:
- 因为前一部分已经是有序的了,所以可以使用折半查找找到带插入的位置,然后在移动元素
- 只能用顺序存储来实现,涉及到折半的都用顺序存储来实现
2.2.2步骤
- 首先使用a0保存带插入的元素,为了防止被覆盖,折半查找就没有使用这个,他是查找的关键字直接在函数中给出的
- 举例:
55(下标为0的哨兵) 20 30 40 50 60 70 80 55 60 90 10
第一次:low=1,A[low]=20,high=7,A[high]=80
第二次
high=mid-1、low=mid+1
注意:
当折半查找找到了与带插入元素相同的元素时,以往的折半查找时停了下来,但是插入排序不可以停下来,需要继续往右边查找,为什么不是左边,因为需要保证稳定性 - 当low大于high时停止折半查找,
- low就是需要插入的位置
- 将low到i-1的元素全部往右边开始移动,
2.2.3代码
//折半查找排序
void InsertSort(int A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){//依次将2到n插入到前面的有序序列中
A[0]=A[i];//将A[0]暂存到A[i];
low=1;high=i-1;//折半查找的范围
while(low<=high){
mid=(low+high)/2;
if(A[mid]>A[0])high=mid-1//带插入元素在mid的左边
else low=min+1//带插入元素在中间元素的右边
}
for(j=i-1;j>=high+1;--j){//为什么时high+1,因为最终的停止位之前就是high+1=low
A[j+1]=A[j];
A[high+1]=A[0];
2.2.4复杂度分析
关键字比较的次数少了,但是移动的次数不变,所以还是On2
2.2.5对链表的插入排序
移动的次数少了,,但是比较的次数不变,所以还是On2
2.3、希尔排序
2.3.1、基本思想
- 先追求元素的部分有序,在全局有序
- 将表拆分成几个子表
- 对各个子表进行直接插入排序
- 缩小增量d,重复上述过程
- 直到d=1结束
2.3.2、过程分析
1、初始状态
第一趟:d1=4,分成四个表,间距为4的分成一组
然后、子表进行直接插入排序
第二趟:
d2=2;相聚距离为2的继续划分成子表
插入排序得到
第三趟:d3=1
最后这个表进行插入排序
得到最后结果
过程综述
// 插入排序
void shellsort(int A[],int n){
for(int dk=n/2;dk>=1;dk=dk/2){//确定分组长度
for(i=dk+1;i<=n;++i){//每一个分组的第二个元素,和直接插入排序中从第二个元素开始插入排序时一样的原理
A[0]=A[i];//i就是带插入元素
for(int j=i-dk;j>0&&A[0]<A[j];j-=dk)//和直接插入排序一样,只不过跨度变成了dk
A[j+dk]=A[0];
}
}
2.3.3、复杂度分析
- 使用了常数个辅助单元,复杂度O1
- 时间效率:一般On1.3,最坏On2
- 稳定性:划分到不同子表时可能改变相对次序
- 实用性:仅仅适用于线性表,顺序存储