数据结构-2020夏 09-排序2 Insert or Merge (25分) 巧妙判断归并段长度

原题地址

https://pintia.cn/problem-sets/1268384564738605056/problems/1289169858763866113

解题思路

自己第一次做的时候是硬做,也就是分别执行两种排序,然后判断哪个排序在哪一步和题目给的序列A(排了一点的那个序列)一毛一样,之后再执行一次相应的排序。

但是这种办法就显得比较粗暴哈哈哈!(也是能过的)

上课之后我决定我要变得聪明!建议观看姥姥的讲解视频~方法很巧妙!

主要分为两步:

1.判断是哪种排序

按照姥姥的话"挑软柿子捏",所以我们来判断是否是插入排序,不是的话就是归并~

首先遍历序列A,发现哪一步开始不有序了就跳出,然后记住这个跳出的位置(方便接下来的操作),检查此位置后面是否和原始序列一样。如果一样的话就是插入排序,不一样则是归并!

2.再进行一次该种排序

如果1的判断结果是插入排序,那么就舒服了,我们1的时候已经记住了开始不有序的位置,那么就接着这个位置再进行一次插入排序就好了!

如果结果是归并排序就有点麻烦,因为要找归并段的长度

姥姥讲解了一个很好的办法,就是首先认为当前的归并段长度至少为len(这个值从2开始,因为至少执行了一次归并),然后去判断是否有可能是len*2。判断成立的条件是:当前len下,每两个归并段之间衔接的两个元素是有序的

这个可能不太好理解,上个图:

图源姥姥的课
(图源姥姥的课)

这是认为当前len至少为2,判断是否可能是4。可以看到,图中每两个归并段之间衔接的前后两个元素都是有序的,所以len就至少为4~这样一直操作下去就好了,直到不满足这个条件,那么当前的len就是这个len

注意事项

1.写归并排序的相关代码的时候要注意是只执行一次(我用了递推的写法)
2.判断归并段长度的时候要注意循环条件怎么写~

参考代码

(弄了半天终于过了呜呜呜)

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
typedef double db;
typedef long long LL;
typedef vector<int> VI;
const int inf = 2e9;
const LL INF = 8e18;
const int maxn = 5e5 + 5;

//归并排序合并两个子列 
void merge(int A[], int tmpA[], int l, int r, int rend) {
	int tmp = l, num = rend - l + 1, lend = r - 1;
	
	while (l <= lend && r <= rend) {
		if (A[l] < A[r]) {
			tmpA[tmp++] = A[l++];
		} else tmpA[tmp++] = A[r++];
	}
	while (l <= lend) tmpA[tmp++] = A[l++];
	while (r <= rend) tmpA[tmp++] = A[r++];
	for (int i = 0; i < num; i++, rend--) {
		A[rend] = tmpA[rend];
	}
}

int main() {
	int n;
	scanf("%d", &n);
	int* A = (int*)malloc(n * sizeof(int));
	int* B = (int*)malloc(n * sizeof(int));
	
	for (int i = 0; i < n; i++) {
		scanf("%d", &B[i]);
	}
	for (int i = 0; i < n; i++) {
		scanf("%d", &A[i]);
	}
	
	bool isInsert = true;
	int p;
	for (p = 1; p < n; p++) {
		if (A[p] < A[p - 1]) break;  //保留跳出的位置 
	}
	for (int i = p; i < n; i++) {
		if (A[i] != B[i]) { 
			isInsert = false;  //后面序列不一致说明不是插入排序 
			break;
		}
	}
	if (isInsert) {
		printf("Insertion Sort\n");
		int tmp = A[p];
		int i;
		//若是插入排序就接着刚才的位置进行操作 
		for (i = p; i > 0 && tmp < A[i - 1]; i--) {
			A[i] = A[i - 1];
		}
		A[i] = tmp;
	} else {
		printf("Merge Sort\n");
		int len;  //len为当前猜测的归并段的长度,至少为2 
		bool flag = true;
		for (len = 2; len <= n; len *= 2) {
			for (int k = len; k < n; k += len * 2) {  //这里注意是k += len*2!!!下一个归并段间隔是len*2 
				//判断有没有可能是len*2
				if (A[k] < A[k - 1]) {  //如果是的话,则当前每两个归并段前后相邻的两个元素应保持A[k - 1] < A[k] 
					len << 1;  //不是,则当前为len,下一步的len是当前的两倍 
					flag = false;
					break;
				}
			}
			if (!flag) break;
		}
		//再执行一步归并排序的操作
		//递推写法 
		int* tmpA = (int*)malloc(n * sizeof(int));
		int i;
		for (i = 0; i <= n - len * 2; i += len * 2) {
			merge(A, tmpA, i, i + len, i + len*2 - 1);
		}
		if (i + len < n) {  //若最后还有2个子列 
			merge(A, tmpA, i, i + len, n - 1);
		}  //若最后不足2个,保持原状 
	}
	
	for (int i = 0; i < n; i++) {
		if (i) printf(" ");
		printf("%d", A[i]);
	}	
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值