算法回顾系列第七篇:归并排序

------------------------------------

归并排序

 

基本原理:

利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后通过递归步骤将排好序的半子表合并成为越来越大的有序序列。

归并排序包括两个步骤,分别为:1、划分子表。2、合并半子表。

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

 

程序实现:

public void sort(int[] data){
        int[] temp = new int[data.length];
        mergeSort(data,temp,0,data.length-1);
    }
    
    /**
     * @param 待排序数组
     * @param 辅助排序数组
     * @param 起始位置
     * @param 结束位置
     */
    private void mergeSort(int[] data,int[] temp,int l,int r){
        int mid=(l+r)/2;
        if(l==r) return ;
        //分治:递归划分左半个子表
        mergeSort(data,temp,l,mid);
        //分治:递归划分右半个子表
        mergeSort(data,temp,mid+1,r);
        System.out.println("begin merge ...");
        /**
         * 合并子表:
         * 左半个子表和右半个子表标记(mid为中间位置)两个指针各指向头元素,
         * 选择相对小的元素放入到合并空间,并移动指针到下一位置。
         * 重复步骤上面步骤直到左(或右)某一指针达到序列尾,
         * 将右(或左)剩下的所有元素直接复制到合并序列尾。
         */
        System.out.println("data:"+Arrays.toString(data)+" temp:"+Arrays.toString(temp));
    	System.out.println("l:"+l+" r:"+r+" mid:"+mid);
    	//复制data中l到r内区域的数到temp的对应位置.
        for(int i=l;i<=r;i++){
            temp[i]=data[i];
        }
        System.out.println("copy data:"+Arrays.toString(data)+" copy temp:"+Arrays.toString(temp));
        //声明2个指针:i1左子表头,i2右子表头,指针作用于temp,合并后的值写入data.
        int i1=l;//指针:mid左边元素坐标位置(起始为l).
        int i2=mid+1;//指针:mid右边元素坐标位置(起始为mid右边第一个元素).
        //遍历l到r区域之间的数据(从l的最左边元素向右遍历直到r).
        for(int cur=l;cur<=r;cur++){
            if(i1==mid+1){//如果i1已经遍历到mid以右(mid为边界).
            	System.out.println("i1==mid+1");
                data[cur]=temp[i2];//将tmp的mid以右坐标位置的元素写入data当前位置.
                i2++;//mid右子表指针移动(mid右边元素坐标+1)
            }else if(i2>r){//如果mid以右元素已经遍历超过r(r为边界).
            	System.out.println("i2>r");
            	data[cur]=temp[i1];//将tmp的mid以左坐标位置的元素写入data当前位置.
            	i1++;//mid左子表指针移动(mid左边元素坐标+1)
            }else if(temp[i1]<temp[i2]){//如果tmp的mid以左坐标位置元素值小于tmp的mid以右的坐标位置元素值.
            	System.out.println("temp[i1]<temp[i2]");
            	data[cur]=temp[i1];//将较小的元素值写入data当前位置.
            	i1++;//mid左子表指针移动(mid左边元素坐标+1)
            }else{//mid以左大于mid以右.
            	System.out.println("else");
            	data[cur]=temp[i2];//将tmp的mid以右坐标位置的元素值写入data当前位置.
            	i2++;//mid右子表指针移动(mid右边元素坐标+1)
            }
            System.out.println("swap data:"+Arrays.toString(data)+" swap temp:"+Arrays.toString(temp));
        }
        //System.out.println("last data:"+Arrays.toString(data)+" last temp:"+Arrays.toString(temp));
        //System.out.println("l:"+l+" r:"+r);
    }
    
    public static void main(String[] args){
    	//int[] arrays = new int[]{10,2,4,8,5,1,6,3,7,9};
    	int[] arrays = new int[]{10,9,8,7,6,5,4,3,2,1};
    	new MergeSort().sort(arrays);
    	System.out.println(Arrays.toString(arrays));
    }

上面程序中:

1、首先通过递归将待排序数组data划分为若干个子序列,

2、合并某个data子序列时将该子序列内元素的复制到temp对应位置。

每个子序列有l,r,mid三个位置,分别对应它的:起始位置、结束位置、中间位置。

从l到mid(包含)为左子表,从mid+1到r(包含)为右子表。

声明两个指针(或标记)分别指向temp的:i1左子表头(l)位置、i2右子表头(mid+1)位置。

比较这两个位置的元素值,如果:

(1)左子表元素值小于右子表元素值,将左子表元素写入data当前位置。左子表指针右移,data当前位置右移。

(2)右子表元素值小于左子表元素值,将右子表元素写入data当前位置。右子表指针右移,data当前位置右移。

(3)左子表遍历完,即i1到达mid+1的位置。将右子表元素写入data当前位置。右子表指针右移,data当前位置右移,相当于把右子表内的剩余元素直接写入data中。

(4)右子表遍历完,即i2到达r+1的位置。将左子表元素写入data当前位置。左子表指针右移,data当前位置右移,相当于把做子表内的剩余元素直接写入到data中。

3、重复以上步骤合并每个子序列,最后通过递归步骤将排好序的子序列合并成为越来越大的有序序列。

 

性能分析:

平均时间复杂度是O(nlogn) 。

速度仅次于快速排序(且为稳定排序算法)。

 

空间复杂度:O(n)。

稳定性:稳定。

 

PS:另外有改进算法如下,有时间研究一下:


In-place Merge Sort (原地归并排序)

即时间复杂度还是O(nlogn),但空间复杂度为O(1)的归并排序算法。