目录
DFS
数据结构 | 空间 | ||
DFS | stack | O(n) | 不具有最短路 |
BFS | queue | O(2^n) | 具有最短路 |
排列数字
给定一个整数 n,将数字 1n排成一排,将会有很多种排列方法现在,请你按照字典序将所有的排列方法输出
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行
数据范围
1≤n≤7
输入样例:3
输出样例:
1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
方法1:使用next_permutation()函数
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)a[i] = i;
do{
for(int i = 1; i <= n; i++)cout << a[i] <<" ";
cout << "\n";
}while(next_permutation(a+1, a+1+n));
return 0;
}
方法2:DFS
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
bool st[N];
int n;
void dfs(int x, int s){//dfs到数字x,现在已经有s个点了
a[s] = x;
if(s == n){//走了n个点了,走完了
for(int i = 1; i <= n; i++)cout << a[i] << " ";
cout << '\n';
return;
}
for(int i = 1; i <= n; i++){
if(!st[i]){//未访问过
st[i] = true;//访问
//可以在这里先记录a[s+1] = i,后面直接dfs(s+1),可以省去传参数(数字)
dfs(i, s+1);
st[i] = false;// 回溯(恢复现场)
}
}
}
int main()
{
cin >> n;
memset(st, false, sizeof st);
for(int i = 1; i <= n; i++)a[i] = i;
dfs(0, 0);
// for(int i = 1; i <= n; i++){
// if(!st[i]){
// st[i] = true;
// a[i] = i;
// dfs(i, 1);
// st[i] = false;
// }
// }
return 0;
}
n-皇后问题
n一皇后问题是指将 n个后放在 n x n 的国际象棋棋盘上,使得后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上
现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式:
共一行,包含整数 n。
输出格式:
每个解决方案占 n 行,每行输出一个长度为 n的字符串,用来表示完整的棋盘状态
其中 . 表示某一个位置的方格状态为空, Q 表示某一个位置的方格上摆着皇后
每个方案输出完成后,输出一个空行
注意:行末不能有多余空格
输出方案的顺序任意,只要不重复且没有遗漏即可
数据范围
1≤n≤9
输入样例:4
输出样例:
.Q.. ...Q Q... ..Q. ..Q. Q... ...Q .Q..
正对角线 y = x + b => b = y - x ; 下标不能为负数 = > b = y + n - x;
反对角线 y = - x + b = > b = x + y;
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
char a[N][N];
bool row[N], col[N], dia[N], bdia[N];
//枚举每个皇后的位置
void dfs(int u){
if(u==n){
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
cout << a[i][j];
}
cout << '\n';
}
cout << "\n";
return ;
}
//遍历当前行
for(int i = 0; i < n; i++){
if(!col[i] && ! dia[u + i] && !bdia[i - u + n]){//纵列,正对角线,反对角线均为有
col[i] = dia[u + i] = bdia[i - u + n] = true;//标记
a[u][i] = 'Q';
dfs(u+1);//下一个皇后
col[i] = dia[u + i] = bdia[i - u + n] = false;//回溯
a[u][i] = '.';
}
}
}
//枚举每个位置放与不放皇后
void dfs(int x, int y, int s){
if(y == n)y = 0, x++;
if(x == n){//放完了,输出
if(s == n){
for(int i = 0; i < n; i++)puts(a[i]);
puts("");
}
return;
}
dfs(x, y + 1, s); // 不放皇后
//放皇后
if(!row[x] && !col[y] && !dia[x+y] && !bdia[x - y + n]){
a[x][y] = 'Q';
row[x] = col[y] = dia[x + y] = bdia[x - y + n] = true;
dfs(x, y + 1, s + 1);
a[x][y] = '.';
row[x] = col[y] = dia[x + y] = bdia[x - y + n] = false;
}
}
int main()
{
cin >> n;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++)a[i][j] = '.';
}
dfs(0);
//dfs(0, 0, 0);
return 0;
}
BFS
走迷宫
给定一个 n x m的二维整数数组,用来表示一个迷宫,数组中只包含 0 或1,其中0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角(n,m)处,至少需要移动多少次数据保证(1,1)处和(n,m)处的数字为 0,且一定至少存在一条通路
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含 m 个整数 (0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数
数据范围
1≤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 <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
int n, m;
int a[110][110], d[110][110];
int dis[][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
queue<pii> q;
pii p[110][110];
int bfs(){
q.push({1,1});
d[1][1] = 0;
while(!q.empty()){
int ax = q.front().first;
int ay = q.front().second;
q.pop();
for(int i = 0; i < 4; i++){
int tx = ax + dis[i][0];
int ty = ay + dis[i][1];
if(tx > 0 && tx <= n && ty > 0 && ty <= m && !a[tx][ty] && d[tx][ty] == -1){
d[tx][ty] = d[ax][ay] + 1;
p[tx][ty] = {ax, ay};// x, y 从t过来的
q.push({tx, ty});
}
}
}
//输出路径
// int x = n, y = m;
// while(x || y){
// cout << x << " " << y << '\n';
// auto t = p[x][y];
// x = t.first, y = t.second;
// }
return d[n][m];
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> a[i][j];
memset(d, -1, sizeof d);
cout << bfs();
return 0;
}
八数码
在一个3 x 3的网格中,1~8这8 个数字和一个 x 恰好不重不漏地分布在这3 x 3的网格中。
例如:
1 2 3
x 4 6
7 5 8
在游戏过程中,可以把x 与其上、下、左、右四个方向之一的数字交换 (如果存)
我们的目的是通过交换,使得网格变为如下排列 (称为正确排列):
1 2 3
4 5 6
7 8 x
例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得正确排列。
交换过程如下:
现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换
输入格式
输入占一行,将 3 x 3 的初始网格描绘出来
例如,如果初始网格如下所示:
1 2 3x 4 6
7 5 8
则输入为:1 2 3 x 4 6 7 5 8
输出格式
输出占一行,包含一个整数,表示最少交换次数。
如果不存在解决方案,则输出-1.
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19
最少步数-> BFS
#include <bits/stdc++.h>
using namespace std;
queue<string> q;
map<string, int> d;
int dir[][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; //四个方向
int bfs(string s)
{
q.push(s);//初始状态
d[s] = 0;
while(!q.empty())
{
auto t = q.front();q.pop();
int dis = d[t];
if(t == "12345678x"){//找到最终状态,退出
return dis;
}
int k = t.find('x');//根据下标算x轴,y轴
int x = k / 3, y = k % 3;
for(int i = 0; i < 4; i++){
int tx = x + dir[i][0], ty = y + dir[i][1];
if(tx >= 0 && tx < 3 && ty >= 0 && ty < 3){
swap(t[tx * 3 + ty], t[k]);//移动
if(!d[t]){//该状态还未存在
d[t] = dis + 1;
q.push(t);
}
swap(t[tx * 3 + ty], t[k]);//回溯
}
}
}
return -1;
}
int main()
{
char c;
string s;
for(int i = 0; i < 9; i++){
cin >> c;
s += c;
}
//初始状态s
cout << bfs(s);
return 0;
}
树与图的深度优先遍历
树是一种特殊的图。图的存储方式有两种:邻接表(单链表)和邻接矩阵(二维数组)
树的重心
给定一颗树,树中包含 n 个结点 (编号1~n)和n -1条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式:
第一行包含整数n,表示树的结点数
接下来 n -1行,每行包含两个整数 a 和 b,表示点 a 和点b之间存在一条边
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值
数据范围1≤n≤10^5
输入样例:
9 1 2 1 7 1 4 2 8 2 5 4 3 3 9 4 6
输出样例:
4
树的重心:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int n, ans = INT_MAX;
int h[N], e[M], ne[M], idx;// h[] 多个头节点, e[]存数据, ne[]下一个节点
bool st[N];
//图的存储
void add(int a, int b){
e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
//深度优先搜索
int dfs(int u)
{
st[u] = true;//标记搜过了
int sum = 1, res = 0;
for(int i = h[u]; i != -1; i = ne[i]){//遍历
if(!st[e[i]]){//未访问过
int s = dfs(e[i]);
res = max(res, s);
sum += s;
}
}
res = max(res, n-sum);
ans = min(ans, res);
return sum;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n;
for (int i = 0; i < n - 1; i++){
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
cout << ans;
return 0;
}
树与图的广度优先遍历
图中点的层次
给定一个n个点 m 条边的有向图,图中可能存在重边和自环所有边的长度都是 1,点的编号为1~n。
请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 -1
输入格式
第一行包含两个整数 n和m。
接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为1的边。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。数据范围:
1≤n,m≤10^5
输入样例:
4 5 1 2 2 3 3 4 1 3 1 4
输出样例:
1
图的宽搜和BFS的思路是一样的,模板也类似。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], d[N], idx;
queue<int> q;
void add(int a, int b){//加边
e[idx] = b; ne[idx] = h[a]; h[a] = idx++;
}
int bfs()
{
q.push(1);
d[1] = 0;
while(!q.empty())
{
int t = q.front(); q.pop();
for(int i = h[t]; i != -1; i = ne[i]){
if(d[e[i]] == -1){//未访问过
d[e[i]] = d[t] + 1;//路径+1
q.push(e[i]);//入队
}
}
}
return d[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
memset(d, -1, sizeof d);
while(m--){
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs();
return 0;
}
图的宽搜常用于拓扑序列
拓扑排序
啥是拓扑排序???
1)有向图,如果图中有入度为0的点,就把这个点删掉,同时删连这个点的边
2)一直按上面处理之后,如果所有点都能被删掉,则这个图可以进行拓扑排序。
拓扑序列一般存在于有向图,有向无环图(AVO图)又称为拓扑图,存在环的图必定不存在拓扑序列。一个有向无环图一定存在一个入度为0的点
有向图的拓扑序列
给定一个n 个点m 条边的有向图,点的编号是1到n,图中可能存在重边和自环。
请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1。
若一个由图中所有点构成的序列 A 满足: 对于图中的每条边(x,y)。x在A 中都出现在y之前,则称 A 是该图的一个拓扑序列。
输入格式
第一行包含两个整数 n和m。
接下来 m 行,每行包含两个整数 x 和y,表示存在一条从点 x 到点y的有向边(x,y)
输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可
否则输出 -1。
数据范围1≤n,m≤10^5
输入样例:
3 3 1 2 2 3 1 3
输出样例:
1 2 3
思路:1)首先记录每个点的入度;2)将入度为0的点放入队列;3)将队列里的点依次出队列,然后遍历,同时删边,入度-1;4)如果所有点都进过队列,则可以拓扑排序,输出所有顶点,否则-1
代码1:用数组来构造邻接表,用队列来记录拓扑序列
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10 , M = 2 * N;
int n, m;
int e[M], ne[M], h[N], idx;
int d[N], cnt;// d[]表示每个元素的入度
queue<int> q, res;// res存储拓扑序列
void add(int x, int y)
{
e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}
bool topsort()
{
for(int i = 1; i <= n; i++){
if(!d[i])q.push(i), res.push(i);//所有入度非0的,都入队
}
while(!q.empty()){
int t = q.front(); q.pop();
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
d[j]--;//入度-1
if(d[j] == 0)q.push(j), res.push(j);//入度为0,出队
}
}
return res.size() == n;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while(m--){
int x, y;
cin >> x >> y;
add(x, y);
d[y]++;//记录入度
}
if(topsort()){
while(!res.empty()){
cout << res.front() << " ";
res.pop();
}
puts("");
}else{
cout << "-1";
}
return 0;
}
代码2:利用stl中的vector数组来构造邻接表,用数组来记录拓扑序列
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int d[N];
vector<int> p[N];
queue<int> q;
int cnt, res[N];
bool topsort()
{
for(int i = 1; i <= n; i++){
if(!d[i])q.push(i);
// res[cnt++] = i;
}
while(!q.empty())
{
int t = q.front(); q.pop();
res[cnt++] = t;
for(int i = 0; i < p[t].size(); i++){
int j = p[t][i];
d[j]--;//删边
if(!d[j]){
q.push(j);
// res[cnt++] = j;
}
}
}
return cnt == n;
}
int main()
{
cin >> n >> m;
while(m--){
int a, b;
cin >> a >> b;
p[a].push_back(b);
d[b]++;
}
if(topsort()){
for(int i = 0; i < cnt; i++){
cout << res[i] << ' ';
}
cout << "\n";
}else{
cout << "-1\n";
}
return 0;
}
本篇介绍了图的存储与遍历,包括DFS,BFS,树与图的深度优先遍历,树与图的广度优先遍历,拓扑排序。
关于图论篇:
只有流过血的手指,才能弹出世间的绝唱。 —— 泰戈尔