排序算法1中,对于选择排序和插入排序进行了介绍,通过代码,可以看出,假设有一个给定的乱序数组,插入排序因为都只能通过交换相邻的元素,于是得到的时间复杂度是相对来说比较高的。从这也可以看出,在插入排序中,元素只能缓慢的从一端到另一端。从这就可以观察得到,这应该还有优化的空间,于是,基于插入排序的改进算法进一步提出。
既然插入排序对于乱序对象的处理能力比较差,也就是某个对象需要从一端到另外一端。基于这个思想,能不能不让对象只一个一个相邻交换。因此,希尔排序通过扩大每个对象的间隔交换值h,从而保证任意间隔值h都都是有序的;这样通过较大的将间隔值,从而保证对象不是一个一个相邻移动,从而提高效率。看代码,此代码还是基于排序算法1中的模板,以下只展示sort部分,其余部分可参见排序算法1:
//目标:实现a[]按照升序排列 public static void sort(Comparable[] a){ int N = a.length; //数据间隔h时为有序。 int h = 1; //找到最大的间隔h,除以几可以根据需要进行调节 while(h < N/3){ h = 3*h + 1; } while(h >= 1){ for (int i = h; i < N ; i++) { for (int j = i; j >= h && less(a[j],a[j-h]); j-=h) { exch(a,j,j-h); } } show(a); h = h/3; } }
在此,可能体会不到希尔排序起到了什么作用,通过一个简单的小实验,体会下
希尔排序结果
原始数组:20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 第一次:7 6 5 4 3 2 1 13 12 11 10 9 8 20 19 18 17 16 15 14 第二次:3 2 1 4 7 6 5 9 8 11 10 13 12 16 15 14 17 20 19 18 第三次:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
通过观察可以得到,希尔排序很快就可以把乱序偏后的元素移动到另外一边,这就是希尔排序的强大之处。所以说,在此也会有很多疑问,如何进行选择间隔递增的h值呢。这个在很多论文都进行了研究,但是是一个比较难的问题,实际中,可以通过调节h值进行测试。也可以看出,希尔排序解决了插入排序在乱序情况下移动困难的问题。并且实践也证明,希尔排序对于中等大小的数组的运行时间是 可以接受的,并且也不需要额外的内存空间。
通过希尔排序,可以知道,其思想是通过控制全部对象中间隔长度为h的保持有序。但基于此是否还可以想到,能否通过一直保证部分有序,最终实现全局有序呢。归并排序通过这样的思想。首先给的的对象数组,一分为二,然后有两个子数组对象,通过保证两个子数组对象有序,然后将结果进行归并,从而达到全局有序。也就是说,归并排序通过分治的思想,最后达到全部对象有序。
其实想法是比较简单的,但实现起来却要考虑很多问题。比如说,对于一个很长的大数组来说,如果每次归并都要创建一个新数组来进行存储归并结果,这导致很大的空间浪费,因此,在这可以考虑原地归并的思路,也就是创建一个和原数组大小一样的数组,进行每次的归并存储。归并排序的代码如下
private static Comparable[] aux; //将两个已排序数组对象进行归并 public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = 0; k <= hi; k++) { aux[k] = a[k]; } for (int k = 0; k <= hi; k++) { if (i > mid) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; else if (less(aux[j], aux[i])) a[k] = aux[j++]; else a[k] = aux[i++]; } } //排序 public static void sort(Comparable[] a) { aux = new Comparable[a.length]; //整个进行排序 sort(a, 0, a.length - 1); } private static void sort(Comparable[] a, int lo, int hi) { if(hi <= lo) return;//递归出口 //二分 int mid = lo + (hi-lo)/2; sort(a, lo, mid);//保证左边有序 sort(a, mid+1, hi);//保证右边有序 merge(a, lo, mid, hi);//进行归并 }
上述是一个自顶向下的归并排序,也就是从整个数组为开端,一步一步的分治,从而达到全部排序的目的。当然,也可以通过自下往上的方式达到目的,在此不再重复赘述。对于归并排序的算法复杂度,可以看见,归并排序似乎有点二分的感觉,是的,每一个都把数据进行二分,因此时间复杂度相对于插入或者选择肯定是降低了的,具体的时间复杂度不在详细讨论,后面会对各种算法的时间空间复杂度进行一个详细的论述。
通过希尔和归并排序,其实要充分理解两种算法还是有一定的难度的,但最重要的是,理解两种算法所带来的核心思想。希尔排序通过保持间隔值h为有序,从而修复了插入排序在乱序临近元素的冗余性。归并排序通过分治的思想,把大问题化解为小问题,通过一分为二的思想,从而解决了全局性问题。对于算法的认识,要充分认识算法在哪里有冗余性,这才是需要改进的空间。