Java练习:分治法之合并排序(merge Sort)

本文介绍了Java编程中的分治法策略,特别是应用于合并排序的过程。通过分解、排序和合并三个步骤,详细阐述了如何实现两路合并排序。文章包含递归方法sort1的实现,以及对算法的改进,如消除递归调用和实现自然合并排序。此外,还提供了方便递归的代码实现和简化代码的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回 Java编程练习目录


分而治之(divide-and-conquer)是一种古老但实用的策略、普适性的问题求解策略。本质上,分而治之策略是将整体分解成部分的思想。

按照系统科学的观点,该策略仅适用于线性系统——整体正好对于部分之和

(两路)合并排序遵循分治法的三个步骤,其操作如下:

(1) 分解:将数组大致分成两半。例如将9个元素的数组划分成5+4两半。

(2) 排序:一般需要对子序列递归地再进行划分。极端地,子序列仅仅剩下一个数据,则子序列不需要排序。以突出前后两个步骤。实际程序,划分是有度的。

(3) 合并:将两个已排序的数据序列合并。

1.合并

[5.1.2 针对LinearList]的例程5-3归并(merging)算法,解决的问题:已知la和lb的元素非降序排列(前面的元素小于等于后面的元素),将la和lb合并为lc,且lc中的元素非降序排列。

    public static void merge(LinearList la, LinearList lb,LinearList lc){
        if(la==null || lb==null|| lc==null){
            throw new java.lang.NullPointerException();
        }
        lc.clear();
        int i=0; int j=0;
        while( (i<=la.length())&&(j<=lb.length()) ){
            int ai=la.getAt(i);
            int bj=lb.getAt(j);
            if(ai<=bj){
                lc.add(ai);
                i++;
            }else{
                lc.add(bj);
                j++;
            }            
        }
        while( i<=la.length() ){
            int ai=la.getAt(i++);
            lc.add(ai);
        }
        while( j<=lb.length() ){
            int bj=lb.getAt(j++);
            lc.add(bj);
        }
    }
练习10-21,要求将例程5-3修改成algorithm.recursion.Merging的static方法int[] merge(int[]arr1,int[] arr2)。例如

    /**3
     * 归并(merging)
     * 把两个非降序的数组arr1和arr2,合并为元素非降序排列的数组后返回。
     */
    public static int[] merge(int[] arr1,int[] arr2){
       if(arr1==null || arr2==null){
            throw new java.lang.NullPointerException();
        } 
        int len1 = arr1.length;
        int len2 = arr2.length;
        if(len1 == 0){return arr2;}//arr0={}
        if(len2 == 0){return arr1;}
        int i,j ; i =j = 0;//index of arr1 and arr2.
        int[] arr3 = new int[ len1 + len2 ]; //辅助数组
        int k =0 ;//index of arr3
        //主循环,完成大体合并
        while ( i<len1 && j<len2 ){//只要arr1而且arr2中还有元素
            if( arr1[i] <= arr2[j] ){//比较两种的第一个元素,将小的数据一个放入arr3
                arr3[k] = arr1[i];
                i++;
            }else{
                arr3[k] = arr2[j];
                j++;
            }
            k++;
        }
        //如果arr1或者arr2中还有元素未放入arr3
        while(i<len1){
            arr3[k] = arr1[i];
            k++;
            i++;            
        }
        while(j<len2){
            arr3[k] = arr2[j];
            k++;
            j++;            
        }
        return arr3;
    }
练习10-22.:解释arr3[k++] = arr2[j++]的等价代码,使用该技巧减少上一练习中代码的行数。

    public static int[] merge1(int[] arr1,int[] arr2){
        if(arr1==null || arr2==null){
            throw new java.lang.NullPointerException();
        }
        if(arr1.length == 0){return arr2;}//arr1={}
        if(arr2.length == 0){return arr1;}

        int[] arr3 = new int[ arr1.length + arr2.length ];
        int i,j,k; i =j = k=0;//index of arr1,2,3
        while ( i<arr1.length && j<arr2.length ){
            if( arr1[i] <= arr2[j] ){
                arr3[k++] = arr1[i++];
            }else{
                arr3[k++] = arr2[j++];
            }
        }
        while( i<arr1.length )
            arr3[k++] = arr1[i++];            
        while( j<arr2.length )
            arr3[k++] = arr2[j++];            
        return arr3;
    }

