1,算法描述
算法描述:对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
2,实现步骤
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
1)常规实现
1 /** 2 * 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2) 3 * 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n) 4 * 平均时间复杂度 ---- O(n^2) 5 * 所需辅助空间 ------ O(1) 6 * 稳定性 ------------ 稳定 7 */ 8 private static void insertionSort1(int[] a) { 9 long start = System.nanoTime(); 10 int len = a.length; 11 for (int i = 1; i < len; i++) { 12 int temp = a[i]; // 右手抓到一张扑克牌 13 int j = i - 1; // 拿在左手上的牌总是排序好的(i-1到0位置) 14 while (j >= 0 && a[j] > temp) {// 将抓到的牌与手牌从右向左进行比较 15 a[j + 1] = a[j]; // 如果该手牌比抓到的牌大,就将其右移 16 j--; 17 } 18 a[j + 1] = temp;// 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的) 19 } 20 long end = System.nanoTime(); 21 System.out.println((end - start) / 1000.0 + "us"); 22 }
2)改进实现(二分插入排序)
改进插入排序: 查找插入位置时使用二分查找的方式。
1 /** 2 * 最差时间复杂度 ---- O(n^2) 3 * 最优时间复杂度 ---- O(nlogn) 4 * 平均时间复杂度 ---- O(n^2) 5 * 所需辅助空间------ O(1) 6 * 稳定性 ------------ 稳定 7 */ 8 private static void insertionSort2(int[] a) { 9 long start = System.nanoTime(); 10 int len = a.length; 11 for (int i = 1; i < len; i++) { 12 int temp = a[i]; // 右手抓到一张扑克牌 13 int left = 0; // 拿到左手上排序序列的左右边界 14 int right = i - 1; 15 while (left <= right) { // 二分查找 16 int mid = (left + right) / 2; 17 if (a[mid] > temp) { 18 right = mid - 1; 19 } else { 20 left = mid + 1; 21 } 22 } 23 for (int j = i - 1; j >= left; j--) {// 将欲插入新牌位置右边的牌整体向右移动一个单位 24 a[j + 1] = a[j]; 25 } 26 a[left] = temp; 27 } 28 long end = System.nanoTime(); 29 System.out.println((end - start) / 1000.0 + "us"); 30 }
3,插入排序的更高效改进:希尔排序
1959年Shell发明; 第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与简单插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
1)算法描述
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n^2)的排序(冒泡排序或直接插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
2)实现步骤
1 /** 2 * 最佳情况:T(n) = O(n) 3 * 最坏情况:根据步长序列的不同而不同 4 * 平均情况:根据步长序列的不同而不同 5 * 所需辅助空间 ------ O(1) 6 * 稳定性 ------------ 不稳定 7 */ 8 private static void shellSort(int[] a) { 9 long start = System.nanoTime(); 10 int len = a.length; 11 int h = 0; 12 while (h <= len){ // 生成初始增量(因为这个增量会递减,所以会产生一个增量序列,序列的最后一个一定是1,即h=1即简单插入排序) 13 h = 3 * h + 1; 14 } 15 while (h >= 1){ 16 for (int i = h; i < len; i++){ //简单插入时右手抓牌是从i=1即第二张牌开始的,现在从h开始 17 int temp = a[i]; 18 int j = i - h; // 拿在左手上的牌还是排序好的(i-h到0位置) 19 while (j >= 0 && a[j] > temp){ 20 a[j + h] = a[j]; 21 j = j - h; 22 } 23 a[j + h] = temp; 24 } 25 h = (h - 1) / 3; // 递减增量 26 } 27 long end = System.nanoTime(); 28 System.out.println((end - start) / 1000.0 + "us"); 29 }
从代码可以看出,h=1时就是简单插入排序。
例如数组长度是n=20,按程序中的计算公式,则步长序列为13,4,1。
3)算法说明
希尔排序是不稳定的排序算法,虽然一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
比如序列:{ 3, 5, 10, 8, 7, 2, 8, 1, 20, 6 },h=2时分成两个子序列 { 3, 10, 7, 8, 20 } 和 { 5, 8, 2, 1, 6 } ,未排序之前第二个子序列中的8在前面,现在对两个子序列进行插入排序,得到 { 3, 7, 8, 10, 20 } 和 { 1, 2, 5, 6, 8 } ,即 { 3, 1, 7, 2, 8, 5, 10, 6, 20, 8 } ,两个8的相对次序发生了改变。
参考:
http://www.cnblogs.com/eniac12/p/5329396.html
来源:https://www.cnblogs.com/xdyixia/p/9142556.html