更多PAT甲级题解尽在我的个人博客--acking-you.github.io
题目
题目大意
- 有很多题目实际不需要看懂题目,只需要看懂输入和输出,比如这题。
此题虽然题目较为学术,且比较长,实际总结下来就是,通过给你一个原数组序列,还有一个用插入排序或者是堆排序排了几轮的数组序列,你要根据这个序列判断所使用的排序方式,并且再以该排序方式往下排一轮。
解题代码拆解
这次由于我使用的接口化函数设计,也就是传入指针进行操作,没有采用全局变量,所以直接input操作写在main函数里面,省空间。
关键的判断函数
isInsert()
设计思路:假设为插入排序后的序列,通过一次循环,找到下一个要被排序的元素位置,按理来说,没有被排序的位置应该和原数组的序列情况一致,如果不一致,则不是插入排序。
//用于确定是否为插入排序,顺便返回此时待插入处理的位置 int isInsert(int* nums, int len) { int i = 1; for (; i < len; i++) { if (nums[i - 1] > nums[i]) break; } for (int j = i; j < len; j++) { if (original[j] != nums[j]) return -1; } return i; }
堆排序和插入排序
插入排序
插入排序很简单,我这里直接写插入排序的每一轮处理函数来代替。
//插入排序的单步处理 void InsertSort(int* nums, int numSize, int i) { int j = i; int temp = nums[i]; for (; j > 0 && nums[j - 1] > temp; j--) { nums[j] = nums[j - 1]; } nums[j] = temp; }
堆排序
对于一个完整的堆排序,分为两个过程:堆化+维持堆化。
建立大根堆,每次堆化找到最大值,为了位置堆的结构和排序,将堆顶与最后一个元素交换,然后更新对的范围到0~i-1
再次堆化,又能找到这个堆中的最大值,长此以往,便完成了排序。
- 对于堆排序中的每一轮:堆排中的每一轮都是缩小堆的范围,并继续维持大根堆,在堆范围以外的元素就是被排好的元素。所以重点在于从后往前找到已经排到了哪个位置,再进行一次交换和堆化便可完成一轮排序。
用于固定范围堆化的函数:sift_down()
//堆化,得到大根堆 //对于从0开始编号的二叉堆: /* iparent = (i-1)/2, ilchild = i*2+1, irchild = i*2+2 */ void sift_down(int arr[], int start, int end) { // 计算父结点和子结点的下标 int parent = start; int child = parent * 2 + 1; while (child <= end) { // 子结点下标在范围内才做比较 // 先比较两个子结点大小,选择最大的 if (child + 1 <= end && arr[child] < arr[child + 1]) child++; // 如果父结点比子结点大,代表调整完毕,直接跳出函数 if (arr[parent] >= arr[child]) return; else { // 否则交换父子内容,子结点再和孙结点比较 swap(arr[parent], arr[child]); parent = child; child = parent * 2 + 1; } } }
不断堆化得到最大值进行排序:heap_sort()
void heap_sort(int arr[], int len) { // 从最后一个节点的父节点开始 sift down 以完成堆化 (heapify) for (int i = (len - 1 - 1) / 2; i >= 0; i--) sift_down(arr, i, len - 1); // 先将第一个元素和已经排好的元素前一位做交换,再重新调整(刚调整的元素之前的元素),直到排序完毕 for (int i = len - 1; i > 0; i--) { swap(arr[0], arr[i]); sift_down(arr, 0, i - 1); } }
确定堆排序到哪个位置的函数:findP
//用于找出堆排已经拍到了哪个位置的最大值。 int findP(int* nums, int len) { int i = 0; for (; i < len; i++) { int val = *max_element(nums, nums + len - i); if (nums[len - 1 - i] != val) break; } return len - 1 - i; }
整合代码进行提交
效率还行!
#include <bits/stdc++.h> using namespace std; int* original = nullptr; //插入排序的单步处理 void InsertSort(int* nums, int numSize, int i) { int j = i; int temp = nums[i]; for (; j > 0 && nums[j - 1] > temp; j--) { nums[j] = nums[j - 1]; } nums[j] = temp; } //堆排序 void sift_down(int arr[], int start, int end) { // 计算父结点和子结点的下标 int parent = start; int child = parent * 2 + 1; while (child <= end) { // 子结点下标在范围内才做比较 // 先比较两个子结点大小,选择最大的 if (child + 1 <= end && arr[child] < arr[child + 1]) child++; // 如果父结点比子结点大,代表调整完毕,直接跳出函数 if (arr[parent] >= arr[child]) return; else { // 否则交换父子内容,子结点再和孙结点比较 swap(arr[parent], arr[child]); parent = child; child = parent * 2 + 1; } } } // void heap_sort(int arr[], int len) { // // 从最后一个节点的父节点开始 sift down 以完成堆化 (heapify) // for (int i = (len - 1 - 1) / 2; i >= 0; i--) // sift_down(arr, i, len - 1); // // 先将第一个元素和已经排好的元素前一位做交换,再重新调整(刚调整的元素之前的元素),直到排序完毕 // for (int i = len - 1; i > 0; i--) { // swap(arr[0], arr[i]); // sift_down(arr, 0, i - 1); // } // } //用于找出堆排已经拍到了哪个位置的最大值。 int findP(int* nums, int len) { int i = 0; for (; i < len; i++) { int val = *max_element(nums, nums + len - i); if (nums[len - 1 - i] != val) break; } return len - 1 - i; } //用于确定是否为插入排序,顺便返回此时待插入处理的位置 int isInsert(int* nums, int len) { int i = 1; for (; i < len; i++) { if (nums[i - 1] > nums[i]) break; } for (int j = i; j < len; j++) { if (original[j] != nums[j]) return -1; } return i; } //统一打印结果函数 void print(int* nums, int len) { cout << nums[0]; for (int i = 1; i < len; i++) { cout << ' ' << nums[i]; } } int main() { //@输入处理 ios::sync_with_stdio(false); int N; cin >> N; int org[N], nums[N]; for (int i = 0; i < N; ++i) { cin >> org[i]; } for (int i = 0; i < N; ++i) { cin >> nums[i]; } //@根据不同的排序方式进行答案的打印 original = org; int flag = isInsert(nums, N); if (flag != -1) { cout << "Insertion Sort" << endl; InsertSort(nums, N, flag); print(nums, N); } else { cout << "Heap Sort" << endl; int pos = findP(nums, N); swap(nums[0], nums[pos]); sift_down(nums, 0, pos - 1); print(nums, N); } return 0; }