有这么一类编程问题,满足如下的几个特性:
1、解空间是有限的,并且规模不太大(能够在有限时间内搜索完所有解);
2、可以从某个解的一部分开始,通过遍历、添加从这个部分开始能够到达的所有可能性,最终能够到达所有的解。
3、很难找到一个确保能得到最优解的策略。
这些问题往往就能用回溯法来解决。
回溯法有通用解法的美称,对于很多问题,如迷宫等都有很好的效果。回溯算法实际上一个类似枚举的深度优先搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回(也就是递归返回),尝试别的路径。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。回溯法说白了就是穷举法。回溯法一般用递归来解决。
总的来说,如果要解决一个回溯法的问题,通常要确定三个元素:
1、选择。每个特定的解肯定是由一步步构建而来的,而每一步怎么构建,肯定都是有限个选择,必须要知道一个明确的从上一步到下一步的选择依据。比如,在迷宫寻路过程中,从一个点开始走下一步的选择是有限的,一般是上下左右四个方向上可以走的通路。一般是通过多个if或者for循环来穷举这些选择。
2、条件。对于每个特定的解的某一步,他必然要符合某个解要求符合的条件,如果不符合条件,就要回溯,其实回溯也就是递归调用的返回。
3、结束。当到达一个特定结束条件时候,就认为这个一步步构建的解是符合要求的解了。在迷宫寻路问题中,结束条件一般就是到达出口。此时可以有两种选择:
(1)把解存下来或者打印出来。有时候还需要构建一个数据结构,把所有符合要求的解存起来,便于当得到所有解后,把解全部输出。这个数据结构要么是全局的,要么作为参数之一传递给递归函数(引用传递)。这种方法适用于“输出所有符合要求的解”的场合。
(2)将解与“之前找到的最优解作比较,保留更优的”。通常来说“之前找到的最优解”可以用全局变量,或者作为参数之一传递给递归函数(引用传递)。这适用于寻找“最优的那个解”的场合。本文的迷宫寻路例子就是这种情况。
当然,上面提到的要素可能会有不严谨的地方。不过基本上八九不离十。这类问题在各大互联网公司的在线编程考题中频繁出现,因此,对于所有立志于进入互联网公司成为一名软件工程师的同学,掌握这类题的解法是非常必要的事实上我之前已经写过一些讨论这类问题的文章,但缺乏一个系统性的讨论和分析。下面就以阿里的暑期实习生招聘在线编程题(迷宫寻路类问题)为例,讨论一下解决这类问题在编程上需要注意的一些问题。
例1 最短路径
问题描述:
在自动化仓库中有若干障碍物,机器人需要从起点出发绕过这些障碍物到终点搬取货柜,现试求机器人从起点运动到终点用时最短的路径。已知机器人只能沿着东西方向或南北方向移动,移动的速度为1m/s,机器人每转向90度需要花费1s。
输入:
第一行:起点位置坐标及机器人朝向,如:
1 0 EAST
代表机器人初始坐标为x=1,y=0,机器人面朝东方。
第二行:终点位置坐标及机器人朝向,如:
0 2 WEST
代表机器人需要移动至点x=0,y=2,且面朝西方。
接下来输入的是地图:
首先是两个数字r,c,代表有地图数据有多少行与多少列,如:
2 3
0 1 0
0 0 0
其中,左上角为坐标原点,从左向右为x轴增大的方向是东方,从上到下为y轴增大的方向是南方。
地图中1代表有障碍物,机器人不能前往,0代表无障碍物机器人可以前往地图中相邻的每两个点之间的距离为1m。
0 <= l,w <= 128
输出:
机器人从起点移动到终点所需要的最短秒数,当不可达时输出65535。
这就是典型的迷宫寻路问题。这道题里我的搜索策略是:搜索所有“机器人先从起点移动到终点。如果机器人到达终点时朝向与要求不同,就原地转身到需要的朝向”的耗时,从中找最短。
我先把我的C++代码贴出来(只测试一次输入):
#include<iostream>
#include<string>
#include<vector>
using namespace std;
void search(vector<vector<int>> &left,//剩下能走的地方
int rows,int cols,//迷宫的规模
int startx, int starty, string startdirection,//搜索的起点
int final_x, int final_y, string final_direction,//搜索的终点
int &result,//之前已经得到的最短时间
int timespent//已经耗费的时间
)
{
if (startx == final_x && starty == final_y && startdirection == final_direction) {
//到达搜索的终点
if (timespent < result) {
result = timespent;
}
}
else if (startx == final_x && starty == final_y &&