1035 插入与归并

在这里插入图片描述
输入样例 1:
10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
输出样例 1:
Insertion Sort
1 2 3 5 7 8 9 4 6 0

输入样例 2:
10
3 1 2 8 7 5 9 4 0 6
1 3 2 8 5 7 4 9 0 6
输出样例 2:
Merge Sort
1 2 3 8 4 5 7 9 0 6

分析:

  1. 首先写出插入与归并的所有过程序列(以输入样例为例),标记有序序列
    3 1 2 8 7 5 9 4 0 6
    插入排序
    第一轮 1 3 2 8 7 5 9 4 0 6
    第二轮 1 2 3 8 7 5 9 4 0 6
    第三轮 1 2 3 8 7 5 9 4 0 6
    第四轮 1 2 3 7 8 5 9 4 0 6
    第五轮 1 2 3 5 7 8 9 4 0 6
    第六轮 1 2 3 5 7 8 9 4 0 6
    第七轮 1 2 3 4 5 7 8 9 0 6
    第八轮 1 2 3 4 5 6 7 8 9 0
    第九轮 0 1 2 3 4 5 6 7 8 9
    3 1 2 8 7 5 9 4 0 6
    归并排序
    第一轮 1 3 2 8 5 7 4 9 0 6
    第二轮 1 2 3 8 4 5 7 9 0 6
    第三轮 1 2 3 4 5 7 8 9 0 6
    第四轮 0 1 2 3 4 5 6 7 8 9
  2. 观察发现,插入排序只有一个排好的有序序列,后面是未排序的元素
    因此,对于输入的中间序列,判断在有序序列之后的元素序列是否和原始序列的同位置部分相同;
    若相同,说明是插入排序;否则是归并排序
  3. 插入排序的实现较简单,找到无序序列第一个元素,插入到前面的有序序列中即可
  4. 归并排序实现,需要先找到当前序列的归并规模cnt(即当前是分别对cnt个元素排序)
    接着进行下一次排序,即规模变为2*cnt

思路:

  1. 根据分析2判断排序类型
  2. 若是插入排序,再排序一次即可,实现起来很简单,不多阐述
  3. 若是归并排序,需要根据分析4找归并的规模
    ①从首元素开始,计算第一个有序块的元素个数cnt
    ②符合题意的有序元素个数必须是2的幂(2, 4 ,8…),所以将cnt为最接近它的2的幂
    ③这样求得的cnt依然不是最终值,比如 1 2 3 4 5 7 8 9 0 6,按照②分析cnt应该是8,但cnt其实是4;所以如果后面还有包含cnt个元素的序列块,必须都满足有序,否则cnt减半;最后不足cnt个元素的有序块不做判断

总结:

  1. 唯一一个难点在于确定给出中间序列的归并规模,通过思路3,写出代码如下,该代码能通过牛客网的测试数据,但无法通过PTA的最后两个数据,原因稍后给出
#include <iostream>
#include <algorithm> 
using namespace std;
int num1[101], num2[101];
void InsertSort(int a[], int n, int k);
void MergeSort(int a[], int n);

int main() {
	int N; scanf("%d", &N);
	for(int i = 0; i < N; i++) 
		scanf("%d", &num1[i]);
	for(int i = 0; i < N; i++) 
		scanf("%d", &num2[i]);	
	int flag = true, k = 0;					//true 插入, false 归并 
	for(int i = 1; i < N; i++) 				//num2[k]值比前一个数小,它是第一个无序元素 
		if(num2[i] < num2[i-1])	{ k = i; break; }	
	for(int i = k; i < N; i++) 				//无序部分和原始无序部分 不完全相同,不是插入排序 
		if(num2[i] != num1[i]) { flag = false; break;}	
	if(1 == flag) InsertSort(num2, N, k);
	else MergeSort(num2, N);
} 

void InsertSort(int a[], int n, int k) {
	printf("Insertion Sort\n");
	sort(&a[0], &a[k+1]); 
	printf("%d", a[0]);
	for(int i = 1; i < n; i++) 
		printf(" %d", a[i]);
}
void MergeSort(int a[], int n) {
	printf("Merge Sort\n");
	int cnt = 0;					//cnt记录有序块的元素个数 
	for(int i = 1; i < n; i++) {	//找出递增元素个数,该值可能包含了其他序列中的元素
		cnt++; if(a[i] < a[i-1]) break;
	} 
	int sum = 1;
	for(int i = 0; ; i++) {			//cnt取值只能是2的幂 
		sum *= 2;
		if(cnt < sum*2) {cnt = sum; break;}
	}
	//第一个有序块的元素个数为cnt,需要判断后面的有序块是否都是cnt个有序元素
	if(n >= cnt*2) {				 
		int x = cnt, y = cnt * 2;  
		while(1) {
			int flag = true;					//flag 标识当前有序块元素是否为cnt 
			for(int i = x; i < y-1; i++) 		//判断第N个有序块的元素个数是否也是cnt
				if(a[i] > a[i+1]) { cnt /= 2; flag = false; break; }
			x = y; y = x + cnt;
			if(false == flag) { x = cnt, y = cnt*2;}	//当前cnt不满足,重新判断 
			if(y > n) break;							//不考虑最后一个个数不足cnt的有序块
		}
	}
	//当前有序块个数为cnt,如果至少有两个这样的有序块,那么下一步的有序块个数应该是cnt*2,忽略最后不足cnt*2的有序块 
	if(n >= cnt*2) {
		int x = 0, y = cnt*2;
		while(1) {
			sort(&a[x], &a[y]);
			x = y; y = x + cnt*2;
			if(y > n) break;
		}
	}
	else {
		sort(&a[0], &a[cnt*2]);
		sort(&a[cnt*2], &a[n]);
	}
	printf("%d", a[0]);
	for(int i = 1; i < n; i++) 
		printf(" %d", a[i]);
}

再次分析:

  1. 对于归并函数,确定当前归并规模是难点,思路3并不能考虑到所有的特殊序列
  2. 忽略了一个很坑的例子,即当前序列有可能和下一轮序列相等
    比如测试样例1的第5,6轮是一样的(插入排序),如果给出该列数据,无法确定处于第几轮;再比如
    1 2 3 4 5 6 7 8 4 3 2 1 6 5 8 7
    1 2 3 4 5 6 7 8 3 4 1 2 5 6 7 8
    1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
    1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
    无法确定到底是②还是③
    再特殊一点,如果给出的原始序列本就全有序,那么每一列中间序列都和原始序列一样
  3. 本题其实有一个较简单的解法,即模拟插入和归并,算出每一列中间序列,通过比较确定规模(然而我一开始嫌太暴力舍弃了)
  4. 通过3的思路,可以知道如果出现2中情况,PTA默认该序列第一次出现(即选②不选③)
  5. 根据4的思路,如果还是要计算cnt,就需要判断cnt/2是否符合条件,然而这是令人崩溃的
    一来,如果给出的原始序列本就全有序,那么光是判断cnt就要循环很多次
    二来,还是以1 2 3 4 5 6 7 8 4 3 2 1 6 5 8 7为例,计算出cnt为8(③),判断cnt为4是否符合(②),本例中符合,就再判断cnt为2是否符合,如果判断方法是每cnt个元素是否升序,那还是符合,这就还需要再和上一列对比,观察一个有序块内的元素是否移出该块
    综上,还是老老实实模拟排序吧,用sort实现真心不难

代码如下(PTA上AC)

#include <iostream>
#include <algorithm> 
using namespace std;
int num1[101], num2[101];
int My_equal(int a[], int b[], int n);
void MergeSort(int a[], int n, int cnt);
int main() {
	int N; scanf("%d", &N);
	for(int i = 0; i < N; i++) 
		scanf("%d", &num1[i]);
	for(int i = 0; i < N; i++) 
		scanf("%d", &num2[i]);	
	int flag = true, k = 0;					//true 插入, false 归并 
	for(int i = 1; i < N; i++) 				//num2[k]值比前一个数小,它是第一个无序元素 
		if(num2[i] < num2[i-1])	{ k = i; break; }	
	for(int i = k; i < N; i++) 				//无序部分和原始无序部分 不完全相同,不是插入排序 
		if(num2[i] != num1[i]) { flag = false; break;}
	if(1 == flag) {							//插入排序 
		printf("Insertion Sort\n");
		sort(&num2[0], &num2[k+1]); 
		printf("%d", num2[0]);
		for(int i = 1; i < N; i++) 
			printf(" %d", num2[i]);
	}
	else {									//归并排序 
		printf("Merge Sort\n");
		int cnt = 2, flag; 
		while(1) {
			MergeSort(num1, N, cnt);				//以cnt的规模,进行一次归并排序 
			flag = My_equal(num1, num2, N);
			cnt *= 2;
			if(flag == true) break;
		}
		MergeSort(num1, N, cnt);	
		printf("%d", num1[0]);
		for(int i = 1; i < N; i++) 
			printf(" %d", num1[i]);
	}
} 

//比较数组a和b是否相同 
int My_equal(int a[], int b[], int n) {
	for(int i = 0; i < n; i++) 
		if(a[i] != b[i]) return false;
	return true;
}
//模拟归并排序,规模为cnt 
void MergeSort(int a[], int n, int cnt) {
	int x = 0, y = cnt;
	while(1) {						//该循环对所有有序块 两两合并排序
		if(y > n) break;
		sort(&num1[x], &num1[y]);
		x = y; y += cnt;
		if(y > n && n-x > cnt/2) {sort(&a[x], &a[n]); }			//合并剩下的成对有序块 
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值