一些课上没有涉及的数据结构和算法其实可以快速的解题,但很多同学因为没有了解过而用很基础的算法写了复杂的代码,在考场上常因为debug困难并且烧脑拿不到分。本文希望速通一些很常用的算法,并且给出往年的考题和作业题来说明它的应用。
一、优先队列
在队列中,元素从队尾进入,从队首删除。相比队列,优先队列里的元素增加了优先级的属性,优先级高的元素先被删除。优先队列的内部是用堆实现的。(如果不了解堆可以忽略这句话)。
它的实现很简单,和队列极为相似,可以迅速上手。
优先队列会默认把较大的数据放在队首。
#include <iostream>
#include <queue>
using namespace std;
int main() {
priority_queue<int> pq;
pq.push(1); pq.push(2); pq.push(3);
while (!pq.empty()) {
cout << pq.top() << endl;
pq.top();
}
return 0;
}
想要把较小的数据放在前面需要声明的时候写成这样:这个写法的含义是这个优先队列里装int,优先队列内部用vector存储,比较方式是greater。
priority_queue <int, vector<int>, greater<int> > pq;
优先队列的优先级重载:这是很常用的。这个例子中定义距离值较小的node优先级较高。(因为dist更大的较小)
struct node {
int dist, loc;
node () { }
bool operator < (const node &a) const {
return dist > a.dist;
}
};
priority_queue<node> pq;
题目1:【期末模拟测试】拼装模型
题目描述
Dzx从日本回来了,并为TN准备了礼物----一个恐龙模型。TN想把它尽快拼好,但是由于模型很庞大,TN又实在比较懒,所以他希望你为他寻找一个最节省时间的拼装方案。
模型是由N个零件组成的,每次TN可以选取两个零件拼装在一起来组成一个新的零件,直到得到完整的模型。由于零件的复杂程度不同,TN每次拼装所需要的时间也是不同的,对于两个零件A和B,假设他们的复杂程度分别为a和b,则TN要将这两个零件拼装在一起所需要的时间为a+b,而这由两个零件所组成的新零件的复杂程度为a+b。
现在TN已经统计出了每个零件的复杂程度,你能告诉他最快的拼装方法需要多少时间么?
提示信息
每次都选择当前最小的两个零件。
关于输入
Line 1: N (1 <= N <= 10000),零件数目
Line 2: N Integers,表示每个零件的复杂程度
关于输出
最快的拼装方案所需要的时间
例子输入
3 1 2 9
例子输出
15
【题目分析】一种方法是每次都选择最少的两个元素,删除后把他们的和放回数组,再排序。这道题数据的范围不超过10000,时间复杂度O(nlogn)可以实现,但是当然有更好的解法,就是优先队列。这道题其实是优先队列最最经典的一道题,叫“合并果子”,甚至连样例都没有改。(不知道老师为什么会在这个时候考察这道题)
下面贴出合并果子的题面,其实和本题完全相同:
我们的想法就是每次取最小的两个移出堆,把它们的和加进答案以后再放进堆。
【代码实现】
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int w[1005];
priority_queue<int, vector<int>, greater<int> > pq;
int main() {
int n, ans = 0;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> w[i];
pq.push(w[i]);
}
while (pq.size() >= 2) {
int x = pq.top();
pq.pop();
x += pq.top();
pq.pop();
ans += x;
pq.push(x);
}
cout << ans << endl;
return 0;
}
题目2:【链表】链表合并排序
现在给出两个整数数列,先要将其合并为一个数列,并且合并后整个数列有序(从小到大),例如:
数列1: 3 1 2 5 9
数列2: 8 4 7 10 11
合并后得到数列:1 2 3 4 5 7 8 9 10 11
输入包括4行
第一行是1个整数N(N<=5000),表示数列1包含的整数个数
第二行是N个整数,表示数列1中的整数
第三行是1个整数M(M<=5000),表示数列2包含的整数个数
第四行是M个整数,表示数列2中的整数
【题目分析】虽然题目要用链表,但显然可以用优先队列,用优先队列最基础的功能函数即可。
【代码实现】
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
priority_queue <int, vector<int>, greater<int> > pq;
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
pq.push(x);
}
cin >> m;
for (int i = 1; i <= m; i++) {
int x;
cin >> x;
pq.push(x);
}
cout << pq.top();
pq.pop();
while (!pq.empty()) {
cout << " " << pq.top();
pq.pop();
}
return 0;
}
二、二分查找
这其实是用来降低复杂度的,基本思想大概在小学奥数时我们就接触过,每次找中间位置mid,然后看我们需要的数在[l, mid-1]还是正好mid还是[mid+1,r]区间中。
【模版】:查找a数组中是否有元素x,如果能找到返回数组下标
int find_x (int a[], int x, int n) { // n是a数组的元素个数,a数组下标从0开始且已经升序排好
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (a[mid] == x) return mid;
else if (a[mid] < x) l = mid + 1;
else r = mid - 1;
}
return -1; // 没有找到
}