参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure
1.直接插入排序
1.1基本思想
一趟直接插入排序的基本思想: 将记录L.r[i]插入到有序子序列L.r[1..i-1]中,使记录的有序序列从L.r[1..i-1]变为L.r[1..i]。 完成这个“插入”分三步进行:
1.查找L.r[i]在有序子序列L.r [1..i-1]中的插入位置j;
2.将L.r [j..i-1]中的记录后移一个位置;
3.将L.r [i]复制到L.r [j]的位置上。
整个排序过程进行n–1趟插入,即:先将序列中的第1个记录着成一个有序的子序列,然后从第2个记录起逐个插入,直至整个序列变成接关键字非递减有序序列为止。
1.2代码实现
//package sort.insertionSort;
public class StraightSort {
/**
* @param args
*/
//对顺序表L做直接插入排序
public static void InsertSort(int[] L){
//先将第一个元素看成是一个有序子序列
for(int i = 2; i <= L.length-1; i++){
//在已经有序的1->i-1的子序列中插入第i个元素,以保证仍然有序,成为一个1->i的有序子序列
L[0] = L[i];//监视哨
int j = i-1;
/*
for(; j > 0; j--){//没有利用监视哨,仍然用j>0作为条件以避免数组下标越界
if(L[0] < L[j])
L[j+1] = L[j];
else
break;
}
*/
for(; L[0] < L[j]; j--)
L[j+1] = L[j];//利用监视哨
//当L[0] >= <[j]时跳出循环,由于j做了一次自减,所以是L[j+1] = L[0],
//当是因为=而跳出循环时(j后来没有自减),L[0]插在L[j]的后面以保证了“稳定”
L[j+1] = L[0];
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 53, 27, 36, 15, 69, 42}; //0号单元未使用
InsertSort(test);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
运行结果:
1.3性能分析
直接插入排序性能分析 ,实现排序的基本操作有: (1)“比较” 关键字的大小 (2)“移动”记录
对于直接插入排序:
最好情况“比较”次数:n-1;“移动”次数:2(n-1)
最坏的情况“比较”和“移动”的次数均达到最大值,分别为:(n+2)(n-1)/2;(n+4)(n-1)/2
由于待排记录序列是随机的,取上述二值的平均值。所以直接插入排序的时间复杂度为 O(n^2)。
直接插入排序是“稳定的”:关键码相同的两个记录,在整个排序过程中,不会通过比较而相互交换。
2.折半插入排序
2.1基本思想
考虑到 L.r[1..i-1] 是按关键字有序的有序序列,则可以利用折半查找实现“ L.r[1…i-1]中查找 L.r[i] 的插入位置”如此实现的插入排序为折半插入排序。折半插入排序在寻找插入位置时,不是逐个比较而是利用折半查找的原理寻找插入位置。待排序元素越多,改进效果越明显。
2.2代码实现
//package sort.insertionSort;
public class BinaryInsertionSort {
/**
* @param args
*/
//对顺序表L做折半插入排序,利用折半查找快速找到要插入的位置
public static void binaryInsertSort(int[] L){
for(int i = 2; i <= L.length-1; i++){
//利用折半查找找到要插入的位置
int low = 1, high = i-1;//在1->i-1的有序子序列中插入第i个元素,使之成为1->i的有序子序列
L[0] = L[i];//暂存要插入的元素
while(low <= high){
int mid = (low+high)/2;
if(L[0] < L[mid])
high = mid -1;
else
//L[0] >= L[mid]
low = mid+1;//等于当成大于处理,这样后出现的相等值就会排在后面,从而到达“稳定”
}
//此时high = low-1,且high+1即low的位置即为要插入的位置
for(int j = i-1; j >= low; j--)
L[j+1] = L[j];
L[low] = L[0];
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 53, 27, 36, 15, 69, 42}; //0号单元未使用
binaryInsertSort(test);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
运行结果:
2.3性能分析
折半插入排序减少了关键字的比较次数,但记录的移动次数不变,其时间复杂度与直接插入排序相同,时间复杂度为O(n^2) 。折半插入排序是“稳定的”。
3.希尔排序(缩小增量排序)
3.1基本思想
希尔排序(Shell Sort)又称为“缩小增量排序”。其基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
3.2代码实现
//package sort.insertionSort;
public class ShellSort {
/**
* @param args
*/
//对顺序表L做一趟希尔排序,本算法和一趟直接插入排序相比,做了如下修改:
//1.前后记录位置的增量式dk,而不是1
//2.L[0]只是暂存单元,而不再是哨兵
public static void shellInsert(int[] L, int dk){
/*对于下面for循环的i = i+dk和i++的分析:
一趟希尔排序里有L.length/dk个子序列,每个子序列要进行直接插入排序,即要进行L.length/dk个直接插入排序
子序列轮着来(有点并发的感觉),即第一个子序列的第2个数排完序,然后是第2个子序列的第2个数排序,然后是第3个子序列。。。
i继续自增,然后是第1个子序列的第3个数往第一个子序列的1->2的有序子序列里插入并排序,然后是第2个子序列的第3个数
往第2个子序列的1->2的有序子序列里插入并排序,然后是第3个子序列的第3个数往第3个子序列的1->2的有序子序列里插入并排序。。。
i继续自增,然后是第1个子序列的第4个数往第一个子序列的1->3的有序子序列里插入并排序,接着
第2个子序列的第4个数往第2个子序列的1->3的有序子序列里插入并排序.。。。。。以此类推,直到所有的完成
*/
//相当于每个子序列的第一个数都被看成是每个子序列的有序子序列
for(int i = 1+dk; i <= L.length-1; i++){
L[0] = L[i];
//找L[i]应该插入的位置
int j = i-dk;
for(; j>0&&L[0]<L[j]; j-=dk)
L[j+dk] = L[j];
L[j+dk] = L[0];
}
}
//按增量dlta[0......len(dlta)-1]对顺序表L做希尔排序
public static void shellSort(int[] L, int[] dlta){
for(int i = 0; i < dlta.length; i++)
shellInsert(L, dlta[i]);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] test = {0, 65, 49, 97, 25, 25, 13}; //0号单元未使用
int[] dlta = {3, 2, 1};//应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。
shellSort(test, dlta);
for(int i = 1; i <= test.length-1; i++)
System.out.print(test[i]+" ");
}
}
运行结果:
3.3性能分析
虽然我们给出的算法是三层循环,最外层循环为log2n数量级,中间的for循环是n数量级的,内循环远远低于n数量级,因为当分组较多时,组内元素较少;此循环次数少;当分组较少时,组内元素增多,但已接近有序,循环次数并不增加。因此,希尔排序的时间复杂性在O(nlog2n)和O(n^2 )之间,大致为O(n^1. 3)。
希尔排序的时间复杂度较直接插入排序低。希尔排序的分析是一个复杂的问题,因为它的时间是和所取“增量”序列的函数密切相关。到目前为止,还没有求得一种最好的增量序列,但有大量的局部结论。
注意:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。
由于希尔排序对每个子序列单独比较,在比较时进行元素移动,有可能改变相同排序码元素的原始顺序,因此希尔排序是不稳定的。
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure