原题地址
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;
}