Java教程

排序算法(2)

本文主要是介绍排序算法(2),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

排序算法(2)

排序算法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为有序,从而修复了插入排序在乱序临近元素的冗余性。归并排序通过分治的思想,把大问题化解为小问题,通过一分为二的思想,从而解决了全局性问题。对于算法的认识,要充分认识算法在哪里有冗余性,这才是需要改进的空间。

这篇关于排序算法(2)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!