菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
376
0

Insert or Merge

原创
05/13 14:22
阅读数 75272

Insert or Merge

 According to Wikipedia:

Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.

Merge sort works as follows: Divide the unsorted list into N sublists, each containing 1 element (a list of 1 element is considered sorted). Then repeatedly merge two adjacent sublists to produce new sorted sublists until there is only 1 sublist remaining.

Now given the initial sequence of integers, together with a sequence which is a result of several iterations of some sorting method, can you tell which sorting method we are using?

Input Specification:

Each input file contains one test case. For each case, the first line gives a positive integer N (≤ 100). Then in the next line, N integers are given as the initial sequence. The last line contains the partially sorted sequence of the N numbers. It is assumed that the target sequence is always ascending. All the numbers in a line are separated by a space.

Output Specification:

For each test case, print in the first line either "Insertion Sort" or "Merge Sort" to indicate the method used to obtain the partial result. Then run this method for one more iteration and output in the second line the resuling sequence. It is guaranteed that the answer is unique for each test case. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.

Sample Input 1:

10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0

Sample Output 1:

Insertion Sort
1 2 3 5 7 8 9 4 6 0

Sample Input 2:

10
3 1 2 8 7 5 9 4 0 6
1 3 2 8 5 7 4 9 0 6

Sample Output 2:

Merge Sort
1 2 3 8 4 5 7 9 0 6

 

解题思路

  这道题关键在于判断给定的序列是按照插入排序还是归并排序排好的。第一次做的时候没有去细想判断用哪种排序算法的方法,而是直接先用归并排序来模拟一遍,如果不是归并那么自然就是插排了。当然这种做法的会消耗很多时间,是一种不聪明的做法。

  这种思路是这样的,先模拟一遍归并排序,每次归并后都与给定的部分排好序的数组比较,如果发现在某次归并后的顺序与之相同,那么给定序列是按照归并排序来排的,再做多一次归并排序,然后打印输出。如果归并排序都做完了,在排序的过程中并没有与给定的部分排好序的数组相同的序列,那就说明不是按照归并排序来的,而是插入排序。接下来就好办了,在给定的部分排好序的数组中,找到已部分排好序的最后一个元素的下标位置,然后再做一次插入排序,打印输出。

  AC代码如下,并不推荐这种做法。

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 void insertionSort(int *ret, int n);
 5 bool mergeSort(int *a, int *ret, int n);
 6 
 7 int main() {
 8     int n;
 9     scanf("%d", &n);
10     int a[n], ret[n];
11     for (int i = 0; i < n; i++) {
12         scanf("%d", a + i);
13     }
14     for (int i = 0; i < n; i++) {
15         scanf("%d", ret + i);
16     }
17     
18     if (mergeSort(a, ret, n)) { // 如果是归并排序,返回true 
19         puts("Merge Sort");
20     }
21     else {                      // 否则,是插入排序 
22         puts("Insertion Sort");
23         insertionSort(ret, n);  // 再进行一次插排 
24     }
25     for (int i = 0; i < n; i++) {
26         if (i) putchar(' ');
27         printf("%d", ret[i]);
28     }
29     
30     return 0;
31 }
32 
33 void insertionSort(int *ret, int n) {
34     bool flag = true;               // true表示还没有元素交换过,表明还没有找到部分排好序的最后一个元素的下标 
35     for (int i = 1; i < n && flag; i++) {
36         int tmp = ret[i], j = i;
37         for ( ; j > 0 && tmp < ret[j - 1]; j--) {
38             ret[j] = ret[j - 1];
39             flag = false;           // 有元素进行交换,表明这是额外一次的插排,flag改写为false,不会再进行下一次的插排 
40         }
41         ret[j] = tmp;
42     }
43 }
44 
45 bool mergeSort(int *a, int *ret, int n) {
46     bool flag = false; 
47     int *tmpA = new int[n];
48     for (int len = 1; len < n; len *= 2) {
49         int right, rightEnd;
50         for (int left = 0; left < n - len; left = rightEnd) {
51             right = left + len, rightEnd = std::min(right + len, n);
52             std::merge(a + left, a + right, a + right, a + rightEnd, tmpA + left);
53         }
54         std::copy(tmpA, tmpA + rightEnd, a);
55         
56         if (flag == false) {                // 如果上次归并结果与给定的部分排好序的数组不相同 
57             int i = 0;
58             for ( ; i < n; i++) {           // 与给定的部分排好序的数组的每一个元素都比较一次 
59                 if (a[i] != ret[i]) break;  // 发现有不同的就退出 
60             }
61             if (i == n) flag = true;        // 如果发现某次归并后,相应的序列与给定的部分排好序的数组相同,flag改写为true 
62         }
63         else {
64             std::copy(a, a + n, ret);       // 把额外一次的归并结果拷贝到ret数组中 
65             break;
66         }
67     }
68     delete[] tmpA;
69     
70     return flag;
71 }

  下面这种思路是参考柳神的,大佬就是大佬,真的太妙了。

  先放出柳神的代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 using namespace std;
 4 int main() {
 5     int n, a[100], b[100], i, j;
 6     cin >> n;
 7     for (int i = 0; i < n; i++)
 8         cin >> a[i];
 9     for (int i = 0; i < n; i++)
10         cin >> b[i];
11     for (i = 0; i < n - 1 && b[i] <= b[i + 1]; i++);
12     for (j = i + 1; a[j] == b[j] && j < n; j++);
13     if (j == n) {
14         cout << "Insertion Sort" << endl;
15         sort(a, a + i + 2);
16     } else {
17         cout << "Merge Sort" << endl;
18         int k = 1, flag = 1;
19         while(flag) {
20             flag = 0;
21             for (i = 0; i < n; i++) {
22                 if (a[i] != b[i])
23                     flag = 1;
24             }
25             k = k * 2;
26             for (i = 0; i < n / k; i++)
27                 sort(a + i * k, a + (i + 1) * k);
28             sort(a + n / k * k, a + n);
29         }
30     }
31     for (j = 0; j < n; j++) {
32         if (j != 0) printf(" ");
33         printf("%d", a[j]);
34     }
35     return 0;
36 }

