输入样例 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
分析:
- 首先写出插入与归并的所有过程序列(以输入样例为例),标记有序序列
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 - 观察发现,插入排序只有一个排好的有序序列,后面是未排序的元素
因此,对于输入的中间序列,判断在有序序列之后的元素序列是否和原始序列的同位置部分相同;
若相同,说明是插入排序;否则是归并排序 - 插入排序的实现较简单,找到无序序列第一个元素,插入到前面的有序序列中即可
- 归并排序实现,需要先找到当前序列的归并规模cnt(即当前是分别对cnt个元素排序)
接着进行下一次排序,即规模变为2*cnt
思路:
- 根据分析2判断排序类型
- 若是插入排序,再排序一次即可,实现起来很简单,不多阐述
- 若是归并排序,需要根据分析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个元素的有序块不做判断
总结:
- 唯一一个难点在于确定给出中间序列的归并规模,通过思路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]);
}
再次分析:
- 对于归并函数,确定当前归并规模是难点,思路3并不能考虑到所有的特殊序列
- 忽略了一个很坑的例子,即当前序列有可能和下一轮序列相等
比如测试样例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的思路,可以知道如果出现2中情况,PTA默认该序列第一次出现(即选②不选③)
- 根据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]); } //合并剩下的成对有序块
}
}