一、DFS
例1:排列数字
给定一个整数 nn,将数字 1∼n1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 nn。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
#include<iostream>
using namespace std;
const int N=10;
int n,path[N];//用来记录每一次的搜索路径
bool used[N];//用来标记某个数字是否被用过
void dfs(int x){//x指第几位数
if(x==n){//已经填到了最后一位
for(int i=0;i<n;i++) printf("%d ",path[i]);
puts("");
return ;
}
//依次枚举每一个备选的数字
for(int i=1;i<=n;i++){
if(used[i]==false){
path[x]=i;//这个位置填上i
used[i]=true;
dfs(x+1);
used[i]=false;
//path[x]=0;
}
}
}
int main(){
cin>>n;
dfs(0);
return 0;
}
例2:n-皇后问题
n−n−皇后问题是指将 nn 个皇后放在 n×nn×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 nn,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 nn。
输出格式
每个解决方案占 nn 行,每行输出一个长度为 nn 的字符串,用来表示完整的棋盘状态。
其中
.
表示某一个位置的方格状态为空,Q
表示某一个位置的方格上摆着皇后。每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。
输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1≤n≤91≤n≤9
输入样例:
4
输出样例:
.Q.. ...Q Q... ..Q. ..Q. Q... ...Q .Q..
#include<iostream>
using namespace std;
const int N=10;
char g[N][N];
int n;
bool col[N],dg[N],udg[N];//列、对角线、反对角线
//每一行有且仅有一个,所以可以按行搜索
void dfs(int x){
if(x==n){//已经搜了n行,输出这条路径
for(int i=0;i<n;i++) puts(g[i]);
puts("");//换行
return ;
}
//枚举x这一行,搜索合法的列
for(int i=0;i<n;i++){
//剪枝,对于不满足条件的点,不再继续搜索
if(!col[i] && !dg[x+i] && !udg[n-x+i]){
g[x][i]='Q';
col[i]=dg[x+i]=udg[i-x+n]=true;
//+n是防止下标小于0
dfs(x+1);
col[i]=dg[x+i]=udg[i-x+n]=false;
g[x][i]='.';//恢复现场
}
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
g[i][j]='.';
}
dfs(0);
return 0;
}
二、BFS
例1:走迷宫
给定一个 n×mn×m 的二维整数数组,用来表示一个迷宫,数组中只包含 00 或 11,其中 00 表示可以走的路,11 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1)(1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m)(n,m) 处,至少需要移动多少次。
数据保证 (1,1)(1,1) 处和 (n,m)(n,m) 处的数字为 00,且一定至少存在一条通路。
输入格式
第一行包含两个整数 nn 和 mm。
接下来 nn 行,每行包含 mm 个整数(00 或 11),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤1001≤n,m≤100
输入样例:
5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0
输出样例:
8
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
//所有边权相同的最短路问题才能用bfs
/* queue<--初始状态
while queue不变{
t<--队头
拓展t
}
*/
const int N=110;
typedef pair<int,int> PII;
int n,m;
int g[N][N];//存地图
int d[N][N];//存每一个点到起点的距离
int bfs(){
queue<PII> q;//队列储存路径
memset(d,-1,sizeof d);//距离全部初始化为-1,表示没有走过
//首先在起点,初始化d,并将这个点加入队列
d[0][0]=0;
q.push({0,0});
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};//向量表示上下左右四个方向
while(q.size()){//队列不空
auto t=q.front();//取出队头
q.pop();//队头出队
for(int i=0;i<4;i++){
int x=t.first+dx[i],y=t.second+dy[i];//枚举向上下左右四个方向移动
//且在边界范围内,且可以走是空地(g[][]==0),且之前没有走过(d[][]==-1)
if(x>=0 && x<n && y>=0 && y<m && g[x][y]==0 && d[x][y]==-1){
d[x][y]=d[t.first][t.second]+1;//更新这个点的距离
q.push({x,y});//把这个位置记录在队列里
}
}
}
//这个循环也就是说,每一个在当时符合条件的位置都会被push到队列里,但是继续往下走时有些路径会无法走通,即无法继续延伸
//而那个点本身被作为对头取出后,就会被pop掉,即被忽略掉
//只有能走到终点的点最后会被留在队里,在循环中继续向下走,并更新着d[][]距离
//而最后返回的答案,是右下角(终点)那个点距离起点的距离
//由于bfs不走重复的路,走到那里只会剩下一条路,即d[n-1][m-1]只会被赋予最小值
//即最短路
return d[n-1][m-1];
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)
cin>>g[i][j];
}
cout<<bfs()<<endl;
return 0;
}
例2:八数码
在一个 3×33×3 的网格中,1∼81∼8 这 88 个数字和一个
x
恰好不重不漏地分布在这 3×33×3 的网格中。例如:
1 2 3 x 4 6 7 5 8
在游戏过程中,可以把
x
与其上、下、左、右四个方向之一的数字交换(如果存在)。我们的目的是通过交换,使得网格变为如下排列(称为正确排列):
1 2 3 4 5 6 7 8 x
例如,示例中图形就可以通过让
x
先后与右、下、右三个方向的数字交换成功得到正确排列。交换过程如下:
1 2 3 1 2 3 1 2 3 1 2 3 x 4 6 4 x 6 4 5 6 4 5 6 7 5 8 7 5 8 7 x 8 7 8 x
现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。
输入格式
输入占一行,将 3×33×3 的初始网格描绘出来。
例如,如果初始网格如下所示:
1 2 3 x 4 6 7 5 8
则输入为:
1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个整数,表示最少交换次数。
如果不存在解决方案,则输出 −1−1。
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;
/*bfs 状态处理
如字符串 1234x5678 判断可以变成什么状态
(1)先变成3x3的矩阵
(2) x上下左右移动的情况进行枚举
(3)再将矩阵恢复成字符串
*/
int bfs(string start){
string end="12345678x";
queue<string> q;//队列里存的是字符串,是一串字符,是以上字符的一种排列方式
unordered_map<string,int> d;//d是某种排列方式距离原始状态移动的次数(即可看作距离)
q.push(start);//初始状态入队
d[start]=0;//初始状态的距离为0
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
//宽搜模板
while(q.size()){
auto t=q.front();//取出队头,t是字符串,是以上字符的一种排列方式
q.pop();
int distance=d[t];//t状态下跟原始状态的距离
if(t==end) return d[t];//如果已经到最终状态,直接返回所用的步数,即答案
//状态转移
int k=t.find('x');//先找到x的位置(返回x的下标) (string中的函数 s.find('a') 返回字符a的下标)
int x=k/3,y=k%3;//一维数组和二维数组下标的转化
for(int i=0;i<4;i++){//枚举四种状态
int a=x+dx[i],b=y+dy[i];
if(a>=0 && a<3 && b>=0 && b<3){//a,b都没有出界
swap(t[k],t[a*3+b]);//交换一维数组中该点与x的下标
if(!d.count(t)){//这个状态没有经历过,可向这个方向交换
d[t]=distance+1;//在之前那个状态下的步数+1
q.push(t);//作为一种情况入队
}
swap(t[k],t[a*3+b]);//恢复状态,继续循环向其他方向的情况
}
}
}
return -1;
}
int main(){
string start;//存初始状态
for(int i=0;i<9;i++){
char c;
cin>>c;
start+=c;
}
cout<<bfs(start)<<endl;//bfs输出最短距离
return 0;
}