关于柳神的具体思路可以参考:https://www.liuchuo.net/archives/1902

  这里主要讲讲如何判断是插入排序还是归并排序,当然这种方法是参考柳神的。

  就是先在给定的部分排好序的数组中,找到有序的最后一个元素的下标位置。然后再从下一个位置开始,将部分排好序的数组与原始数组相应的位置比较。我们知道,如果是插排的话,那么后面的元素都是相同的。所以如果有一个元素不相同就说明不是插排,而是归并了。是的,这就判断是哪种排序算法的方法。接下来,如果是插排,因为我们已经找到有序的最后一个元素的下标位置,所以直接再进行一次插入排序就可以了。而归并则需要从头开始模拟模拟一遍,与上面的方法一样。

  这里给出之前代码的改进版:

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 void mergeSort(int *a, int *ret, int n);
 5 
 6 int main() {
 7     int n;
 8     scanf("%d", &n);
 9     int a[n], ret[n];
10     for (int i = 0; i < n; i++) {
11         scanf("%d", a + i);
12     }
13     for (int i = 0; i < n; i++) {
14         scanf("%d", ret + i);
15     }
16     
17     int i, j;
18     for (i = 0; i < n - 1 && ret[i] <= ret[i + 1]; i++);// 在给定的部分排好序的数组中找到有序的最后一个元素的下标位置 
19     for (j = i + 1; j < n && a[j] == ret[j]; j++);      // 从下一个位置开始,将部分排好序的数组与原始数组每一个元素进行比较
20     if (j == n) {                                       // 如果每一个元素都相同,说明是插入排序    
21         puts("Insertion Sort");
22         std::sort(ret, ret + i + 2);                    // 实际上并不需要特意写一个插入排序 
23     }
24     else {                                              // 否则是归并排序 
25         puts("Merge Sort");
26         mergeSort(a, ret, n);
27     }
28     for (int i = 0; i < n; i++) {
29         if (i) putchar(' ');
30         printf("%d", ret[i]);
31     }
32     
33     return 0;
34 }
35 
36 void mergeSort(int *a, int *ret, int n) {
37     bool flag = false;
38     int *tmpA = new int[n];
39     for (int len = 1; len < n; len *= 2) {
40         int right, rightEnd;
41         for (int left = 0; left < n - len; left = rightEnd) {
42             right = left + len, rightEnd = std::min(right + len, n);
43             std::merge(a + left, a + right, a + right, a + rightEnd, tmpA + left);
44         }
45         std::copy(tmpA, tmpA + rightEnd, a);
46         
47         if (flag == false) {
48             int i = 0;
49             for ( ; i < n; i++) {
50                 if (a[i] != ret[i]) break;
51             }
52             if (i == n) flag = true;
53         }
54         else {
55             std::copy(a, a + n, ret);
56             break;
57         }
58     }
59     delete[] tmpA;
60 }

 

参考资料

  1089. Insert or Merge (25)-PAT甲级真题:https://www.liuchuo.net/archives/1902

发表评论

0/200
376 点赞
0 评论
收藏