堆排序
概念
堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。
思路
1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换(相当于把最大的一个元素移除待排序范围),然后将除最后一个节点之外的剩下的节点重新构造成一个大顶堆;
3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
代码
#include <iostream> #include <algorithm> using namespace std; void max_heapify(int arr[], int start, int end) { //建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) //若子节点指标在范围内才做比较 { if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数 return; else //否则交换父子内容再继续子节点和孙节点比较 { swap(arr[dad], arr[son]); dad = son; son = dad * 2 + 1; } } } void heap_sort(int arr[], int len) { //初始化,i从最後一个父节点开始调整 for (int i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕 for (int i = len - 1; i > 0; i--) { swap(arr[0], arr[i]); max_heapify(arr, 0, i - 1); } } int main() { int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 }; int len = (int) sizeof(arr) / sizeof(*arr); heap_sort(arr, len); for (int i = 0; i < len; i++) cout << arr[i] << ' '; cout << endl; system("pause"); }
算法考题
TOPK
题目描述
输入一个长度为n的整数数列,从小到大输出前m小的数。
输入格式
第一行包含整数n和m。
第二行包含n个整数,表示整数数列。
输出格式
共一行,包含m个整数,表示整数数列中前m小的数。
思路分析
小顶堆的经典场景
C++ 中可以解除 priority_queue 去实现
代码
#include <iostream> #include <queue> using namespace std; int n,m,a; priority_queue<int, vector<int>, greater<int> > h; int main(){ scanf("%d%d",&n,&m); while(n--) scanf("%d",&a), h.push(a); while(m--) printf("%d ",h.top()) , h.pop(); return 0; }
最大线段重合问题
题目描述
给定很多线段,每条线段都有两个数组 [start, end],表示线段的开始位置和结束位置,左右都是闭区间。规定:
线段开始和结束位置一定都是整数值;
线段重合区域的长度必须 >=1 (比如(1,3) 和 (3,5) 不算重合,因为只有3这个点重合)
返回线段最多重合区域中,包含了几条线段。
思路分析
1、先根据每条线段的 start 进行从小到大的排序,然后准备一个小根堆,用于存放每条线段的 end。 【重合线段一定是以某条线段的 start 为左边界的】
2、流程
(1)假设第一条线段是 [1,7],先先问小根堆,弹出所有 <=1 的数,由于这是第一条线段,没有 <=1 的数,所以直接将 end = 7 放入小根堆中,此时小根堆中只有 7 这个数,所以 [1,7] 这条线段对应的答案是1;【解释:答案1的含义是如何重合区域必须以 [1,7] 的 1 为左边界的话,有多少条线段会越过这个 1 往右延伸】
(2)下一条线段 [2,3],start = 2,将小根堆中 <=2 的数都弹出,而当前小根堆中的数是 7,没有符合条件的数,所以将 end = 3放入小根堆中,此时小根堆中的数为 (3,7),所以 [2,3] 这条线段对应的答案是 2;【解释:之所以要将 <=2 的数弹出,也就是 end <= 2 的数弹出,因为这样的线段是无法越过2的,答案为2的意思是以[2,3] 的 2 为左边界,有多少条线段可以穿过来】
(3)下一条线段 [4,6],start = 4,小根堆(3,7),弹出 <= 4 的 3,然后将 end = 6 放入小根堆中,此时小根堆(6,7),size = 2,所以 [4,6] 这条线段对应的答案是 2;【解释:小根堆中的3被弹出,因为这条end = 3的线段无法穿过[4,6] 中的 4,所以要将它剔除,答案为2表示重合边界以 [4,6] 的 4 为左边界,有2条线段可以穿过来】
(4)下一条线段 [4,5], start = 4,小根堆(6,7),没有 <= 4 的数,直接将 end = 5 放入小根堆中,此时小根堆(5,6,7),size = 3,所以 [4,5] 这条线段对应的答案是 3;
3、总结:上述流程总结来说就是将所有线段以 start 从小到大进行排序后,依次考察每条线段,将小根堆中 <= start 的数弹出,然后将此时的 end 加入到小根堆中,此时小根堆的size 就是 当前这条线段与多少条线段重合的答案,所有答案中的最大值就是要求的结果。这就是在求以每条线段的 start 为重合区域的开始位置的情况下的所有答案。
代码
#include <iostream> #include <ctime> #include <cmath> #include <vector> #include <queue> #include <algorithm> using namespace std; struct Line { public: int start; int end; Line() {} Line(int s, int e) : start(s), end(e) { } bool operator<(const Line &rhs) const { return start < rhs.start; } }; struct CMP { bool operator()(const Line &l1, const Line &l2) const { return l1.start > l2.start; } }; int maxCover(vector<vector<int> > &arr) { vector<Line> lines(arr.size()); for (int i = 0; i < arr.size(); i++) { lines[i] = Line(arr[i][0], arr[i][1]); } sort(lines.begin(), lines.end()); //start从小到大排序 priority_queue<int, vector<int>, greater<int> > heap; //小根堆 int ans = 0; for (int i = 0; i < lines.size(); i++) { while (!heap.empty() && heap.top() <= lines[i].start) { heap.pop(); } heap.push(lines[i].end); ans = max(ans, (int)heap.size()); } return ans; } //不使用辅助数组 int maxCover2(vector<vector<int> > &arr) { //二维数组使用sort + lambda表达式进行排序,按照线段的start从小到大进行排序 sort(arr.begin(), arr.end(), [](const vector<int>& e1, const vector<int>& e2) {return e1[0] < e2[0]; } ); priority_queue<int, vector<int>, greater<int> > heap; int ans = 0; for (vector<int> line : arr) { while (!heap.empty() && heap.top() <= line[0]) { heap.pop(); } heap.push(line[1]); ans = max(ans, (int)heap.size()); } return ans; } //暴力解法 int maxCover3(vector<vector<int> > &lines) { int _min = INT_MAX; int _max = INT_MIN; for (int i = 0; i < lines.size(); i++) { _min = min(_min, lines[i][0]); _max = max(_max, lines[i][1]); } int cover = 0; for (double p = _min + 0.5; p < _max; p += 1) { int cur = 0; for (int i = 0; i < lines.size(); i++) { if (lines[i][0] < p && lines[i][1] > p) { cur++; } } cover = max(cover, cur); } return cover; } //生成随机数组 vector<vector<int> > generateLines(int n, int l, int r) { int size = (rand() % n) + 1; vector<vector<int> > ans(size, vector<int>(2)); for (int i = 0; i < size; i++) { int a = l + ((rand() % (r - l + 1))); int b = l + (rand() % (r- l + 1)); if (a == b) b = a + 1; ans[i][0] = min(a, b); ans[i][1] = max(a, b); } return ans; } int main() { srand(time(0)); Line l1(4, 9); Line l2(1, 4); Line l3(7, 15); Line l4(2, 4); Line l5(4, 6); Line l6(3, 7); priority_queue<Line, vector<Line>, CMP> heap; heap.push(l1); heap.push(l2); heap.push(l3); heap.push(l4); heap.push(l5); heap.push(l6); while (!heap.empty()) { Line cur = heap.top(); heap.pop(); cout << cur.start << ", " << cur.end << endl; } cout << "test begin" << endl; int n = 100; int l = 0; int r = 200; int testTimes = 200000; for (int i = 0; i < testTimes + 1; i++) { vector<vector<int> > lines = generateLines(n, l, r); int ans1 = maxCover(lines); int ans2 = maxCover2(lines); int ans3 = maxCover3(lines); if (ans1 != ans3 || ans2 != ans3) { cout << "Oops!!" << endl; break; } if (i && i % 1000 == 0) cout << i << " cases passed!" << endl; } cout << "test end" << endl; return 0; }