首先让我们看看BFS是个什么样的运作原理:
BFS是以广度为优先,也就是先把同级的每一个节点(例如2)先进行运算,同时将和它相邻的需要运算的节点入队,稍后再算(例如2对应的5、6),当同级的所有节点都运算完了的时候(2、3、4),由于队列先进先出的特点,自然轮到了下一级节点(5、6、7、8、9)
然后再看看在BFS中,树是如何剪枝的:
在很多问题中(尤其是棋盘移动之类的),会出现多个节点连上同一个节点的情况(如图中的2、3、4都连着下一级的6),这时候根据条件判断哪一条更优。例如图中红色数值越大越优,当判断到2连6的数字是20时,6的红色值被赋予20,与2相连;当判断到3连6的数字是30时,30比20大,6的红色值被赋予30,与3相连;当判断到4连6的数字是10时,10比30小,6的红色值不变,仍然与3相连;这样,就剪断了2和6,4和6的枝。
那么,没有剪枝的话,会是怎样的呢?
不多解释了,来看一个例子,题目粘下来是这样:
Michael喜欢滑雪,这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。
输出最长区域的长度,本例中是 25。
先上代码吧:
#include <iostream>
#include <queue>
using std::queue;
using std::cout;
const int Length = 5;
const int Height[Length][Length] =
{
{1, 2, 3, 4, 5},
{16, 17, 18, 19, 6},
{15, 24, 25, 20, 7},
{14, 23, 22, 21, 8},
{13, 12, 11, 10, 9}
};
int maxsteps[Length][Length] = { 0 };
const int direction[4][2] = { {1,0},{0,1},{-1,0},{0,-1} }; //右、下、左、上
//记录每一个点的坐标和从起点到这个点所用的步数
struct point
{
int x;
int y;
int step;
point(int i, int j, int k):x(i),y(j),step(k) {} //使用初始化列表的构造函数
};
queue<point> pts;
int steps = 0;
void clear(queue<point>& q); //定义一个清空队列的函数
void clear(int arr[Length][Length]); //定义一个清空步数缓存的函数
void bfs(point pt)
{
cout << " 正在点(" << pt.x << ',' << pt.y << ")\n";
for (int dir = 0; dir < 4; dir++)
{
int next_x = pt.x + direction[dir][0];
int next_y = pt.y + direction[dir][1];
if (0 <= next_x && next_x < Length && 0 <= next_y && next_y < Length) //判断方向是否有效
{
cout << " 正在测试有效点(" << next_x << ',' << next_y << ")高度\n";
if (Height[next_x][next_y] < Height[pt.x][pt.y]) //判断下一个方向是否高度更低,能够前进
{
if (maxsteps[next_x][next_y] < pt.step + 1) //判断如果向该方向再走一步,是否步数比其他已经尝试的方式都大(也就是砍掉不可能是最大的路径)
{
pts.push(point(next_x, next_y, pt.step + 1)); //将下一个可行的点存入队列中
maxsteps[next_x][next_y] = pt.step + 1; //更新步数表格
}
}
}
}
steps = pt.step + 1 > steps ? pt.step + 1 : steps;
return;
}
int main()
{
int max_steps = 0;
for (int start_x = 0; start_x < Length; start_x++)
{
for (int start_y = 0; start_y < Length; start_y++)
{
cout << "正在以(" << start_x << ',' << start_y << ")为起点\n";
steps = 0;
clear(maxsteps);
clear(pts);
bfs(point(start_x, start_y, 1));
while (!pts.empty())
{
point next_point = pts.front();
pts.pop();
bfs(next_point);
}
steps--; //因为最后一次无法前进的点还记了一次步数,所以要减掉
cout << "步数: " << steps << '\n';
max_steps = steps > max_steps ? steps : max_steps; //计入最大步数
}
}
cout << max_steps;
return 0;
}
void clear(queue<point>& q)
{
queue<point> empty;
swap(empty, q);
}
void clear(int arr[Length][Length])
{
for (int i = 0; i < Length; i++)
{
for (int j = 0; j < Length; j++)
{
arr[i][j] = 0;
}
}
}
- #include <iostream>
- #include <queue>
- using std::queue;
- using std::cout;
- const int Length = 5;
- const int Height[Length][Length] =
- {
- {1, 2, 3, 4, 5},
- {16, 17, 18, 19, 6},
- {15, 24, 25, 20, 7},
- {14, 23, 22, 21, 8},
- {13, 12, 11, 10, 9}
- };
- int maxsteps[Length][Length] = { 0 };
- const int direction[4][2] = { {1,0},{0,1},{-1,0},{0,-1} }; //右、下、左、上
- //记录每一个点的坐标和从起点到这个点所用的步数
- struct point
- {
- int x;
- int y;
- int step;
- point(int i, int j, int k):x(i),y(j),step(k) {} //使用初始化列表的构造函数
- };
- queue<point> pts;
- int steps = 0;
- void clear(queue<point>& q); //定义一个清空队列的函数
- void clear(int arr[Length][Length]); //定义一个清空步数缓存的函数
- void bfs(point pt)
- {
- cout << " 正在点(" << pt.x << ',' << pt.y << ")\n";
- for (int dir = 0; dir < 4; dir++)
- {
- int next_x = pt.x + direction[dir][0];
- int next_y = pt.y + direction[dir][1];
- if (0 <= next_x && next_x < Length && 0 <= next_y && next_y < Length) //判断方向是否有效
- {
- cout << " 正在测试有效点(" << next_x << ',' << next_y << ")高度\n";
- if (Height[next_x][next_y] < Height[pt.x][pt.y]) //判断下一个方向是否高度更低,能够前进
- {
- if (maxsteps[next_x][next_y] < pt.step + 1) //判断如果向该方向再走一步,是否步数比其他已经尝试的方式都大(也就是砍掉不可能是最大的路径)
- {
- pts.push(point(next_x, next_y, pt.step + 1)); //将下一个可行的点存入队列中
- maxsteps[next_x][next_y] = pt.step + 1; //更新步数表格
- }
- }
- }
- }
- steps = pt.step + 1 > steps ? pt.step + 1 : steps;
- return;
- }
- int main()
- {
- int max_steps = 0;
- for (int start_x = 0; start_x < Length; start_x++)
- {
- for (int start_y = 0; start_y < Length; start_y++)
- {
- cout << "正在以(" << start_x << ',' << start_y << ")为起点\n";
- steps = 0;
- clear(maxsteps);
- clear(pts);
- bfs(point(start_x, start_y, 1));
- while (!pts.empty())
- {
- point next_point = pts.front();
- pts.pop();
- bfs(next_point);
- }
- steps--; //因为最后一次无法前进的点还记了一次步数,所以要减掉
- cout << "步数: " << steps << '\n';
- max_steps = steps > max_steps ? steps : max_steps; //计入最大步数
- }
- }
- cout << max_steps;
- return 0;
- }
- void clear(queue<point>& q)
- {
- queue<point> empty;
- swap(empty, q);
- }
- void clear(int arr[Length][Length])
- {
- for (int i = 0; i < Length; i++)
- {
- for (int j = 0; j < Length; j++)
- {
- arr[i][j] = 0;
- }
- }
- }
#include <iostream>
#include <queue>
using std::queue;
using std::cout;
const int Length = 5;
const int Height[Length][Length] =
{
{1, 2, 3, 4, 5},
{16, 17, 18, 19, 6},
{15, 24, 25, 20, 7},
{14, 23, 22, 21, 8},
{13, 12, 11, 10, 9}
};
int maxsteps[Length][Length] = { 0 };
const int direction[4][2] = { {1,0},{0,1},{-1,0},{0,-1} }; //右、下、左、上
//记录每一个点的坐标和从起点到这个点所用的步数
struct point
{
int x;
int y;
int step;
point(int i, int j, int k):x(i),y(j),step(k) {} //使用初始化列表的构造函数
};
queue<point> pts;
int steps = 0;
void clear(queue<point>& q); //定义一个清空队列的函数
void clear(int arr[Length][Length]); //定义一个清空步数缓存的函数
void bfs(point pt)
{
cout << " 正在点(" << pt.x << ',' << pt.y << ")\n";
for (int dir = 0; dir < 4; dir++)
{
int next_x = pt.x + direction[dir][0];
int next_y = pt.y + direction[dir][1];
if (0 <= next_x && next_x < Length && 0 <= next_y && next_y < Length) //判断方向是否有效
{
cout << " 正在测试有效点(" << next_x << ',' << next_y << ")高度\n";
if (Height[next_x][next_y] < Height[pt.x][pt.y]) //判断下一个方向是否高度更低,能够前进
{
if (maxsteps[next_x][next_y] < pt.step + 1) //判断如果向该方向再走一步,是否步数比其他已经尝试的方式都大(也就是砍掉不可能是最大的路径)
{
pts.push(point(next_x, next_y, pt.step + 1)); //将下一个可行的点存入队列中
maxsteps[next_x][next_y] = pt.step + 1; //更新步数表格
}
}
}
}
steps = pt.step + 1 > steps ? pt.step + 1 : steps;
return;
}
int main()
{
int max_steps = 0;
for (int start_x = 0; start_x < Length; start_x++)
{
for (int start_y = 0; start_y < Length; start_y++)
{
cout << "正在以(" << start_x << ',' << start_y << ")为起点\n";
steps = 0;
clear(maxsteps);
clear(pts);
bfs(point(start_x, start_y, 1));
while (!pts.empty())
{
point next_point = pts.front();
pts.pop();
bfs(next_point);
}
steps--; //因为最后一次无法前进的点还记了一次步数,所以要减掉
cout << "步数: " << steps << '\n';
max_steps = steps > max_steps ? steps : max_steps; //计入最大步数
}
}
cout << max_steps;
return 0;
}
void clear(queue<point>& q)
{
queue<point> empty;
swap(empty, q);
}
void clear(int arr[Length][Length])
{
for (int i = 0; i < Length; i++)
{
for (int j = 0; j < Length; j++)
{
arr[i][j] = 0;
}
}
}
首先定义好高度的二维数组,以及用来存到某一点所需步数的临时二维数组;为了代码简洁,同时定义好含四个方向的数组,以及包含着x、y坐标和已经移动的步数的point结构(并且写好构造函数)
主要思路是这样的:
遍历所有的起点
–清空临时数据
–检测起点的可移动信息,并将可以去的下一个点入队(函数bfs())
–队列非空时,对每个元素再检测可移动信息,并将下一个点入队(函数bfs())
–计入每个起点最终的最大移动步数
函数bfs的思路:
遍历四个方向的下一个点进行测试
–判断该方向下一个点是否还在二维数组中
—-判断下一个点的高度是否比当前点更小
——判断下一个点是否没有别的路径到达过,或者有但是路径比正在测试的路径短(注意:这里正是树剪枝的办法,直接剪掉不可能是最长的路径)
——–如果均为是,将下一个可以到达的点入队,并且更新步数的表格
遍历完之后,总步数+1(因为最后一次无路可走也会导致步数+1,最终结果要-1)
样例输出:
首先,最终结果输出的是25,也就是从(2,2)点的“25”开始出发的最长路径。
然后以图上的(4,4),既“9”为原点为例,可以看出探索的路径:
最初在起点(4,4),判断到下一个存在的点有(3,4)和(4,3),挑选出没有经过过、并且高度更低的点(3,4),再判断存在的点(4,4),(2,4)和(3,3)。因为高度只能下降,可以没有必要去把曾经经过的点标记为已经过。通过多次测试,最终按照9、8、7、6、5、4、3、2、1的顺序找到了步数为9的最大路径。
以下是完整的输出结果: