1. 归并排序算法的使用情景

归并排序算法和快速排序算法是java.util.Arrays中使用的排序算。对于一般的基本数据类型,Arrays.sort函数使用双轴快速排序算法,而对于对象类型使用归并排序(准确的说使用的是TimSort排序算法,它是归并排序的优化版本)。这样做的原因有两点,第一个原因,归并排序是稳定的,而快速排序不是稳定的。第二个原因,对于基本数据类型,排序的稳定性意义不大,但对于复合数据类型(比如对象)排序的稳定性就能帮助我们保持排序结果的某些性质。

2. 自顶向下的归并排序

自顶向下的排序算法就是把数组元素不断的二分,直到子数组的元素个数为一个,因为这个时候子数组必定是已有序的,然后将两个有序的序列合并成一个新的有序的序列,两个新的有序序列又可以合并成另一个新的有序序列,以此类推,直到合并成一个有序的数组。

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

为了体现归并的排序的稳定性,我们的代码使用java的泛型来实现对任意对象的排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static <T extends Comparable<? super T>>
void MergeSortUpToDown(T[] A){
    @SuppressWarnings("unchecked")
    //创建合并两个有序序列的辅助数组
    T[] aux = (T[])Array.newInstance(A.getClass().getComponentType(), A.length);
    mergeSortUpToDown0(A, aux, 0, A.length-1);
}
 
public static <T extends Comparable<? super T>>
void mergeSortUpToDown0(T[] A, T[] aux, int start, int end){
    if(start == end)
        return;
    int mid = (start+end)/2;
    mergeSortUpToDown0(A, aux, start, mid);
    mergeSortUpToDown0(A, aux, mid+1, end);
    //复制到辅助数组中,此时[start,mid] [mid+1, end]两个子数组已经有序
    System.arraycopy(A, start, aux, start, end - start + 1);
    //然后归并回来
    int i = start, j = mid+1, k;
    for(k = start; k <= end; k++){
        if(i > mid){
            A[k] = aux[j++];
        }else
        if(j > end){
            A[k] = aux[i++];
        }else
        if(aux[i].compareTo(aux[j]) <= 0){
            A[k] = aux[i++];
        }else{
            A[k] = aux[j++];
        }
    }
}

3. 自底向上的归并排序

自底向上的归并排序算法的思想就是数组中先一个一个归并成两两有序的序列,两两有序的序列归并成四个四个有序的序列,然后四个四个有序的序列归并八个八个有序的序列,以此类推,直到,归并的长度大于整个数组的长度,此时整个数组有序。需要注意的是数组按照归并长度划分,最后一个子数组可能不满足长度要求,这个情况需要特殊处理。自顶下下的归并排序算法一般用递归来实现,而自底向上可以用循环来实现。

平面设计培训,网页设计培训,美工培训,游戏开发,动画培训

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//自底向上归并排序
public static <T extends Comparable<? super T>> void MergeSortDownToUp(T[] A){
    @SuppressWarnings("unchecked")
    T[] aux = (T[])Array.newInstance(A.getClass().getComponentType(), A.length);
    int len,i,j,k,start,mid,end;
    //len表示归并子数组的长度,1表示,一个一个的归并,归并后的长度为2,2表示两个两个的归并,归并后的长度为4,以此类推
    for(len = 1; len < A.length; len = 2*len){
        //复制到辅助数组中
        System.arraycopy(A, 0, aux, 0, A.length);
        //按照len的长度归并回A数组,归并后长度翻倍
        for(start = 0; start < A.length; start = start+2*len){
            mid = start + len - 1;
            //对于数组长度不满足2的x次幂的数组,mid可能会大于end
            end = Math.min(start + 2*len - 1, A.length-1);
            i = start;
            //mid大于end时,j必然大于end,所以不会引起越界访问
            j = mid+1;
            //[start,mid] [mid+1, end]
            for(k = start; k <= end; k++){
                if(i > mid){
                    A[k] = aux[j++];
                }else
                if(j > end){
                    A[k] = aux[i++];
                }else
                if(aux[i].compareTo(aux[j]) < 0){
                    A[k] = aux[i++];
                }else{
                    A[k] = aux[j++];
                }
            }
        }
    }
}

4. 参考文章

[1] 算法(第四版)RobertSedgewick

分类: 算法