超详细解决自然合并排序问题

  1. 自然合并排序的原理:对于初始给定的数组a = {4,8,3,7,1,5,2,6};其中存在四个子序列段,分别是{4,8},{3,7},{1,5},{2,6}。然后将子序列段两两合并,如{3,4,7,8},{1,2,5,6}等更大的子序列段,直到子序列段为1。
  2. 怎么确定一个子序列段?有两种方式,获取子序列段的首元素坐标和尾元素坐标,或获取首尾元素坐标之一和子序列段的长度,因为首尾元素坐标之差的绝对值就是子序列长度,这里我们采取第二种方式。
  3. 那怎么获取子序列段的首坐标和长度呢?首先定义两个整型变量,分别记录首坐标(设为First)和长度(设第一个子序列的长度为Fnum。子序列长度至少为1)。在对数组a从头开始扫描的时候,对If((a[i]<a[+1]))成立的,长度变量+1.当该条件不成立时,就把 i 赋值给首坐标变量,但此时的i是子序列的尾坐标,所以仍需减去子序列的长度得到子序列的首坐标,然后将长度赋值给Fnum。
 for(int i=0;i<N;i++){
    	if(less(a[i],a[i+1]) && i != N-1){
    		cnt++; //这里的cnt就是子序列长度
		}
  1. 因为至少两个子序列段才能合并。所以我们还要找出第二子序列段。设第二个子序列段首座标为Last,长度为Lnum。同样根据上面代码队可以取得首坐标。那么,问题来了。因为是线性扫描,那么怎么确定扫描到的坐标应该分配给First还是Last呢。答案是定一个标志值flag来判断First是否已经赋值,若赋值过,则扫描到的子序列段的尾坐标分配给Last。
  2. 这里还存在的一个问题是当扫到最后的子序列段是满足条件时,其并不会给First或Last赋值,这是因为次数的最后一个的子序列段就是数组末端,后面没有元素跟它比较大小。那怎么解决呢?我们得意识到,最后的子序列段的尾坐标一定是赋值给Last的。为什么呢?当是奇数的时候,按理来说是将尾坐标赋值给First,但没有多出一个的子序列段尾坐标来赋值给Last,让First和Last代表的子序列段进行合并。也就是说在子序列段为奇数的时候将最后一个子序列段赋值给First是无意义的,因为缺少Last代表的第二个子序列段无法进行合并。而偶数的时候自然是赋值给Last的。所以,无论子序列段的数量是奇数还是偶数,最后一个子序列段的尾坐标都是赋值给Last,此时的尾坐标自然就是数组的最大长度了。在这里插入图片描述
	if(flag == 0 && i != N-1){
				First = i-cnt+1;//这里加1是因为cnt是从1开始的,而First和Last是从0开始的			
				Fnum = cnt;			
				flag = 1;//表示已赋值 
				cnt = 1;//重新开始计数 
				segments++;
			}else{				
				Last = i-cnt + 1;
				Lnum = cnt;				
				cnt = 1;//重新计数 
				Llag = 1;
				segments++;
			} 

6.当确定两个子序列段时,就可以调用排序函数对数组a进行第一次合并,然后将数组b中的值copy到a数组a里面,所以下面的 i = first是很重要的 。

int sort(int a[],int b[],int First,int Fnum,int Last,int Lnum)
{
	int i = First;//a[]和b[]子序列位置要相等
	while(Fnum>0 && Lnum > 0){
	
		if(less(a[First],a[Last])){
			b[i++] = a[First++];
			Fnum--;
		}else{
			b[i++] = a[Last++];
			Lnum--;
		}
	}	
	if(Fnum>0){
     	while(Fnum>0){
     		
       	b[i++] = a[First++];	
      	Fnum--;
	    }			
	}
	else{
		while(Lnum>0) {
			b[i++] = a[Last++];
			Lnum--;
		}
	}
}

7.最后还存在一个问题,怎么确定需要扫描多少次。我们要知道合并排序是将他的子序列段(子序列段为奇数的话就再加1)减少一半直到1,这样的话,我们就可算出需要多少遍扫描。如.我们可以在第一次扫描的时候对数组排一遍,排了一遍序的同时知道子序列段数。之后扫描次数再减去这一次即可,但这里的前提条件是极端恶劣的情况,更好的情况是,完成排序的两个子序列刚好的最大值小于下一个子序列的的最小值,这就减少了一次合并,也使得子序列数量在一次合并后减少了两个,所以下图是按极端恶劣情况来处理的,以防止出现扫描次数过少的问题。在这里插入图片描述

	int Num = 0;  //记录需要多少次扫描 
	float segments = k;
	while(segments>1){
		segments = segments/2;
		bour = segments;
		if(segments > bour){//应对子序列段是奇数的情况 
			bour++;
			segments = bour;
		}
		Num++;
	} 

8.最后附上源码

#include<stdio.h>
#include<stdlib.h>
#define key(A)  A
#define less(A,B) A<B
int sort(int a[],int b[],int First,int Fnum,int Last,int Lnum)
{
	int i = First;
	while(Fnum>0 && Lnum > 0){//一个序列中的元素取完之后就添加另一个序列元素了。
	
		if(less(a[First],a[Last])){
			b[i++] = a[First++];
			Fnum--;
		}else{
			b[i++] = a[Last++];
			Lnum--;
		}
	}	
	if(Fnum>0){
     	while(Fnum>0){
     		
       	b[i++] = a[First++];	
      	Fnum--;
	    }			
	}
	else{
		while(Lnum>0) {
			b[i++] = a[Last++];
			Lnum--;
		}
	}
}

void copy(int a[],int b[])
{
	int i = 0;
	int j = 0;
	while(b[j]){
		a[i++] = b[j++];
	}
}

int huafen(int a[],int b[],int N)//N为数组中最大元素值的下标。 
{
    int First;//前一段数组的首坐标 
    int Last;//后一段数组的首坐标
	int Fnum; //容纳第一段数组的元素量 
	int Lnum;//容纳后一段数组的元素量 
    int cnt = 1;//计数器
	int flag = 0;//判断First是否赋值 
	int Llag = 0;//判断First是否赋值 
	int segments = 1;

    for(int i=0;i<N;i++){
    	if(less(a[i],a[i+1]) && i != N-1){
    		cnt++; 
		}
		else{		
			if(flag == 0 && i != N-1){
				First = i-cnt+1;//这里加1是因为cnt是从1开始的,而First和Last是从0开始的			
				Fnum = cnt;			
				flag = 1;//表示已赋值 
				cnt = 1;//重新开始计数 
				segments++;
			}else{				
				Last = i-cnt + 1;
				Lnum = cnt;				
				cnt = 1;//重新计数 
				Llag = 1;
				segments++;
			} 
		if(Llag == 1 && flag == 1){
		sort(a,b,First,Fnum,Last,Lnum);
		flag = 0;//排完序之后标志值设为零 
		Llag = 0;
		copy(a,b);		
		}
				
		}
			
	}
    return segments;
}

int main()
{
	int a[8] = {3,4,7,5,9,6,10,2};//第一次排列数是3,4,7,8,1,5,2,6. //第二次成功调用是3,4,8,7,1,5,2,6 
	int b[8] = {0};
	int k = huafen(a,b,8); //对数组进行第一遍合并的时候同时获取子序列段数,根据子序列段数可以知道需要循环多少遍数组
	int bour;
	int i=0;//用于输出时做数组下标 
	int Num = 0;  //记录需要多少次扫描 
	float segments = k;
	while(segments>1){
		segments = segments/2;
		bour = segments;
		if(segments > bour){//应对子序列段是奇数的情况 
			bour++;
			segments = bour;
		}
		Num++;
	} 
	
	for(int i=0;i<Num-1;i++){
	   huafen(a,b,8);
	}
	while(b[i]){
		printf(" %d ",b[i++]);
	}
}


9.总结:先确定对象的特征,再研究确定每一个符合的对象,最后在确定对特殊对象的处理方式。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值