题目描述
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.
Heap sort divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving that to the sorted region. it involves the use of a heap data structure rather than a linear-time search to find the maximum.
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 “Heap 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 resulting 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 6 0
6 4 5 1 0 3 2 7 8 9
Sample Output 2:
Heap Sort
5 4 3 1 0 2 6 7 8 9
题目大意
根据维基百科:
插入排序迭代,每一次重复消耗一个输入元素,并且增加有序输出序列的长度。每一次迭代,插入排序从输入数据中移除一个元素,再从有序列表中找到这个元素应在的位置,然后将它插入。重复此过程直到没有输入元素为止。
堆排序是把输入分成有序部分和无序部分,通过提取最大元素并将其移动到有序序列部分,来迭代地缩短无序序列。它牵扯到堆数据结构的使用,而不是线性时间地去查找最大值。
现在给定初始的整数序列,和用某种方法迭代了几步之后的序列,你能识别出使用了哪种排序方法吗?
输入说明
每一个输入文件包含一个测试用例。对于每一个测试用例,第一行是一个正整数N(≤ 100)。下一行是N个整数初始序列,最后一行是N个部分排序序列。假设目标序列总是递增的。一行里的所有数都用空格分隔。
输出说明
对于每一个测试用例,第一行输出Insertion Sort
或者Heap Sort
来表明部分有序序列是采用哪种方法得到的。然后再运行这种方法迭代一次,最后在下一行输入迭代结果。保证每一个测试用例的答案是唯一的。一行里的所有数字必须用空格分隔,并且在行尾不能有多余的空格。
题目分析
写两个函数,一个用来实现插入排序,一个用来实现堆排序。题目中说目标序列是递增,据此我们可以推断出输入的序列中不会有重复元素,否则不可能得到递增序列,只能得到非递减序列。题目中还说每个测试用例的答案是唯一的,因此N
一定是大于等于3的,有兴趣的读者可以尝试用反证法进行证明。测试用例中的部分排序序列一定是由初始序列经过至少一次迭代得到的,这在题目中也有很明确的说明,其实这个地方也可以从另一个角度推断出来,若部分排序序列直接就是初始序列,未经过任何迭代,那么你就无法判断是用插入排序还是堆排序。最后一点,部分排序序列一定还能继续迭代,因为题目中说了要输出再迭代一步之后的结果,从另一个角度看,若部分排序序列不能继续迭代,也就是说部分排序序列就是一个递增序列,那么也无法判断采用的是哪种排序方法。
挖掘出了上述的四条信息,我们可以根据部分排序序列的第1个元素和第2个元素(从1开始编号)的大小关系来判断是哪种排序方法。若part[1] < part[2]
,说明是插入排序,否则是堆排序。处理插入排序时,可以用sort()
函数模拟插入过程,比如说要将第i
个元素插入到已排好序的前i-1
个元素中,可以直接对前i
个元素进行排序以模拟插入操作。堆排序套用向下调整的模板即可。
源代码
#include <bits/stdc++.h>
using namespace std;
vector<int> ini(105);//初始序列
vector<int> part(105);//部分有序序列
int n;
//向下调整
void downAdjust(int low, int high) {
int i = low;
int j = 2 * i;
while (j <= high) {
if (j + 1 <= high && ini[j+1] > ini[j])
j++;
if (ini[j] > ini[i]) {
swap(ini[j], ini[i]);
i = j;
j = 2 * i;
}
else
break;
}
}
//插入排序
void InsertionSort() {
for (int i = 2; i <= n; i++) {
sort(ini.begin() + 1, ini.begin() + 1 + i);
if (ini == part) {//当前的部分排序序列和输入的是否相同
cout << "Insertion Sort" << endl;
//one more iteration
sort(ini.begin() + 1, ini.begin() + 1 + i + 1);
return;
}
}
}
//堆排序
void HeapSort() {
//create heap
for (int i = n / 2; i >= 1; i--)
downAdjust(i, n);
for (int i = n; i >= 2; i--) {
swap(ini[1], ini[i]);
downAdjust(1, i - 1);
if (ini == part) {
cout << "Heap Sort" << endl;
//one more iteration
swap(ini[1], ini[i - 1]);
downAdjust(1, i - 2);
return;
}
}
}
int main()
{
cin >> n;
//数组的0号元素不用,从1号元素开始
for (int i = 1; i <= n; i++)
cin >> ini[i];
for (int i = 1; i <= n; i++)
cin >> part[i];
if (part[1] < part[2])
InsertionSort();
else
HeapSort();
//output
for (int i = 1; i <= n - 1; i++)
cout << ini[i] << " ";
cout << ini[n] << endl;
}
代码优化
先看下面这个测试用例。
10
3 1 2 8 7 5 9 4 6 0
6 4 5 1 0 3 2 7 8 9
我们可以很容易的判断出来它用的是堆排序,那么再想一下,它已经进行到堆排序的哪一步了?很明显,后三个元素7 8 9
是已经排好序了的,因为他们都比堆顶元素6
大。下一次的迭代过程就是交换堆顶元素6
和2
,得到2 4 5 1 0 3 6 7 8 9
,再对堆顶元素进行向下调整。明白了这一点,我们可以省去建堆过程,省去从初始序列3 1 2 8 7 5 9 4 6 0
迭代到6 4 5 1 0 3 2 7 8 9
的过程,,而是直接对部分排序序列6 4 5 1 0 3 2 7 8 9
进行一次迭代,之后输出即可。
以上是堆排序的情况,对于插入排序,处理起来会稍微复杂一点。先看下面的测试用例。
5
2 4 6 5 3
2 4 6 5 3
我们仍然可以很轻松的判断出它采用的是插入排序,问题在于判断它处于第几步迭代。若采用普通的插入排序来处理这个输入,在进行了一步迭代之后得到的序列是2 4 6 5 3
,它与部分排序序列相等,因此我们可以判断出下一步迭代应该是将第三个元素6
插入到2 4
中。据此可以得到一般性的结论,对于插入排序,若初始序列和部分排序序列完全相同,那么部分排序序列是由初始序列经过一步迭代得到的。
再看下面这个初始序列和部分排序序列不相同的情况。
10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0
这两个序列从后往前看,对应位置进行比较,找到最后一个两序列对应元素相等的位置,记为index
。对于上面的两序列,找到的位置应该是6(从1开始编号),两序列在6号位置的元素都是5
。接下来对部分排序序列的[1,index]区间的元素排序,得到的结果即为题目中所要的输出。
优化后的代码
#include <bits/stdc++.h>
using namespace std;
vector<int> ini(105);//初始序列
vector<int> part(105);//部分有序序列
int n;
//向下调整
void downAdjust(int low, int high) {
int i = low;
int j = 2 * i;
while (j <= high) {
if (j + 1 <= high && part[j + 1] > part[j])
j++;
if (part[j] > part[i]) {
swap(part[j], part[i]);
i = j;
j = 2 * i;
}
else
break;
}
}
int main()
{
cin >> n;
//数组的0号元素不用,从1号元素开始
for (int i = 1; i <= n; i++)
cin >> ini[i];
for (int i = 1; i <= n; i++)
cin >> part[i];
if (part[1] < part[2]) {//插入排序
cout << "Insertion Sort" << endl;
if (ini == part)
sort(part.begin() + 1, part.begin() + 1 + 3);
else {
int index = n;
while (part[index] == ini[index])
index--;
sort(part.begin() + 1, part.begin() + 1 + index + 1);
}
}
else {//堆排序
cout << "Heap Sort" << endl;
int index = 2;
while (part[1] > part[index])
index++;
swap(part[1], part[index - 1]);
downAdjust(1, index - 2);
}
//output
for (int i = 1; i <= n - 1; i++)
cout << part[i] << " ";
cout << part[n] << endl;
}