文章目录
DFS(系统栈,for循环,递归)
重要概念
当然搜索DFS不一定要通过递归来实现,用循环也是可以的
一下子懂了DFS:dfs()函数里会有个循环,然后循环里有个if判断条件,满足条件的情况时一条条分支
注意:我下标一般是从1开始的,所以填数时也是从u = 1(第二层)开始填的,如g[u + 1][i]
剪枝
:能够判断出不是可行解或不是最优解
一般进行剪枝后,就难以分析时间复杂度了出口
:确定出口的方法:看当前已经搜到的个数,以及u的值
如:排列数字中dfs中参数u = 1时已经搜到了1个数
,所以 时,说明已经搜到了n个数,所以可以设为出口;LeetCode79单词搜索中dfs参数 时已经搜到了2个数,所以你要搜n个数,那设置出口 即可恢复现场
:保证下一个分支的搜索能正常进行
比如上图第一数填充了1后,继续搜索,有两个分支,如果第一个分支把2标记为true,并完成该分支搜索后,未把2重新标记为false,那么for循环进行到下一个分支时,2已经被提前舍弃了,这显然是不合理的,这让下一个分支本有的选择变少了。
所以弄乱了别人的东西一定要整理好恢复原状才能离开
排列数字
题目:输出1到n的所有全排列
注意:我习惯path数组从下标1开始填数
// 2020年1月13日 星期一 10:29:57
#include <iostream>
using namespace std;
const int N = 10;
int n;
int path[N];
bool st[N]; // 用来判断搜下一层的位置 可以选什么
// 通过u判断层数 u = 0表示第1层
// DFS就是把所有情况搜一遍,也可以加个判断条件找最优(剪枝)
void dfs(int u) {
if (u == n) {
for (int i = 1; i <= n; i ++ ) printf("%d ", path[i]);
puts("");
return;
}
for (int i = 1; i <= n; i ++) // for循环有几个if条件成立,当前结点就有几个分支
if (!st[i]) {
path[u + 1] = i; // 一开始u = 0,u + 1表示从第2层开始填数,具体可以看图
st[i] = true; // true表示i用过了
dfs(u + 1); // 对u + 1位置进行填数,递归
path[u + 1] = 0; // 此题,这步可有可无,因为会被覆盖,但一般题目建议都加上!
st[i] = false; // 恢复现场(方便下一个分支能正常搜索)
}
}
int main() {
cin >> n;
dfs(0); // 0表示从树根全是空位开始
return 0;
}
n皇后问题
对于dg[u + i - 1],udg[n - u + i],画个图就能确定表达式
按行枚举
// 2020年1月13日 星期一 11:06:23
// 按行搜索,所以不用row[N]
#include <iostream>
using namespace std;
const int N = 20; // 要比n的两倍大一些
char g[N][N];
bool col[N], dg[N], udg[N];
int n;
// bool数组用来判断搜索的下一个位置是否可行
// col列,dg对角线(左上到右下),udg反对角线
// g[N][N]用来存路径
void dfs(int u) {
if (u == n) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n ;j ++) {
cout << g[i][j];
}
cout << endl;
}
cout << endl;
}
// 对n个位置按行搜索
for (int i = 1; i <= n; i ++) {
// 剪枝(对于不满足要求的点,不能继续往下搜索)
if (!col[i] && !dg[u + i - 1] && !udg[u - i + n]) {
g[u + 1][i] = 'Q';
col[i] = dg[u + i - 1] = udg[u - i + n] = true;
dfs(u + 1);
g[u + 1][i] = '.'; // 恢复现场,这步必须有,因为这题不像排列数字那题都能被覆盖到
col[i] = dg[u + i - 1] = udg[u - i + n] = false; // 恢复现场,不影响下一个分支的搜索,这步很关键
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n ;j ++) {
g[i][j] = '.';
}
}
dfs(0);
return 0;
}
枚举每个位置
#include <iostream>
using namespace std;
const int N = 20;
int x, y;
int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N];
// s表示已经放了几个皇后
void dfs(int x, int y, int s) {
if (y == n + 1) {
y = 1, x ++;
}
if (x == n + 1) {
if (s == n) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n ;j ++) {
cout << g[i][j];
}
cout << endl;
}
cout << endl;
}
return;
}
// 相当于按行枚举中,for(int i = 0; i < 2; i++) if (i == 0) ... if (i == 1) ..
// 当前位置放皇后
if (!row[x] && !col[y] && !dg[x + y - 1] && !udg[y + n - x]) {
g[x][y] = 'Q';
row[x] = col[y] = dg[x + y - 1] = udg[y + n - x] = true;
dfs(x, y + 1, s + 1);
g[x][y] = '.';
row[x] = col[y] = dg[x + y - 1] = udg[y + n - x] = false;
}
// 当前位置不放皇后
dfs(x, y + 1, s);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
g[i][j] = '.';
}
}
dfs(1, 1, 0);
return 0;
}
LeetCode51.N皇后
class Solution {
public:
static const int N = 30;
char g[N][N];
bool col[N], dg[N], udg[N];
vector<vector<string>> ans;
void dfs(int u, int n) {
if (u == n) {
vector<string> sol;
for(int i = 1; i <= n; i ++) {
string tmp = "";
for (int j = 1; j <= n; j ++) {
tmp += g[i][j];
}
sol.push_back(tmp);
}
ans.push_back(sol);
return;
}
for (int i = 1; i <= n; i ++) {
if (!col[i] && !dg[u + i - 1] && !udg[u - i + n]) {
g[u + 1][i] = 'Q'; // 从u = 1开始填
col[i] = dg[u + i - 1] = udg[u - i + n] = true;
dfs(u + 1, n);
g[u + 1][i] = '.'; // 恢复现场,这步必须有,因为这题不像排列数字那题都能被覆盖到
col[i] = dg[u + i - 1] = udg[u - i + n] = false; // 恢复现场,不影响下一个分支的搜索,这步很关键
}
}
}
vector<vector<string>> solveNQueens(int n) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n ; j ++) {
g[i][j] = '.';
}
}
dfs(0, n);
return ans;
}
};
46.全排列
class Solution {
public:
static const int N = 30;
bool st[N];
vector<int> path;
vector<vector<int>> ans;
void dfs(int u, int n, vector<int>& nums) {
if (u == n) {
ans.push_back(path);
return;
}
for (int i = 1; i <= n; i ++) {
if (!st[i]) {
path.push_back(nums[i - 1]);
st[i] = true;
dfs(u + 1, n, nums);
path.pop_back(); // 恢复现场
st[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int n = nums.size();
dfs(0, n, nums);
return ans;
}
};
17. 电话号码的字母组合(DFS)
class Solution {
public:
vector<string> res;
string str;
vector<string> table = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
void dfs(int u, string digits) {
int n = digits.size();
if (u == n) {
res.push_back(str); // str = "ad"
return;
}
// 如digits[u] = '3',则table对于的下标为1
for (int i = 0; i < table[digits[u] - '2'].size(); i ++) { // 分支的所有可能
// 此题所有分支都是可行的
str.push_back(table[digits[u] - '2'][i]); // string也可以用push_back(x);
dfs(u + 1, digits);
str.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if (digits == "") return res;
// digits = "23"
dfs(0, digits);
return res;
}
};
79.单词搜索(DFS好题)
分析:遍历矩阵中的每个点,对每个点进行dfs搜索,如果有一个搜索满足条件,则结果为true
版本1 习惯
注意st[][]的位置
class Solution {
public:
static const int N = 1000;
int n, m;
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
bool st[N][N];
bool exist(vector<vector<char>>& board, string word) {
if(board.empty() || board[0].empty()) return false;
n = board.size(), m = board[0].size();
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++) {
st[i][j] = true; // 把起点设为访问过的点
if(board[i][j] == word[0] && dfs(board, i, j, word, 0)) return true;
st[i][j] = false;
}
}
return false;
}
bool dfs(vector<vector<char>> & board, int sx, int sy, string & word, int u) {
if (u == word.size() - 1) return true;
for (int i = 0; i < 4; i ++) {
int a = sx + dx[i], b = sy + dy[i];
// 3个if用来剪枝,类似n皇后的if条件
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b] == true) continue;
if (board[a][b] != word[u + 1]) continue;
st[a][b] = true;
if (dfs(board, a, b, word, u + 1)) return true; // 只要有一个是true就会一直返回true
st[a][b] = false; // 恢复现场,不影响其他分支
}
return false;
}
};
版本2
class Solution {
public:
static const int N = 1000;
int n, m;
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
bool st[N][N];
bool exist(vector<vector<char>>& board, string word) {
if(board.empty() || board[0].empty()) return false;
n = board.size(), m = board[0].size();
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++) {
if(dfs(board, i, j, word, 0)) return true;
}
}
return false;
}
bool dfs(vector<vector<char>> & board, int sx, int sy, string & word, int u) {
if (board[sx][sy] != word[u]) return false;
if (u == word.size() - 1) return true;
st[sx][sy] = true;
for (int i = 0; i < 4; i ++) {
int a = sx + dx[i], b = sy + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b] == true) continue;
if (dfs(board, a, b, word, u + 1)) return true;
}
st[sx][sy] = false; // 恢复现场,不影响其他分支
return false;
}
};
Acwing 1116.马走日
分析:DFS遍历8个方向,注意恢复现场以及如何确定出口u的取值
#include <iostream>
using namespace std;
int n, m, x, y;
int res = 0;
bool st[11][11];
int dx[] = {1, 1, 2, 2, -1, -1, -2, -2};
int dy[] = {2, -2, -1, 1, 2, -2, 1, -1};
void dfs(int sx, int sy, int u) {
if (u == n * m - 1) {
res ++;
return;
}
for (int i = 0; i < 8; i ++) {
int a = sx + dx[i], b = sy + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b] == true) continue; // 已访问过
st[a][b] = true;
dfs(a, b, u + 1);
st[a][b] = false;
}
}
int main() {
std::ios::sync_with_stdio(false); // 不加这个优化,最后一个用例就会超时
int T;
cin >> T;
while(T --) {
cin >> n >> m;
cin >> x >> y;
res = 0;
st[x][y] = true;
dfs(x, y, 0);
cout << res << endl;
st[x][y] = false; // 恢复现场
}
return 0;
}