测试:

    public static void test(){
        int[] arr1 = {3,5,8,11};
        int[] arr2 = {2,6,8,9,11,15,20};
        int[] arr3 = merge1(arr1,arr2);        
        display(arr1);
        display(arr2);
        display(arr3);
    }

    private static void display(int[] data){//java.util.Arrays.toString(<span style="font-family: Arial, Helvetica, sans-serif;">data</span>)
        for (int i:data){
            System.out.print(" " + i);
        }
        System.out.println("");
    }

方便递归

为了方便递归调用,将上面的arr1后接arr2,组成一个数组arr的一部分。low1、high1表示原arr1的起止点索引,low2、high2表示原arr2的起止点索引.(有low2其实可以不要high1)

练习10-22.:编写public static void merge2(int[] arr, int low1, int high1, int low2, int high2)

    public static void merge2(int[] arr, int low1, int high1, int low2, int high2){
        //辅助空间arr3,arr3的长度为[low1,high2]即high2-low1+1
        int[] arr3; 
        int k = 0; //辅助空间arr3的索引
        int low3 = low1;
        int high3 = high2;
        arr3 = new int[high2-low1+1];
        
        //合并。
        while (low1 <= high1 && low2 <= high2){ 
            if (arr[low1] < arr[low2]){
                arr3[k++] = arr[low1++];
            }else{
                arr3[k++] = arr[low2++];
            }
        }
        while(low1 <= high1){
            arr3[k++] = arr[low1++];
        }
        while(low2 <= high2){
            arr3[k++] = arr[low2++];
        }
        
        //最后将子序列arr3的有效元素放入初始的数组arr
        k=0;
        for (int i = low3; i <= high3; i++){
            arr[i] = arr3[k++];
        }
    } //end merge2()

精简代码

以arr3为中心,将上面代码的while合并。

    public static void merge3(int[] arr, int low1, int low2, int high2){
        final int p = low1,q = low2-1,len =high2-low1 +1;
        int[] arr3 = new int[len];        
        for(int k=0; k<len; k++){//辅助空间arr3的索引
            if ( 
                (low1 <= q && low2 <= high2  && arr[low1] <= arr[low2])
                ||(low2 > high2)//进一步
            ){                
                arr3[k] = arr[low1++];
            }else{
                arr3[k] = arr[low2++];
            }
        }        
        System.arraycopy(arr3, 0, arr,p,len);
    } //end merge3() 
最后还可以再精简一下,if-else由?:替代。

    public static void merge4(int[] arr, int low1, int low2, int high2){
        final  int p = low1,q = low2-1,len =high2-low1 +1;
        int[] arr3 = new int[len];        
        for(int k=0; k<len; k++){//辅助空间arr3的索引
            arr3[k] = (low1 <= q && low2 <= high2  && arr[low1] <= arr[low2]) ||(low2 > high2)?
                arr[low1++]:arr[low2++]; 
        }
        System.arraycopy(arr3, 0, arr,p,len);
    } 

2.递归方法sort1(int[] arr, intfirst, int last)

(两路)合并排序的分解,将数组大致分成两半,并递归分解到一个元素。因此分治法的三个步骤中分解、排序非常简单。

package algorithm.recursion;
public class MergeSort{
    public static int[] sort(int[] data){
        sort1(data, 0, data.length-1);//为了递归,使用私有方法sort1()
        return data;
    }
    
    private static void sort1(int[] arr, int first, int last){
        //递归的结束条件:first>= last
        if(first < last){
            int middle = (first + last) / 2; //计算中间位置 (first + last) >>> 1;
            sort1(arr, first, middle);//递归地调用
            sort1(arr, middle +1, last);
            Merging.merge4(arr, first,middle+1 , last); 
        }
    }
} 

注:由于algorithm.recursion.MergeSort作为第10章的内容,没有归档到第11章排序中,所以MergeSort的int[] sort(int[] data)设计为static。它没有作为algorithm.sorting.IntSort的子类。

3.改进

1、消除递归调用。algorithm.recursion.MergeSort三个步骤中分解、排序事实上没有什么作用。换言之,我们可以直接将待排序的数组分成两个元素一组,调用Merging.merge4,在n/2个长度为2的子集合上,两两进行Merging.merge4...

2.自然合并排序。扫描待排序的数组,将整个数组分解成一系列已经有序的数组子段,然后合并、合并。如{2 6 4 7 8 0 3 9 1 5}可以分解成{2 6}{ 4 7 8}{ 0 3 9}{ 1 5}.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值