前言:
之前考虑的是自顶向下的思路,当然也可以反过来~
算法思想:
- 将此数组按照从坐到右的顺序两两划分成多个小组来进行归并排序的过程(一个组有2个元素)。
- 在两个元素归并排序完成后,再按照从坐到右的顺序将两个组进行归并到一个组(即1个组有4个元素)。
- 依次类推。
public static void sort(Comparable[] arr){ int n = arr.length; for(int sz = 1; sz < n; sz *= 2){ for(int i = 0; i + sz < n; i += sz+sz){ // 对 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 进行归并 merge(arr, i, i + sz -1, Math.min(i + 2*sz - 1, n - 1)); } } }
代码实现:
在此过程中,并不需要递归,而是采用迭代来实现归并排序,代码如下:
- 首先最外层循环需要对归并的个数进行遍历,size从1开始遍历到n,每次循环增加自身值,即(1->2->4->8)
- 内存循环就是每一轮在归并过程起始的元素位置,位置从0开始到n - sz,每次循环增加2个size,即第一轮从0~size-1、从size~2size-1这两部分进行归并,第二轮从2size~3size-1、从3size~4size-1这两部分进行归并。注意:这里代码编写需要严谨考虑越界问题。
正因如此,可使用O(n*logn)的时间对链表这样的数据结构进行排序,重要的算法题~
代码优化:
- 对于小数组,使用插入排序、
- 在内循环中,如果各个小组都是有序的,就不用归并排序了,即arr[mid] <= arr[mid+1]的情况~
public static void sort(Comparable[] arr){ int n = arr.length; for( int i = 0 ; i < n ; i += 16 ) InsertionSort.sort(arr, i, Math.min(i+15, n-1) ); for( int sz = 16; sz < n ; sz += sz ) for( int i = 0 ; i < n - sz ; i += sz+sz ) // 对于arr[mid] <= arr[mid+1]的情况,不进行merge if( arr[i+sz-1].compareTo(arr[i+sz]) > 0 ) merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1) ); }
值得注意的是:这个算也是一个O(nlogn)复杂度的算法,虽然只使用两重for循环,注意:不要轻易根据循环层数来判断算法的复杂度,Merge Sort BU就是一个反例。
结论:
两者算法效率是差不多的,自底向上的归并排序,是比自顶向下的归并排序要快的。
对归并的两个优化是一样的,第一个是小数组用插入排序,这个是一样的,第二个是真正执行merge操作,这个操作可能在自顶向下会发生在很高的层次,即两个很大的数组也可以优化,进而不用处理两个很大的数组,而自底向上的话却不能,只能一步一步从小数组上构造~所以自底向上的归并速度被拖慢了~
但是,递归调用时需要额外的开销,但是最新的编译器都会对此有所优化~