目录
1,插入排序思想
插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按其关键字大小插入到前面己排好序的子序列中,直到全部记录插入完成。由插入排序的思想可以引申出三个
重要的排序算法:直接插入排序、折半插入排序和希尔排序。
2,直接插入排序
2.1,直接插入排序
根据上面的插入排序思想,不难得出一种最简单也最直观的直接插入排序算法。假设在排序过程中,待排序表L [1...n ] 在某次排序过程中的某一时刻状态如下:
有序序列L[1,2.........i-1] | L(i) | 无序序列:L[i+1..........n] |
直接插入排序过程:要将元素L( i ) 插入到己有序的子序列L [ 1... i -1 ] 中,需要执行以下操作(为避免混淆, 下面用L [] 表示一个表, 而用L() 表示一个元素):
- 查找出L (i) 在L [1... i - 1] 中的插入位置k 。
- 将L [ k. . .i -1] 中的所有元素依次后移一个位置。
- 将L (i) 复制到L (k) 。
为了实现对L [1 .. 口]的排序, 可以将L( 2 ) ~ L( n)依次插入到前面己排好序的子序列中,初始L [1] 可以视为是一个己排好序的于序列。上述操作执行n-1次就能得到一个有序的表。插入
排序在实现上通常采用就地排序(空间复杂度为0(1)),因而在从后向前的比较过程中,需要反复把己排序元素逐步向后挪位,为新元素提供插入空间。下面图片展示了插入排序的过程。
2.2,直接插入排序性能分析
- 空间性能分析:
- 空间效率:仅使用了常数个辅助单元,因而空间复杂度为0(1)。
- 时间复杂度分析:
- 时间效率: 在排序过程中,向有序子表中逐个地插入元素的操作进行了n-1 趟,每趟操作都分为比较关键字和移动元素, 而比较次数和移动次数取决于待排序表的初始状态。
- 在最好情况下,表中元素已经有序,此时每插入一个元素,都只需比较一次而不用移动元素,因而时间复杂度为O(n) 。
- 在最坏情况下,表中元素顺序刚好与排序结果中的元素顺序相反(逆序),总的比较次数达到最大,为
,总的移动次数也达到最大,为
。
- 平均情况下,考虑待排序表中元素是随机的,此时可以取上述最好与最坏情况的平均值作为平均情况下的时间复杂度, 总的比较次数与总的移动次数均约为
,直接插入排序算法的时间复杂度为
。
- 稳定性:
- 稳定性:由于每次插入元素时总是从后向前先比较再移动,所以不会出现相同元素相对位置发生变化的情况,即直接插入排序是一个稳定的排序方法。
- 实用性: 直接插入排序算法适用于顺序存储和链式存储的线性表。为链式存储时,可以从前往后查找指定元素的位置。
2.3,代码实现
/**
* 插入排序代码实现
* @param arr 待排序数组
* @param n 数组元素个数
*/
public static void insertSorted(int []arr,int n){
// 辅助数组
// int [] array=new int[n];
// array[0]=arr[0];
for(int i=1;i<n;i++){
int temp=arr[i];
int k=i-1;
while ((k>=0)&&(arr[k]>=temp)){
arr[k+1]=arr[k];
k--;
// if(k == 0){
// array[k]=temp;
// break;
// }
}
arr[k+1]=temp;
System.out.println(Arrays.toString(arr));
}
}
2,折半插入排序
从直接插入排序算法中,不难看出每趟插入的过程中都进行了两项工作:①从前面的有序子表中查找出待插入元素应该被插入的位置: ②给插入位置腾出空间,将待插入元素复制到表中的插入位置。注意到在该算法中,总是边比较边移动元素。下面将比较和移动操作分离,即先折半查找出元素的待插入位置,然后统一地移动待插入位置之后的所有元素。当排序表为顺序表时,可以对直接插入排序算法做如下改进:由于是顺序存储的线性表,所以查找有序子表时可以用折半查找来实现。确定待插入位置后,就可统一地向后移动元素。
/**
* 折半插入排序
* @param arr 待排序数组
*/
public static void BinaryInsertSorted(int []arr){
int left;
int right;
int mid;
int temp=0;
for(int i=1;i<arr.length;i++){
temp=arr[i];
left=0;
right=i-1;
mid=(left+right)/2;
while (left <=right){
mid=(left+right)/2;
if(arr[i] > arr[mid]){
left =mid+1;
}else if(arr[i]<arr[mid]){
right=mid-1;
}
}
// 循环退出说明找到插入的位置
for(int j=i-1;j>=left;j--){
arr[j+1]=arr[j];
}
arr[left]=temp;
}
System.out.println(Arrays.toString(arr));
}
不难看出折半插入排序仅减少了比较元素的次数,约为O(nlog2)时, 该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n; 而元素的移动次数并未改变,它依赖于待排序表的初始状态。因此,折半插入排序的时间复杂度仍为O() ,但对于数据量不很大的排序表,折半插入排序往往能表现出很好的性能。折半插入排序是一种稳定的排序方法。
3,希尔排序
直接插入排序算法的时间复杂度为,但若待排序列为"正序" 时,其时间复杂度可提高至
,由此可见它更适用于基本有序的排序表和数据量不大的排序表。希尔排序正是基于这两点分析对直接插入排序进行改进而得来的,又称缩小增量排序。
3.1,希尔排序思想
- 希尔排序思想:
-
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,待整个序列中的元素基本有序时,再对全体元素进行一次直接插入排序。算法便终止。
-
- 简单插入排序很循规蹈矩,不管数组分布是怎么样的,依然一步一步的对元素进行比较,移动,插入,比如[5,4,3,2,1,0]这种倒序序列,数组末端的0要回到首位置很是费劲,比较和移动元素均需n-1次。而希尔排序在数组中采用跳跃式分组的策略,通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。希尔排序通过这种策略使得整个数组在初始阶段达到从宏观上看基本有序,小的基本在前,大的基本在后。然后缩小增量,到增量为1时,其实多数情况下只需微调即可,不会涉及过多的数据移动。
- 我们来看下希尔排序的基本步骤,在此我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
希尔排序过程:
- 第一步:
- 第二步:增量为数组长度的一半,5。
- 第三步:增量在缩小为一半,2
- 第四步:增量减小到1,做一次直接插入排序即可。
3.2,希尔排序性能分析
- 空间复杂度:
- 仅使用了常数个辅助单元,因而空间复杂度为0(1) 。
- 时间复杂度:
- 由于希尔排序的时间复杂度依赖于增量序列的函数,这涉及数学上尚未解决的难题,所以其时间复杂度分析比较困难。当n 在某个特定范围时,希尔排序的时间复杂度约为0(n 1.3 ) 。在最坏情况下希尔排序的时间复杂度为
。
- 由于希尔排序的时间复杂度依赖于增量序列的函数,这涉及数学上尚未解决的难题,所以其时间复杂度分析比较困难。当n 在某个特定范围时,希尔排序的时间复杂度约为0(n 1.3 ) 。在最坏情况下希尔排序的时间复杂度为
- 稳定性:
- 稳定性: 当相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,因此希尔排序是一种不稳定的排序方法。
- 适用性:
- 希尔排序算法仅适用于线性表为顺序存储的情况。
- 注意:希尔排序每一趟不会选择一个元素放在最终的位置上面。
3.3,代码实现
3.3.1,基于交换的希尔排序
/**
* shell排序,基于交换式的shell排序
*有逆序对的话就直接交换
* @param arr 待排序数组
*/
public static void shellSorted(int[] arr) {
int len = arr.length;
int step = len / 2;
int temp = 0;
while (step > 0) {//控制增量
//两个for循环对每一个分组做插入排序
for (int i = step; i < len; i++) {
for (int j = i - step; j >= 0; j -= step) {
if (arr[j] > arr[j + step]) {
temp = arr[j];
arr[j] = arr[j + step];
arr[j + step] = temp;
}
}
}
step = step / 2;
System.out.println(Arrays.toString(arr));
}
}
3.3.2,基于直接插入的希尔排序
/**
* 基于插入法的希尔排序算法改进,集合插入排序
* 因为插入法排序对于有序的序列排序复杂度在o(n)左右
* 对于大数据集,排序速度提高很多
* 基于插入法,没有找到合适位置之前一直往后移动元素,找到后直接插入元素
*
* @param arr 待排序数组
*/
public static void ShellSorted(int[] arr) {
int len = arr.length;
int step = len / 2;
int temp = -1;
while (step > 0) {//控制步长
// 里面的双层循环代表插入排序
for (int i = step; i < len; i++) {
int k = i;//记录待插入元素的下标
temp = arr[i];//记录待插入的元素
if (arr[k] < arr[k - step]) {//后面的元素值大于前面的元素值
// while循环开始寻找插入位置
while ((k - step >= 0) && (arr[k] < arr[k - step])) {
arr[k] = arr[k - step];
k -= step;
//退出循环,说明找到插入的位置
arr[k] = temp;
}
}
}
step /= 2;
System.out.println(Arrays.toString(arr));
}
}
参考资料:
[1] https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
[2] https://zhuanlan.zhihu.com/p/122293204