文章中其实有很多图来帮助理解,但是因为外链的原因,我电脑上的图不能直接拉过来,要完整版的可以评论我直接发PDF版本。因为笔记的原因没有英文注释代码,个人笔记,仅供参考。
排序
排序
排序算法的评估指标
算法的稳定性:若待排序表中有两个元素 R i R_i Ri和 R j R_j Rj,其对应的关键字相同即 k e y i = k e y j key_i = key_j keyi=keyj,且在排序前 R i R_i Ri在 R j R_j Rj的前面,若使用某一排序算法排序后, R i R_i Ri仍然在 R j R_j Rj的前面,则称这个排序算法时稳定的,否则称排序算法是不稳定的。
排序算法的分类
- 内部排序:数据都在内存中——关注如何使算法时间、空间复杂度更低。
- 外部排序:数据太多,无法全部放入内存——还要关注如何使读/写磁盘次数更少。
插入排序
算法思想:每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成。
//直接插入排序
void InsertSort(int A[], int n){
int i, j, temp;
for(i=1; i<n; i++) //将各元素插入已排好序的序列中
if(A[i] < A[i-1]){
//若A[i]关键字小于前驱
temp = A[i]; //用temp暂存A[i]
for(j=i-1; j>=0 && A[j]>temp; --j) //检查所有前面已排好序的元素
A[j+1] = A[j]; //所有大于temp的元素都向后挪位
A[j+1] = temp; //复制到插入位置
}
}
算法效率分析
空间复杂度 O ( 1 ) O(1) O(1)
时间复杂度 O ( n 2 ) O(n^2) O(n2)
算法稳定性:稳定
优化——折半插入排序
思路:先用折半查找找到应该插入的位置,再移动元素
当 l o w > h i g h low > high low>high时,折半查找停止,应将 [ l o w , i − 1 ] [low,\ i-1] [low, i−1]内的元素全部右移,并将 A [ 0 ] A[0] A[0]复制到 l o w low low所指位置。
当 A [ m i d ] = = A [ 0 ] A[mid] == A[0] A[mid]==A[0]时,为了保证算法的“稳定性”,应继续在 m i d mid mid所指位置右边寻找插入位置。
//折半插入排序
void InsertSort(int A[], int n){
for i, j, low, high, mid;
for(i=2; i<=n; i++){
//依次将A[2]~A[n]插入前面的已排列序列
A[0] = A[i]; //将A[i]暂存到A[0]
low = 1; //设置折半查找的范围
high = i-1;
while(low <= high){
//折半查找(默认递增有序)
mid = (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]; //插入操作
}
}
希尔排序(Shell Sort)
先追求表中元素部分有序,再逐渐逼近全局有序。
先将待排序表分割成若干形如 L [ i , i + d , i + 2 d , . . . , i + k d ] L[i, i+d, i+2d,..., i+kd] L[i,i+d,i+2d,...,i+kd]的“特殊”子表,对各个子表分布进行直接插入排序。缩小增量 d d d,重复上述过程,直到 d = 1 d=1 d=1为止。(每次将增量缩小一半)
//希尔排序
void ShellSort(int A[i], int n){
int d, i, j;
//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(d=n/2; d>=1; d=d/2) //步长变化
for(i=d+1; i<=n; ++i)
if(A[i] < A[i-d]){
//需将A[i]插入有序增量子表
A[0] = A[i]; //暂存再A[0]
for(j=i-d; j>0 && A[0]<A[j]; j-=d)
A[j+d] = A[j]; //记录后移,查找插入的位置
A[j+d] = A[0]; //插入
}//if
}
算法性能分析
时间复杂度:和增量序列 d 1 , d 2 , d 3 . . . d_1,d_2,d_3... d1,d