机试准备第16天
今天进入搜索的学习。搜索问题一般较为复杂,状态空间很大,使用规划过的遍历/枚举,不重复或不遗漏的找到解决问题。有广度优先搜索以及深度优先搜索。广度优先搜索访问某节点时,将其邻居置为待访问节点。广度优先遍历一般用于寻找最优解。层序遍历本质上就是广度优先遍历,BFS的核心数据结构是队列。深度优先遍历是一条路走到黑,实现DFS就是从大问题到小问题,知道最小问题,用递归或分治法实现。
第一题是树的高度。把这棵树看成图的特例,从根出发使用BFS遍历。
#include <iostream>
#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;
int main(){
int n,m;
scanf("%d%d", &n,&m);//编号是1,2,3....n
vector<vector<int>> tree(n+1);//tree[0]没用
//tree[1]~tree[n] 对应不同编号的节点
for(int i = 0;i<n-1;i++){
int u, v;
scanf("%d%d", &u, &v);
tree[u].push_back(v);
tree[v].push_back(u);
}
queue<int> Tovisit;//用来做BFS的队列
vector<int> distance(n+1);//用来判断某个节点是否访问过
//记录根节点到这个节点的最短距离
for(int i = 1;i<=n;i++){
distance[i] = -1;
}
distance[m] = 0;//根节点到根节点的距离是0
int maxdis = 0;
Tovisit.push(m);
while(!Tovisit.empty()){
int cur = Tovisit.front();
Tovisit.pop();
for(int i = 0;i<tree[cur].size();i++){
int child = tree[cur][i];
if(distance[child]!=-1){
continue;
}
Tovisit.push(child);
distance[child] = distance[cur]+1;
maxdis = distance[child];
}
}
printf("%d", maxdis);
return 0;
}
第二题是玛雅人的密码。使用map结构避免重复。
#include <stdio.h>
#include <string>
#include <queue>
#include <unordered_map>//时间复杂度O(1)
using namespace std;
int main(){
char strarr[20] = {0};
int n;
scanf("%d", &n);
scanf("%s", strarr);
if(n < 4) {
printf("-1");
return 0;
}
string str = strarr;
queue<string> myqueue;
myqueue.push(str);
unordered_map<string , int> dismap;
dismap.insert({str, 0});
while(!myqueue.empty()){
string cur = myqueue.front();
if(cur.find("2012")!=string::npos){
printf("%d\n", dismap[cur]);
break;
}
myqueue.pop();
for(int i = 0;i<cur.size()-1;i++){
string next = cur;
char temp = next[i];
next[i] = next[i+1];
next[i+1] = temp;
if(dismap.count(next) == 0){
myqueue.push(next);
dismap.insert({next, dismap[cur]+1});
}
}
}
if(myqueue.empty()) printf("-1\n");
}
第三题是走路还是走公交,使用map指示当前pos以及到达pos的最小时间。太多map.count导致超时了。
#include <stdio.h>
#include <queue>
#include <unordered_map>
using namespace std;
int main(){
int n,k;
while(scanf("%d%d", &n, &k)!= EOF){
queue<int> pos;//位置队列
pos.push(n);
unordered_map<int, int> res;//位置 时间
res.insert({n, 0});
while(!pos.empty()){
int curpos = pos.front();//当前位置
pos.pop();
if(curpos == k){
printf("%d\n", res[curpos]);
break;
}
else{
if(res.count(curpos+1) == 0&&(curpos+1)<=100000&&curpos<k){
res.insert({curpos+1, res[curpos]+1});
pos.push(curpos+1);
}
if(res.count(curpos-1) == 0&&(curpos-1)>=0){
res.insert({curpos-1, res[curpos]+1});
pos.push(curpos-1);
}
if(res.count(curpos*2) == 0&&curpos*2<=100000&&curpos<k){
res.insert({curpos*2, res[curpos]+1});
pos.push(curpos*2);
}
}
}
}
return 0;
}
#include <stdio.h>
#include <queue>
#include <unordered_map>
using namespace std;
const int MAX = 100000;
// 我们可以尝试用数组去替代unordered_map
bool visited[MAX + 1]; // 使用数组来记录位置是否被访问过,下标表示位置,值表示是否访问,初始化为false
int steps[MAX + 1]; // 使用数组来记录到达每个位置的步数,初始化为0
int main() {
int n, k;
while (scanf("%d%d", &n, &k) != EOF) {
queue<int> pos;
pos.push(n);
visited[n] = true; // 标记起始位置已访问
steps[n] = 0; // 起始位置步数为0
while (!pos.empty()) {
int curpos = pos.front();
pos.pop();
if (curpos == k) {
printf("%d\n", steps[curpos]);
break;
}
else {
// 当前位置+1是否在范围内且未被访问过
if (curpos + 1 <= MAX &&!visited[curpos + 1]) {
visited[curpos + 1] = true;
steps[curpos + 1] = steps[curpos] + 1;
pos.push(curpos + 1);
}
// 当前位置-1是否在范围内且未被访问过
if (curpos - 1 >= 0 &&!visited[curpos - 1]) {
visited[curpos - 1] = true;
steps[curpos - 1] = steps[curpos] + 1;
pos.push(curpos - 1);
}
// 当前位置*2是否在范围内、未被访问过且乘2操作合理(避免超过目标位置且浪费计算)
if (curpos * 2 <= MAX &&!visited[curpos * 2] && curpos <= k) {
visited[curpos * 2] = true;
steps[curpos * 2] = steps[curpos] + 1;
pos.push(curpos * 2);
}
}
}
// 重置数组状态
for (int i = 0; i <= MAX; ++i) {
visited[i] = false;
steps[i] = 0;
}
}
return 0;
}
第四题是逃离迷宫。给了,这题是真不会。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <limits.h>
#include <queue>
using namespace std;
struct Node {
int x;
int y;
int direct; // 1,2,3,4 四个方向
int turn; // 已经转弯的次数
Node(int _x, int _y, int _direct, int _turn) {
x = _x;
y = _y;
direct = _direct;
turn = _turn;
}
};
bool operator<(Node lsh, Node rsh) {
return lsh.turn > rsh.turn;
}
char maze[200][200];
int main() {
int t;
scanf("%d", &t);
for (int idx = 0; idx < t; ++idx) {
int m, n;
scanf("%d%d", &m, &n);
// m n 是行和列
for (int i = 0; i < m; ++i) {
scanf("%s", maze[i]);
}
int k, x1, x2, y1, y2;
// k 最多转弯数
scanf("%d%d%d%d%d", &k, &y1, &x1, &y2, &x2);
--x1; // 将编号从1到N 转成下标从0到N-1
--x2;
--y1;
--y2;
int vis[101][101][5];
for (int i = 0; i < 101; ++i) {
for (int j = 0; j < 101; ++j) {
vis[i][j][1] = INT_MAX; //目前按照1方向到达i,j位置所经历的最小转弯次数
vis[i][j][2] = INT_MAX; //目前按照2方向到达i,j位置所经历的最小转弯次数
vis[i][j][3] = INT_MAX; //目前按照3方向到达i,j位置所经历的最小转弯次数
vis[i][j][4] = INT_MAX; //目前按照4方向到达i,j位置所经历的最小转弯次数
}
}
priority_queue<Node> tovisit; // 使用优先队列保存待访问的邻居结点
Node root(x1, y1, 0, 0); // 0 表示当前没有任何方向
tovisit.push(root);
bool isfind = false;
while (!tovisit.empty()) {
// 当前结点
Node cur = tovisit.top();
int x = cur.x, y = cur.y;
tovisit.pop();
if (cur.x == x2 && cur.y == y2) {
isfind = true;
break;
}
// 假设下一步走1方向
int nextTurn = (cur.direct == 1) ? cur.turn : cur.turn + 1;
// 假设下一个位置在范围内,且消耗转向次数更少,则入队
if (nextTurn <= k + 1 && x + 1 < m && maze[x + 1][y] != '*' && vis[x + 1][y][1] > nextTurn) {
Node next(x + 1, y, 1, nextTurn);
tovisit.push(next);
vis[x + 1][y][1] = nextTurn;
}
// 假设下一步走2方向
nextTurn = (cur.direct == 2) ? cur.turn : cur.turn + 1;
if (nextTurn <= k + 1 && x - 1 >= 0 && maze[x - 1][y] != '*' && vis[x - 1][y][2] > nextTurn) {
Node next(x - 1, y, 2, nextTurn);
tovisit.push(next);
vis[x - 1][y][2] = nextTurn;
}
nextTurn = (cur.direct == 3) ? cur.turn : cur.turn + 1;
if (nextTurn <= k + 1 && y + 1 < n && maze[x][y + 1] != '*' && vis[x][y + 1][3] > nextTurn) {
Node next(x, y + 1, 3, nextTurn);
tovisit.push(next);
vis[x][y + 1][3] = nextTurn;
}
nextTurn = (cur.direct == 4) ? cur.turn : cur.turn + 1;
if (nextTurn <= k + 1 && y - 1 >= 0 && maze[x][y - 1] != '*' && vis[x][y - 1][4] > nextTurn) {
Node next(x, y - 1, 4, nextTurn);
tovisit.push(next);
vis[x][y - 1][4] = nextTurn;
}
}
if (isfind) {
printf("yes\n");
// printf("%d\n", dis[x2][y2]);
}
else {
printf("no\n");
}
}
return 0;
}
第五题是八皇后。经典递归牢题。
#include <stdio.h>
#include <vector>
using namespace std;
//希望所有的函数都能访问queenvec
vector<vector<int>> queenvec;//所有的合法皇后序列
void DFSFindQueen(vector<int> &queen, int pos){
//打算放下一个皇后
for(int i = 1;i <= 8;i++){
//i就是第pos号皇后打算放的列数
bool isOK = true;
for(int j = 0; j<pos;j++){
//j用来遍历之前已经放好的皇后0~pos-1
if(queen[j] == i||//第j号皇后与pos号皇后在同一列
pos - j == queen[j]-i||pos - j == i - queen[j]
//在一条斜对角线上
){
isOK = false;
break;
}
if(isOK == true){
//将pos号皇后放置好
queen.push_back(i);
if(pos == 7){
//八个皇后都已经放好了
queenvec.push_back(queen);
}
else {
DFSFindQueen(queen, pos+1);
}
queen.pop_back();//为下一种i的可能性做准备
}
}
}
}
int main(){
vector<int> queen;//用来记录已经放好的皇后的位置
DFSFindQueen(queen, 0);
return 0;
}
取巧的方法:如果解的数量有限,而且算解的时间很长。可以先写一个比较差的算法先生成所有的解,保存所有的解,写入代码,访问时直接访问解集合即可。著名的打表法。
#include <stdio.h>
#include <vector>
using namespace std;
//希望所有的函数都能访问queenvec
vector<vector<int>> queenvec;//所有的合法皇后序列
void DFSFindQueen(vector<int> &queen, int pos){
//打算放下一个皇后
for(int i = 1;i <= 8;i++){
//i就是第pos号皇后打算放的列数
bool isOK = true;
for(int j = 0; j<pos;j++){
//j用来遍历之前已经放好的皇后0~pos-1
if(queen[j] == i||//第j号皇后与pos号皇后在同一列
pos - j == queen[j]-i||pos - j == i - queen[j]
//在一条斜对角线上
){
isOK = false;
break;
}
}
if(isOK == true){
//将pos号皇后放置好
queen.push_back(i);
if(pos == 7){
//八个皇后都已经放好了
queenvec.push_back(queen);
// printf("\"");
// for(int k = 0; k<8;k++){
// printf("%d", queen[k]);
// }
// printf("\",\n");
}
else {
DFSFindQueen(queen, pos+1);
}
queen.pop_back();//为下一种i的可能性做准备
}
}
}
int main(){
vector<int> queen;//用来记录已经放好的皇后的位置
DFSFindQueen(queen, 0);
int n;
scanf("%d", &n);
for(int i = 0; i<n;i++){
int b;
scanf("%d", &b);
for(int j = 0; j < queenvec[b-1].size();j++){
printf("%d", queenvec[b-1][j]);
}
printf("\n");
}
return 0;
}
第六题是数组划分。直接超时被拿下。
#include <vector>
#include <stdio.h>
using namespace std;
int sum = 0;//记录数组的和
int diff = 0;//记录遍历过程中最小的差值
void DFSFindMinDiff(vector<int> &arr, int pos, int sa){
if(pos == arr.size()){
return;
}
//arr[pos]不放入集合中
DFSFindMinDiff(arr, pos+1,sa);
//arr[pos]放入集合中
int newdiff;//记录当前的差值
if(2*(sa+arr[pos]) > sum){
newdiff = 2*(sa+arr[pos])-sum;
}
else newdiff = sum - 2*(sa+arr[pos]);
if(newdiff < diff){
diff = newdiff;
}
DFSFindMinDiff(arr, pos+1, sa+arr[pos]);
}
int main(){
vector<int> arr;
int i;
while(scanf("%d", &i)!=EOF){
arr.push_back(i);
}
for(int i = 0; i<arr.size();i++){
sum += arr[i];
}
diff = sum;
DFSFindMinDiff(arr, 0, 0);
//sa+sb = sum
//sb-sa = diff
int sa = (sum+diff)/2;
int sb = (sum - diff)/2;
printf("%d%d", sa, sb);
}
对代码进行剪枝,去掉不必要的搜索过程,尽快找到最优解,提前终止搜索。可以排序arr,先执行加入arr[pos],再执行不加入。一开始sa<sb,中间某个时刻sb大,此时没有必要继续。若diff为0或为1,终止,其次arr[pos]>sum/2。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
int sum = 0; //记录数组的和
int diff = 0; // 记录遍历过程最小的差值
bool exitFlag = false; // 记录是否要提前退出
void DFSFindMinDiff(vector<int> &arr, int pos, int sa) {
if (pos == arr.size() || exitFlag == true) {
return;
}
// arr[pos] 放入到 a集合当中
int newdiff; //记录当前的差值
if (2 * (sa + arr[pos]) - sum > 0) {
newdiff = 2 * (sa + arr[pos]) - sum;
}
else {
newdiff = sum - 2 * (sa + arr[pos]);
}
if (newdiff < diff) {
diff = newdiff;
if (diff == 0 || diff == 1 || 2 * arr[pos] > sum) {
exitFlag = true;
}
}
if (2 * (sa + arr[pos]) - sum < 0) {
DFSFindMinDiff(arr, pos + 1, sa + arr[pos]);
}
// arr[pos] 不放入 a集合中
DFSFindMinDiff(arr, pos + 1, sa);
}
bool compare(int lhs, int rhs) {
return lhs > rhs;
}
int main() {
vector<int> arr;
int i;
while (scanf("%d", &i) != EOF) {
arr.push_back(i);
}
for (int i = 0; i < arr.size(); ++i) {
sum += arr[i];
}
diff = sum;
sort(arr.begin(), arr.end(), compare);
DFSFindMinDiff(arr, 0, 0);
// sa+sb = sum
// sb-sa = diff
int sa = (sum - diff) / 2;
int sb = sa + diff;
printf("%d %d\n", sb, sa);
return 0;
}
兄弟们轻易别跨考,要补的东西太多了,博主现在天天都在焦虑。祝刷到的兄弟们一切顺利吧,能考上出个帖子给大伙讲讲备考过程。