《算法》阅读笔记-2.2归并排序

本文深入讲解了归并排序的各种实现方式,包括自顶向下和自底向上的归并排序,并探讨了不同改进措施如何提高效率。

2.2归并排序

2.2.1 原地归并的抽象方法

归并:将两个有序的数组归并成一个更大的有序数组。

原地归并的抽象方法merge(a,lo,mid ,hi),将子数组a[lo..mid]和a[id+1..hi]归并成一个有序的数组并将结果存放在a[lo..hi]中,代码如下:

public static voidmerge(Comparator[] a,intlo,intmid, inthi){
   
int i =lo, j = mid +1;
   

    for(intk = lo; k <= hi; k++)//将a[lo..hi]复制到aux[lo..hi]中
       
aux[k] = a[k];
   

    for(intk = lo; 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++];
   
}
}

代码解释:

1.    前提是子数组是有序的;

2.    将a[lo..hi]复制到aux[lo..hi]中;

3.    左半边用尽:取右半边的元素;

4.    右半边用尽:取左半边的元素;

5.    右半边的当前元素小于左半边的当前元素:取右半边的元素

6.    右半边的当前元素大于等于左半边的当前元素:取左半边的元素

算法复杂度:

需要比较的次数为2^n / 2~ 2^n,其中2^n = N(数组的长度)

 

2.2.2 自顶向下的归并排序

代码如下:

private static Comparator[] aux;

public static void sort(Comparator[] a){
    aux = new Comparator[a.length];
    sort(a,0,a.length-1);
}

public static void sort (Comparator[] a, int lo, int hi){
    //将数组a[lo: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);
}

代码执行可用以下图来理解:merge(..0..1)  merge(..2..3) merge(..0..3) merge(..4..5) merge(..6..7)merge(..4..7) merge(..0..7) merge(..8..9) merge(..10..11) ……

代码解释:

1.    分治的思想:将大问题分割成小问题分别解决,然后所有小问题的答案来解决整个大问题;

2.    Sort()的作用本质是安排多次merge()方法调用的顺序。

算法复杂度:

按照上图依赖树来表示merge()方法的调用,若树有n层,则0到n-1间的任意k,自顶向下的第k层有2^k个数组,每个数组长度为2^n-k,因此每层的比较次数是

2^k * 2^n-k / 2~ 2^k * 2^n-k,即 2^n / 2 ~ 2^n,n层总共为n * 2^n / 2 ~ n * 2^n。

若N = 2^n(长为N的数组),归并排序需要1/2 * NlgN至NlgN,复杂度为NlgN。

长为N的数组,自定向下的归并排序最多需要访问数组6NlgN次(不懂)

       应用:

可以归并排序处理数百万甚至更大规模的数组,这是插入排序选择排序做不到的

2.2.2.1改进-对小规模子数组使用插入排序

递归使小规模问题中的方法调用过于频繁。插入排序在处理小规模数组(长度小于15)比归并排序更快,一般可将归并排序的运行时间缩短10%~15%。

2.2.2.2改进-判断数组是否已经有序

添加判断条件:如果a[mid] <= a[mid+1],就认为数组已经有序,跳过merge()方法

2.2.2.3改进-不将元素复制到辅助数组

在递归调用的每个层次交换输入数组和辅助数组(不懂)

 

2.2.3 自底向上的归并排序

算法思想:先归并微型数组,在成对归并得到的子数组。

先两两归并,再四四归并,在八八归并……最后一次归并的第二个子数组可能比第一个子数组要小,如果不是的话所有归并的两个数组大小是一样的。

算法实现:

public static void sort(Comparator[] a){
    int N = a.length;
    aux = new Comparator[N];
    for(int sz = 1; sz < N; sz = sz + sz){//每次归并的数组长度为sz*2
        for(int lo = 0; lo < N - sz; lo += sz + sz){
            merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));//当最后一次调用merge方法时,第二个数组不等于(小于)第一个数组时取N-1
        }
    }
}

算法复杂度:

对于长度为N的数组,比较的次数是1/2 * NlgN~NlgN,最多访问数组6NlgN次。

应用:

自底向上归并排序适合用链表组织数据。

2.2.4 排序算法的复杂度

排序算法的复杂度最小是NlgN,试图寻找复杂度小于NlgN排序算法是无意义的

 

问答

不同算法适用于不同应用场景的不同输入

归并排序比希尔排序快嘛?实际中运行时间的差距在常数级别内,相对性能取决于具体实现

不把aux[]声明为merge()方法的局部变量是为了避免每次归并时创建一个新的数组

所有元素都相同时,归并排序的时间是线性的(前提是有额外的判定)

如果有多个不同的重复值,运行时间仍然是线性对数的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值