插入排序(Selection Sort)和希尔排序(Shell Sort)
插入排序
基本思想
对于未排序的元素,在已排序的元素中从后向前扫描,找到合适的位置后插入。
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
简介
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
算法描述
①从第一个元素开始,该元素可以认为已经被排序;
②取出下一个元素,在已经排序的元素序列中从后向前扫描;
③如果该元素(已排序)大于新元素,将该元素移到下一位置;
④重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
⑤将新元素插入到该位置后;
⑥重复步骤2~5。
动图演示
代码实现
/**
* 插入排序
*/
public static int[] insertionSort ( int[] arr ) {
if (arr.length > 0) {
//循环从第二个元素开始判断
for (int i = 1; i < arr.length; i++) {
//如果左边元素大于右边元素,调换元素位置;如果不是,证明左边都是从小到大排列
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
return arr;
}
分析
稳定性
直接插入排序是稳定的。因为未排序的元素在向前扫描的过程中遇到相同的元素就不会继续向前扫描了,更不会插在它的前面。
时间复杂度
**平均和最差情况 T(n) = O(n^2),最佳情况 T(n) = O(n)。**如果这个数组原来就是有序的,那么只需要比较 n-1 次,不需要移动,所以最好情况下的时间复杂度是 n。
希尔排序
基本思想
希尔排序是插入排序升级版,是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
简介
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
算法描述
希尔排序的基本步骤,选择增量interval=length/2,缩小增量继续以interval= interval/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处示例使用希尔增量。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
①选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
②按增量序列个数k,对序列进行k 趟排序;
③每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
图例演示1
图例演示2
图例演示3
代码实现
/**
* 希尔排序(插入排序升级版)
*/
public static int[] shellSort ( int[] arr ) {
if (arr.length>0){
//不断缩小增量
for (int interval = arr.length / 2; interval > 0; interval = interval / 2) {
//增量为1的插入排序
for (int i = interval; i < arr.length; i++) {
int target = arr[i];
int j = i - interval;
while (j > -1 && target < arr[j]) {
arr[j + interval] = arr[j];
j -= interval;
}
arr[j + interval] = target;
}
}
}
return arr;
}
分析
实例分析
例如:序列 9 8 7 6 5 4 3 2 1
- 确定一个增量序列,如4(length/2) 2 1,从大到小使用增量
- 使用第一个增量,将序列划分为若干个子序列,下标组合为0-4-8,1-5,2-6,3-7
- 依次对序列使用直接插入排序法
- 使用第二个增量,将序列划分为若干个子序列(0-2-4-6-8),(1-3-5-7)
- 依次对子序列使用直接插入排序法;
- 使用第三个增量1,这时子序列就是元序列(0-1-2-3-4-5-6-7-8),使用直接插入法
- 完成排序
核心——间隔
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列
while (interval < len / 3) { // 动态定义间隔序列
interval = interval * 3 + 1;
}
稳定性
希尔排序是不稳定的。由于相同的元素可能会被划分至不同子序列单独排序,因此稳定性是无法保证的。
时间复杂度
最差情况 T(n) = O(n^2),平均情况 T(n) = O(n*logn),最佳情况 T(n) = O(n)。
希尔排序为什么比插入排序速度快?
如果原始数据的大部分元素已经排序,那么插入排序的速度很快(因为需要移动的元素很少)
希尔排序为什么快?
- 无序的时候,元素少
- 元素多的时候,已经基本有序