题目:对所给元素存储于数组中或链表中(选择一种情形),写出自然合并排序算法
结果演示:
基本思想:
自然排序是在合并排序的基础上修改而成。
①合并排序
给出一个n个元素无序的整数数组, 将其一分为2,则一个子集为n/2,再将子集划分为2,不断划分直到只有一个元素。
如 9 8 6 7 3 4 5 2 1,划分为{9},{8},{6},{7},{3},{4},{5},{2},{1}
在相邻的两两合并排序,如合并一次为{8,9},{6,7},{3,4},{5,2},{1}
不断重复合并排序直至全部被合并成一个有序数组。
②自然合并排序
(1)与合并排序的主要不同为划分子集方法。自然合并排序根据非按自然排列(一般指递增)的子段划分,只有后面的数比前面小,则划开。
如 9 8 6 7 3 4 5 2 1,划分为{9},{8},{6,7},{3,4,5},{2},{1}
由此划分的子段大小不一,因此需要一个数组a来存储每个子段末尾在原数组中的下标,
如:上述的a为:a={0,1,3,6,7,8}(由于在声明数组s时不确认有多少子段,按最坏情况声明则为n个,又为了搜索方便,后面用divideNum来记录多少个子段)
对子段的访问根据a上记录的索引值访问。
注:在取段时,其开头为前一段的结尾索引+1,即第i段开头索引为a[i-1]+1。当取第1段时,应直接取为0,否则数组a会-1越界。
(2)此时数组a中的每个元素分段为最小分段,在后续合并中会将最小分段合并成大分段,用s表示该合成的最小分段的数目。
如:{9}与{8}合并成{8,9},{6,7}和{3,4,5}合并成{3,4,5,6,7},此时s=1;
{8,9}和{3,4,5,6,7}合并,此时s=2;
不难发现s的增加规律为s+=s;
(3)在合并过程中难免出现有的子段没有更多相同s大小的合并对象的情况:
情况一:如3个小段,合并第一次s=1时,多出了s=1一个子段,此时将其复制到第一次合并的结果后面即可,等待下一次合并
情况二:如7个小段合并到第3次,此时有3个s=2的子段,一个s=1的子段,多的s=2的子段和s=1的子段合并,在编程过程中主要体现为索引取值规律的不同
注:测试时难免疏忽,哪里有可能越界欢迎指出
代码中命名有些混乱,可以结合注释掉的代码段运行理解
package algorism2;
import java.util.Scanner;
public class NaturalMerger {
static int divideNum=0;
public static int[] SSplit(String input){//将输入的字符串划分成整数数组
int i=0;
String[] inps=input.split(" "); //以空格划分
int[] a=new int[inps.length];
for(String s:inps){ //迭代取出
a[i++]=Integer.valueOf(s); //字符串转整数
}
return a;
}
public static int[] divide(int[] a){ //按自然排序划分为段,将a中每段末尾的索引存储到一个数组s中
int[] s=new int[a.length];
if(a.length==1){
s[0]=0;
return s;
}
for(int i=1;i<a.length;i++){
if(a[i]<a[i-1]){//非自然排列划分
s[divideNum]=i-1;
divideNum++;
}
if(i==a.length-1){//最后一个结尾
s[divideNum]=i;
divideNum++;
}
}
/*System.out.print("分割后为:");
for(int i=0;i<NaturalMerger.divideNum;i++)
System.out.print(s[i]+" ");
System.out.println("");*/
return s;
}
public static void merger(int[] input,int[] output,int start,int middle,int end,int s){
//合并input[start:middle]和input[middle+1:end]到output[0:end-start]
int i=start,j=middle+1,o=start;
while(i<=middle&&j<=end){ //可以将其中最大整数较小的一段添加完
if(input[i]<=input[j]){
output[o++]=input[i++];
}
else{
output[o++]=input[j++];
}
}
if(i>middle){ //将另一段后面全加进去
for(;j<=end;j++)output[o++]=input[j];
}
else{
for(;i<=middle;i++)output[o++]=input[i];
}
System.out.print("s:"+s+"合并后为:");
for(int in:output)System.out.print(in+" ");
System.out.println("");
return ;
}
public static void DeToMerger(int[] a,int[] input,int[] output,int s){
//根据divide出最小分段末尾索引的数组,相邻两两合并
//s表示为第几阶段合并,第n阶段为n个最小段与n个最小段合并
int i=0;
while(i<=NaturalMerger.divideNum-2*s){
//合并大小为s的相邻2段
int r=(i+2*s-1)>NaturalMerger.divideNum?NaturalMerger.divideNum:(i+2*s-1);
//System.out.println("i:"+i+" r:"+r);
if(i==0)
NaturalMerger.merger(input, output, 0, a[i+s-1], a[r],s);
else
NaturalMerger.merger(input, output, a[i-1]+1, a[i+s-1], a[r],s);
i=i+2*s;
}
if(i+s<NaturalMerger.divideNum){
//System.out.println("i:"+i+"s:"+s);
if(i==0)
NaturalMerger.merger(input, output, 0, a[i+s-1], a[NaturalMerger.divideNum-1],s);
else
NaturalMerger.merger(input, output, a[i-1]+1, a[i+s-1], a[NaturalMerger.divideNum-1],s);
}
else if(i<NaturalMerger.divideNum)
for(int j=a[i];j<=input.length-1;j++)
output[j]=input[j];
}
public static void mergerSort(int[] input,int[] s,int[] output){
int x=1; //对应最小段数
//System.out.println(NaturalMerger.divideNum);
while(x<NaturalMerger.divideNum){
NaturalMerger.DeToMerger(s, input, output, x);//合并一轮
x+=x;//对应的最小段加倍
if(x>NaturalMerger.divideNum){
System.out.print("output最终结果:");
for(int in:output)System.out.print(in+" ");
System.out.println("");
return;
}
NaturalMerger.DeToMerger(s, output, input, x);//将上次合并的结果作为这次要合并的数组
x+=x;
}
System.out.println("input最终结果:");
for(int in:input)System.out.print(in+" ");
}
public static void main(String args[]){
System.out.println("请输入整数串,用空格隔开");
Scanner sc=new Scanner(System.in);
String input=sc.nextLine(); //获取整数的字符串
int[] a=NaturalMerger.SSplit(input);//将字符串划分成整数数组
int[] s=NaturalMerger.divide(a);
int[] output=new int[a.length];
NaturalMerger.mergerSort(a,s,output);
}
}