返回 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}.