深度优先搜索
计算数的全排列
#include
#include
#include
#include
using namespace std;
void dfs(vector<int> & box, vector<bool> & flags, int step, int n)
{
if (step == n)
{
ostream_iterator<int> o_iter(cout, "");
copy(box.begin(), box.end(), o_iter);
cout << endl;
return;
}
for (int i = 1; i <= n; ++i)
{
if (flags[i - 1] == false)
{
box[step] = i;
flags[i - 1] = true;
dfs(box, flags, step + 1, n);
flags[i - 1] = false;
}
}
}
int main(void)
{
int n = 3;
vector<int> box(n);
vector<bool> flags(n, false);
dfs(box, flags, 0, n);
return 0;
}
深度优先搜索把每一种可能都去尝试一遍(for),当前步解决的情况下进入下一步,而下一步解决的方法和当前步是完全一样的。
void dfs(int step)
{
判断边界
尝试每一种可能 for(i = 1; i <= n; ++i)
{
继续下一步 dfs(step + 1);
}
返回
}
再次尝试用深度优先搜索解决暴力枚举
将数字1~9填入等式“??? + ??? = ???”使等式成立
#include
#include
using namespace std;
void dfs(vector<int> & box, vector<bool> & flags, int step, int n, int & total)
{
if (step == n)
{
int a = box[0] * 100 + box[1] * 10 + box[2];
int b = box[3] * 100 + box[4] * 10 + box[5];
int c = box[6] * 100 + box[7] * 10 + box[8];
if (a + b == c)
{
cout << box[0] << box[1] << box[2] << '+'
<< box[3] << box[4] << box[5] << '='
<< box[6] << box[7] << box[8] << endl;
++total;
}
return;
}
for (int i = 1; i <= n; ++i)
{
if (flags[i - 1] == false)
{
box[step] = i;
flags[i - 1] = true;
dfs(box, flags, step + 1, n, total);
flags[i - 1] = false;
}
}
}
int main(void)
{
int n = 9;
vector<int> box(n);
vector<bool> flags(n, false);
int total = 0;
dfs(box, flags, 0, n, total);
cout << total / 2 << endl;
return 0;
}
深度优先搜索迷宫
迷宫由n行m列的单元格组成,每个单元格要么是空地,要么是障碍物,任务是找到一条从迷宫的起点通往目的位置的最短路径。
按照右下左上的顺序尝试路径搜索。
#include
#include
#include
#include
#include
using namespace std;
struct Point
{
int x;
int y;
};
void dfs(vector<vector<int>> & map, vector<vector<bool>> & flags,
Point curr, Point end,
int step, vector & path, int & min, vector & min_path)
{
if (curr.x == end.x && curr.y == end.y)
{
if (step < min)
{
min = step;
min_path.resize(path.size());
copy(path.begin(), path.end(), min_path.begin());
}
return;
}
static const vector<vector<int>> next = {
{0, 1},
{1, 0},
{0, -1},
{-1, 0}
};
for (const auto & direction : next)
{
Point trypoint;
trypoint.x = curr.x + direction[0];
trypoint.y = curr.y + direction[1];
if (trypoint.x < 0 || trypoint.x >= map.size()
|| trypoint.y < 0 || trypoint.y >= map[0].size())
continue;
if (map[trypoint.x][trypoint.y] == 0 && flags[trypoint.x][trypoint.y] == false)
{
flags[trypoint.x][trypoint.y] = true;
path.push_back(trypoint);
dfs(map, flags, trypoint, end, step + 1, path, min, min_path);
path.pop_back();
flags[trypoint.x][trypoint.y] = false;
}
}
}
int main(void)
{
int rows = 5;
int cols = 4;
vector<vector<int>> map = {
{0, 0, 1, 0},
{0, 0, 0, 0},
{0, 0, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 1},
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
Point start;
start.x = 0;
start.y = 0;
Point end;
end.x = 3;
end.y = 2;
vector path;
path.push_back(start);
vector min_path;
int min = INT_MAX;
dfs(map, flags, start, end, 0, path, min, min_path);
cout << min << endl;
for (auto iter = min_path.begin(); iter != min_path.end(); ++iter)
{
cout << '(' << iter->x << ',' << iter->y << ')';
if (iter == min_path.end() - 1)
break;
cout << " ==> ";
}
cout << endl;
ostream_iterator<int> o_iter(cout, " ");
cout << "原图" << endl;
for (const auto & line : map)
{
copy(line.begin(), line.end(), o_iter);
cout << endl;
}
cout << "走起" << endl;
for (const auto & point : min_path)
map[point.x][point.y] = 2;
for (const auto & line : map)
{
copy(line.begin(), line.end(), o_iter);
cout << endl;
}
return 0;
}
发明深度优先算法的是John E.Hopcroft和Robert E.Tarjan。1971~1972年,他们在斯坦福大学研究图的连通性(任意两点是否可以相互到达)和平面性(图中所有的边相互不交叉。在电路板上设计布线的时候,要求线与线不能交叉。这就是平面性的一个实际应用。)时,发明了这个算法,因此获得了1986年的图灵奖。
在授奖仪式上,当年全国象棋程序比赛的优胜者说他的程序用的就是深度优先算法,这是以奇制胜的关键。此外Tarjan是另外两位图灵奖得主Robert W.Floyd和Donald E.Knuth的学生。
广度优先搜索迷宫
深度优先搜索是沿着某条路直到走不通的时候回起点继续尝试,而广度优先搜索是通过一层层扩展的方法来搜索的。
使用队列模拟。
#include
#include
#include
using namespace std;
struct Point
{
int x;
int y;
int last;
};
int main(void)
{
int rows = 5;
int cols = 4;
vector<vector<int>> map = {
{0, 0, 1, 0},
{0, 0, 0, 0},
{0, 0, 1, 0},
{0, 1, 0, 0},
{0, 0, 0, 1},
};
static const vector<vector<int>> next = {
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
{ -1, 0 }
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
Point start;
start.x = 0;
start.y = 0;
Point end;
end.x = 3;
end.y = 2;
vector search;
start.last = 0;
search.push_back(start);
int header = 0;
bool isfind = false;
while (header != search.size() && !isfind)
{
for (const auto & direction : next)
{
Point trypoint;
trypoint.x = search[header].x + direction[0];
trypoint.y = search[header].y + direction[1];
if (trypoint.x < 0 || trypoint.x >= map.size()
|| trypoint.y < 0 || trypoint.y >= map[0].size())
continue;
if (map[trypoint.x][trypoint.y] == 0 && flags[trypoint.x][trypoint.y] == false)
{
flags[trypoint.x][trypoint.y] = true;
trypoint.last = header;
search.push_back(trypoint);
}
if (trypoint.x == end.x && trypoint.y == end.y)
{
isfind = true;
break;
}
}
++header;
}
vector path;
int curr_index = search.size() - 1;
while (true)
{
path.push_back(search[curr_index]);
if (curr_index == search[curr_index].last)
break;
curr_index = search[curr_index].last;
}
for (auto iter = path.rbegin(); iter != path.rend(); ++iter)
{
cout << '(' << iter->x << ',' << iter->y << ')';
if (iter == path.rend() - 1)
break;
cout << " ==> ";
}
cout << endl;
ostream_iterator<int> o_iter(cout, " ");
cout << "原图" << endl;
for (const auto & line : map)
{
copy(line.begin(), line.end(), o_iter);
cout << endl;
}
cout << "走起" << endl;
for (const auto & point: path)
map[point.x][point.y] = 2;
for (const auto & line : map)
{
copy(line.begin(), line.end(), o_iter);
cout << endl;
}
return 0;
}
1959年,Edward F.Moore率先在“如何从迷宫中寻找出路”这一问题中提出了广度优先搜索算法。1961年,C.Y.Lee在“电路板布线”这一问题中也独立提出了相同的算法。
再解炸弹人
枚举法没考虑是否能走到对应坐标点放置炸弹
可以通过广度优先搜索或者深度优先搜索来枚举所有可以到达的点,然后在这些可以到达的点上分别统计可以消灭的敌人数。
广度优先搜索解决方案
#include
#include
#include
#include
using namespace std;
struct Point
{
int x;
int y;
};
int getnum(const vector<string> & map, int i, int j)
{
int sum = 0;
int x = i;
int y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
++x;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
--x;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
++y;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
--y;
}
return sum;
}
int main(void)
{
vector<string> map = {
"#############",
"#GG.GGG#GGG.#",
"###.#G#G#G#G#",
"#.......#..G#",
"#G#.###.#G#G#",
"#GG.GGG.#.GG#",
"#G#.#G#.#.#.#",
"##G...G.....#",
"#G#.#G###.#G#",
"#...G#GGG.GG#",
"#G#.#G#G#.#G#",
"#GG.GGG#G.GG#",
"#############"
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
static const vector<vector<int>> next = {
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
{ -1, 0 }
};
queue search;
search.push({ 3, 3 });
int max_count = getnum(map, 3, 3);
int max_x = 3;
int max_y = 3;
while (!search.empty())
{
for (const auto & direction : next)
{
Point trypoint;
trypoint.x = search.front().x + direction[0];
trypoint.y = search.front().y + direction[1];
if (trypoint.x < 0 || trypoint.x >= map.size()
|| trypoint.y < 0 || trypoint.y >= map[0].size())
continue;
if (map[trypoint.x][trypoint.y] == '.' && flags[trypoint.x][trypoint.y] == false)
{
flags[trypoint.x][trypoint.y] = true;
search.push(trypoint);
int sum = getnum(map, trypoint.x, trypoint.y);
if (sum > max_count)
{
max_count = sum;
max_x = trypoint.x;
max_y = trypoint.y;
}
}
}
search.pop();
}
cout << '(' << max_x << ',' << max_y << ")==>" << max_count << endl;
return 0;
}
深度优先搜索解决方案
每走到一个新点就统计该点可以消灭的敌人数,并从该点继续尝试往下走,直到无路可走的时候返回,再尝试走其他方向,直到将所有可以走到的点都访问一遍,程序结束。
#include
#include
#include
using namespace std;
int getnum(const vector<string> & map, int i, int j)
{
int sum = 0;
int x = i;
int y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
++x;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
--x;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
++y;
}
x = i;
y = j;
while (map[x][y] != '#')
{
if (map[x][y] == 'G')
++sum;
--y;
}
return sum;
}
void dfs(const vector<string> & map, vector<vector<bool>> & flags, int x, int y,
int & max_x, int & max_y, int & max_count)
{
int sum = getnum(map, x, y);
if (sum > max_count)
{
max_count = sum;
max_x = x;
max_y = y;
}
static const vector<vector<int>> next = {
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
{ -1, 0 }
};
for (const auto & direction : next)
{
int try_x = x + direction[0];
int try_y = y + direction[1];
if (try_x < 0 || try_x >= map.size() || try_y < 0 || try_y >= map[0].size())
continue;
if (map[try_x][try_y] == '.' && flags[try_x][try_y] == false)
{
flags[try_x][try_y] = true;
dfs(map, flags, try_x, try_y, max_x, max_y, max_count);
}
}
}
int main(void)
{
vector<string> map = {
"#############",
"#GG.GGG#GGG.#",
"###.#G#G#G#G#",
"#.......#..G#",
"#G#.###.#G#G#",
"#GG.GGG.#.GG#",
"#G#.#G#.#.#.#",
"##G...G.....#",
"#G#.#G###.#G#",
"#...G#GGG.GG#",
"#G#.#G#G#.#G#",
"#GG.GGG#G.GG#",
"#############"
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
int max_count = getnum(map, 3, 3);
int max_x = 3;
int max_y = 3;
dfs(map, flags, 3, 3, max_x, max_y, max_count);
cout << '(' << max_x << ',' << max_y << ")==>" << max_count << endl;
return 0;
}
宝岛冒险(着色问题)
用二维矩阵表示地图,数字表示海拔,0表示海洋,1-9表示陆地,给定一个降落位置,算出降落地所在岛的面积(格子数)。
广度优先搜索解决方案
从队列中取出点进行扩展(四个方向),当扩展出的点大于0时就加入队列,直到队列扩展完毕。所有被加入队列的点的总数就是小岛面积。
#include
#include
#include
#include
using namespace std;
struct Point
{
int x;
int y;
};
void bfs(vector<vector<int>> & map, vector<vector<bool>> & flags, int x, int y, int color)
{
static const vector<vector<int>> next = {
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
{ -1, 0 }
};
queue search;
search.push({ x, y });
flags[x][y] = true;
while (!search.empty())
{
for (const auto & direction : next)
{
Point trypoint;
trypoint.x = search.front().x + direction[0];
trypoint.y = search.front().y + direction[1];
if (trypoint.x < 0 || trypoint.x >= map.size()
|| trypoint.y < 0 || trypoint.y >= map[0].size())
continue;
if (map[trypoint.x][trypoint.y] >= 1 &&
map[trypoint.x][trypoint.y] <= 9 &&
flags[trypoint.x][trypoint.y] == false)
{
flags[trypoint.x][trypoint.y] = true;
search.push(trypoint);
}
}
map[search.front().x][search.front().y] = color;
search.pop();
}
}
int main(void)
{
vector<vector<int>> map = {
{1, 2, 1, 0, 0, 0, 0, 0, 2, 3},
{3, 0, 2, 0, 1, 2, 1, 0, 1, 2},
{4, 0, 1, 0, 1, 2, 3, 2, 0, 1},
{3, 2, 0, 0, 0, 1, 2, 4, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 5, 3, 0},
{0, 1, 2, 1, 0, 1, 5, 4, 3, 0},
{0, 1, 2, 3, 1, 3, 6, 2, 1, 0},
{0, 0, 3, 4, 8, 9, 7, 5, 0, 0},
{0, 0, 0, 3, 7, 8, 6, 0, 1, 2},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0}
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
for (const auto & line : map)
{
for (const auto & point : line)
cout << setw(3) << point;
cout << endl;
}
int color = -1;
for (int i = 0; i < map.size(); ++i)
{
for (int j = 0; j < map[i].size(); ++j)
{
if (map[i][j] >= 1 && map[i][j] <= 9)
{
bfs(map, flags, i, j, color);
--color;
}
}
}
cout << "共有" << (-color - 1) << "个岛屿" << endl;
for (const auto & line : map)
{
for (const auto & point : line)
cout << setw(3) << point;
cout << endl;
}
return 0;
}
深度优先搜索解决方案
这种方法又叫着色法——以某个点为源点对其邻近的点进行着色。
#include
#include
#include
using namespace std;
void dfs(vector<vector<int>> & map, vector<vector<bool>> & flags, int x, int y, int color)
{
static const vector<vector<int>> next = {
{ 0, 1 },
{ 1, 0 },
{ 0, -1 },
{ -1, 0 }
};
map[x][y] = color;
for (const auto & direction : next)
{
int tx = x + direction[0];
int ty = y + direction[1];
if (tx < 0 || tx >= map.size() || ty < 0 || ty >= map[0].size())
continue;
if (map[tx][ty] >= 1 && map[tx][ty] <= 9 && flags[tx][ty] == false)
{
flags[tx][ty] = true;
dfs(map, flags, tx, ty, color);
}
}
}
int main(void)
{
vector<vector<int>> map = {
{1, 2, 1, 0, 0, 0, 0, 0, 2, 3},
{3, 0, 2, 0, 1, 2, 1, 0, 1, 2},
{4, 0, 1, 0, 1, 2, 3, 2, 0, 1},
{3, 2, 0, 0, 0, 1, 2, 4, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 5, 3, 0},
{0, 1, 2, 1, 0, 1, 5, 4, 3, 0},
{0, 1, 2, 3, 1, 3, 6, 2, 1, 0},
{0, 0, 3, 4, 8, 9, 7, 5, 0, 0},
{0, 0, 0, 3, 7, 8, 6, 0, 1, 2},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 0}
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
for (const auto & line : map)
{
for (const auto & point : line)
cout << setw(3) << point;
cout << endl;
}
int color = -1;
for (int i = 0; i < map.size(); ++i)
{
for (int j = 0; j < map[i].size(); ++j)
{
if (map[i][j] >= 1 && map[i][j] <= 9)
{
flags[i][j] = true;
dfs(map, flags, i, j, color);
--color;
}
}
}
cout << "共有" << (-color - 1) << "个岛屿" << endl;
for (const auto & line : map)
{
for (const auto & point : line)
cout << setw(3) << point;
cout << endl;
}
return 0;
}
这就是求一个图中独立子图的个数,这个算法叫做Floodfill漫水填充法(种子填充法)。Floodfill在计算机图形学中有着非常广泛的运用,比如图像分割、物体识别等等。另外Windows下画图软件的油漆桶就是基于这个算法的,当需要给某个密闭区域涂色或者更改某个密闭区域的颜色时,程序自动选中与种子点(鼠标单击点)周边颜色相同的区域,接着将该区域替换成指定的颜色。PhotoShop的魔术棒选择工具也可以基于这个算法实现。
具体算法描述:查找种子点周边的点,将与种子点颜色相近的点(设置阈值以确定)入队作为新种子,并对新入队的种子也进行同样的扩展操作,这样就选取了和最初种子相近颜色的区域。
水管工游戏
只有两种管道,一种弯管一种直管。弯管有4种状态,直管有2种状态。
出水口有四个方向
从最左边进,从最右边出
#include
#include
using namespace std;
enum PIPE_TYPE {
TREE, L_UR, L_RD, L_DL, L_LU, I_H, I_V
};
enum IN_DIRECTION {
IN_LEFT, IN_UP, IN_RIGHT, IN_DOWN
};
struct Point
{
int x;
int y;
};
void dfs(const vector<vector> & map, vector<vector<bool>> & flags,
int x, int y, IN_DIRECTION direction, vector & path)
{
if (x == map.size() - 1 && y == map[0].size())
{
for (const auto & point : path)
cout << '(' << point.x << ',' << point.y << ')' << ' ';
cout << endl;
return;
}
if (x < 0 || x >= map.size() || y < 0 || y >= map[0].size())
return;
if (flags[x][y] == true)
return;
flags[x][y] = true;
path.push_back({ x, y });
if (map[x][y] == I_H || map[x][y] == I_V)
{
if (direction == IN_LEFT)
dfs(map, flags, x, y + 1, IN_LEFT, path);
else if (direction == IN_UP)
dfs(map, flags, x + 1, y, IN_UP, path);
else if (direction == IN_RIGHT)
dfs(map, flags, x, y - 1, IN_RIGHT, path);
else
dfs(map, flags, x - 1, y, IN_DOWN, path);
}
else
{
if (direction == IN_LEFT)
{
dfs(map, flags, x + 1, y, IN_UP, path);
dfs(map, flags, x - 1, y, IN_DOWN, path);
}
else if (direction == IN_UP)
{
dfs(map, flags, x, y + 1, IN_LEFT, path);
dfs(map, flags, x, y - 1, IN_RIGHT, path);
}
else if (direction == IN_RIGHT)
{
dfs(map, flags, x - 1, y, IN_DOWN, path);
dfs(map, flags, x + 1, y, IN_UP, path);
}
else
{
dfs(map, flags, x, y + 1, IN_LEFT, path);
dfs(map, flags, x, y - 1, IN_RIGHT, path);
}
}
flags[x][y] = false;
path.pop_back();
}
int main(void)
{
vector<vector> map = {
{I_H, L_DL, I_H, L_DL},
{L_UR, I_H, L_DL, TREE},
{L_RD, L_DL, I_H, L_UR},
{I_V, L_UR, L_UR, I_H},
{L_UR, I_H, I_H, L_LU}
};
vector<vector<bool>> flags(map.size(), vector<bool>(map[0].size(), false));
vector path;
dfs(map, flags, 0, 0, IN_LEFT, path);
return 0;
}