搜索
1. DFS和BFS
DFS:深度优先遍历。时间复杂度O(h)O(h)O(h)。不能保证搜到最短路。
BFS:宽度优先遍历。时间复杂度O(2n)O(2^n)O(2n)。可以保证第一次搜到的就是最短路。
题目要求最短/最优之类的,一般都使用BFS。
题目比较奇怪,对复杂度要求高的,一般都用DFS。
注意两个概念
- 回溯:在DFS中,这条路走不通了,回退到父节点
- 剪枝:提前判断这个方法是不合法的
1. 1 DFS
- 想清楚顺序:用什么顺序遍历所有方案
- 注意回溯的时候要恢复现场
排列数字
有n个位置,从第一个位置开始选择,要填入什么数字
#include <iostream>
using namespace std;
const int N = 10;
int n;
int path[N];
bool vis[N]; // vis[i]表示数字i已经被放到位置上了
void dfs(int u)
{
if (u == n) {
for (int i = 0; i < n; i++) {
cout << path[i] << " ";
}
cout << endl;
return;
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
path[u] = i;
vis[i] = true;
dfs(u + 1);
vis[i] = false;
}
}
}
int main()
{
cin >> n;
dfs(0); // 从第一个位置开始看
return 0;
}
八皇后问题
- 方法一:类似全排列的扩展。一行一行枚举,看每一个行的皇后要放到哪一列。
#include <iostream>
using namespace std;
const int N = 20;
int n;
// 记录棋盘
int g[N][N];
// col指每一列只能有一个,一共有n列
// dg表示正对角线:从左下角到右上角,一共有2n-1条
// udg表示反对角线:从左上角到右下角,一共有2n-1条
bool col[N], dg[N], udg[N];
void dfs(int u)
{
if (u == n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
printf("%c", g[i][j]);
}
puts("");
}
puts("");
return;
}
// 枚举第u行的皇后应该放在哪一列
for (int i = 0; i < n; i++) {
if (!col[i] && !dg[i + u] && !udg[i - u + n]) {
g[u][i] = 'Q';
col[i] = dg[i + u] = udg[i - u + n] = true;
dfs(u + 1);
col[i] = dg[i + u] = udg[i - u + n] = false;
g[u][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;
}
- 一个格子一个格子的枚举
#include <iostream>
using namespace std;
const int N = 20;
int n;
// 记录棋盘
int g[N][N];
// dg表示正对角线:从左下角到右上角,一共有2n-1条
// udg表示反对角线:从左上角到右下角,一共有2n-1条
bool row[N], col[N], dg[N], udg[N];
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++) {
for (int j = 0; j < n; j++) {
printf("%c", g[i][j]);
}
puts("");
}
puts("");
}
return;
}
// 不放皇后
dfs(x, y + 1, s);
// 放皇后
if (!row[x] && !col[y] && !dg[y + x] && !udg[x - y + n]) {
row[x] = col[y] = dg[y + x] = udg[x - y + n] = true;
g[x][y] = 'Q';
dfs(x, y + 1, s + 1);
row[x] = col[y] = dg[y + x] = udg[x - y + n] = false;
g[x][y] = '.';
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
g[i][j] = '.';
}
}
dfs(0, 0, 0);
return 0;
}
1. 2 BFS
步骤:
- 把初始状态放到队列中
- 写while循环,当队列不空时
走迷宫
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
int g[N][N]; // 存迷宫
int d[N][N]; // 每个点到起点的距离
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int bfs()
{
memset(d, -1, sizeof(d));
d[0][0] = 0;
queue <PII> q;
q.push({0, 0});
while (!q.empty()) {
PII t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int x = t.first + dx[i];
int y = t.second + dy[i];
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});
}
}
}
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. 树和图的存储
-
树是特殊的图(无环连通图)。
所以只需要考虑图怎么存储。
-
图包括有向图和无向图。
对于无向图,就建立两条边。
所以只要考虑有向图怎么存储。
-
有向图的两种存储方法
-
邻接矩阵:二维数组
-
邻接表:每一个节点都有一个单链表,表示这个节点可以走到哪些点。
插入时,插入到头节点之后。
-
#include <cstring>
#include <iostream>
#include <algorithm>
uisng namespace std;
const int N = 100010, M = N * 2;
// h存链表的头节点,h[i]表示节点i指向的第一个节点值
// e存每一个节点的值
// ne存每一个节点的下一个节点
int h[N], e[M], ne[M], idx;
void add(int a, int b)
{
e[idx] = b; // 建立一个新的节点:第idx个节点的值为b
ne[idx] = h[a]; // 该节点的下一个节点指向
h[a] = idx++; // 节点a指向第idx个节点,然后idx加一
}
int main()
{
// 初始,头节点均为-1,表示空
memset(h, -1, sizeof(h));
}
3. 树和图的遍历
复杂度均是O(n+m),n为节点,m为边
3. 1 深度优先遍历
dfs框架
#include <cstring>
#include <iostream>
#include <algorithm>
uisng namespace std;
const int N = 100010, M = N * 2;
int h[N], e[M], ne[M], idx;
bool vis[N];
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u)
{
vis[u] = true; // 标记已经被搜过
// 遍历该节点的邻接边
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!vis[j]) {
dfs(j);
}
}
}
int main()
{
memset(h, -1, sizeof(h));
}
树的重心问题
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = N * 2;
int n;
int h[N], e[M], ne[M], idx;
bool vis[N];
int ans = N;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int dfs(int u)
{
vis[u] = true; // 标记已经被搜过
int sum = 1; // 以u为根节点的子树的大小,1表示当前点
int res = 0; // 删掉之后,联通块节点的最大值
// 遍历该节点的邻接边
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!vis[j]) {
int s = dfs(j);
res = max(res, s);
sum += s;
}
}
res = max (res, n - sum);
ans = min(res, ans);
return sum;
}
int main()
{
cin >> n;
memset(h, -1, sizeof(h));
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
cout << ans << endl;
return 0;
}
3. 2 宽度优先遍历
树中点的层次
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int bfs()
{
int hh = 0, tt = 0;
q[0] = 1;
memset(d, -1, sizeof(d));
d[1] = 0;
while (hh <= tt) {
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (d[j] == -1) {
d[j] = d[t] + 1;
q[++tt] = j;
}
}
}
return d[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs() << endl;
return 0;
}