问题1
描述
你的目标是让机器人走出迷宫。机器人面朝北,开始位置是在迷宫中间,你 可以让机器人转向面朝东、南、西、北。你可以让机器人向前走一段距离,在撞墙之前它会停步。
a. 将问题形式化,状态空间有多大?
形式化如下:
状态:状态由机器人的位置和朝向所确定,当下一个位置只有4个朝向可选,可能的状态数为4,再下一次同样有4个朝向可选,选择朝向与前进的行动是连续的。
初识状态:机器人面朝北。
转移模型:面朝任何一个方向都可向前走,除非即将撞墙。
目标测试:测试当前位置是否走出迷宫。
路径消耗:总的耗散值为机器人移动的距离.
由于机器人在移动过程中,位置朝向并没有限制条件,而机器人的移动也非离散的,因此可能状态呈指数级增长,状态空间为无穷大。
b. 在迷宫中游走,在两条路或更多路交叉的路口可以转弯,重新形式化这个问题,现在状态空间有多大?
状态:状态受机器人的位置所确定,当所处的位置为交叉路口时,才可转弯至下一个状态,状态需要记录下交叉路口的位置以及个数。
初识状态:机器人在迷宫正中间面朝北。
转移模型:走到下一个交叉路口,除非即将撞墙。
目标测试:测试当前位置是否走出迷宫。
路径消耗:总的耗散值为移动距离.
机器人在移动过程中,状态空间受交叉路口个数影响(在其他时刻不改变朝向),当交叉路口为1时,可能状态为4,为2时可能状态为8,当交叉路口为n时状态空间为4n。
c. 从迷宫的任一点出发,我们可以朝四个方向中的任一方向前进直到可以转弯的地方,而且我们只需要这样做,重新对这个问题进行形式化,我们只需要记录机器人的方向吗?
状态:状态由机器人的位置和朝向所确定,当所处的位置为交叉路口时,下一个位置朝向有4个朝向可选,并且,还可转弯至下一个状态,状态需要记录下交叉路口的位置以及个数。
初识状态:机器人在迷宫正中间面朝北。
转移模型:面朝任何一个方向都可向前走,除非即将撞墙。
目标测试:测试当前位置是否走出迷宫。
路径消耗:总的耗散值为移动距离.
不需要记录方向信息,只需要记录交叉路口位置,因为在交叉路口才会有新的状态,与方向信息无关。
d. 在我们最初对问题的最初描述中已经对现实世界进行了抽象,限制了机器人的行动并移除了细节,列出三个我们做的简化。
简化1:机器人的移动速度
简化2:机器人以何种方式作为驱动力
简化3:机器人传感器种类,摄像头或红外测距
问题2
传教士和野人问题,三个传教士和三个野人在河的一岸,有一条能载一个人或者两个人的船。请设法使所有人都渡到河的另一岸,要求在任何地方野人数都不能多于传教士的人数。这个问题在AI中很有名,是因为它是第一个从分析的观点探讨问题形式化的论文主题。
a. 请对该问题进行详细形式化,只描述确保该问题求解所必需的特性。画出完整的状态空间图。
问题解决参考自:https://www.jianshu.com/p/0af3a6bb1e43
一些前提:从左岸到右岸是有往返的,其中传教士和野人均可以开船,状态在转化过程中不能出现循环,即舍弃掉会恢复上一步的状态。问题形式化如下:
状态:用一个三元组(m,c,b)来表示河岸上的状态,其中m、c分别代表某一岸上传教士与野人的数目,b用来表示船是从左岸到对岸还是返程(b=1表示船在这一岸,b=0则表示船不在)。约束条件是:两岸上船上
≤2,为船上的传教士和野人数量。
初识状态:三元组初态为(3,3,1),即左岸野人和传教士都为3。
转移模型:船从左岸开到右岸,b从0-->1,再从1-->0。只要不满足限制条件,则进入下一个状态。
目标测试:是否达到终态(0,0,0)。
路径消耗:走的路径长度.
状态空间图:
b.应用合适的搜索算法求出该问题的最优解。对于这个问题检查重复状态是个好主意吗?
利用深度优先搜索算法求解该问题,代码如下:
#include<iostream>
#include<cstdio>
#include<map>
#include<string>
#include<set>
#include<vector>
using namespace std;
using namespace std;
int N;
set<string>ans;
void print(vector<int>way) {
int i = 0;
string h;
for (i = 1; i < way.size(); i++) {
if (way[i] < 100) {
h += "0";
}
h += to_string(way[i]);
h += " ";
}
ans.insert(h);
}
void dfs(int pre, int ni, int nj, int b, set<int>s, vector<int>way) {
int now = ni * 100 + nj * 10 + b;//
s.insert(pre);
if (s.count(now) || (N - ni) < 0 || (N - nj) < 0 || ni < 0 || nj < 0 || (ni < nj && ni != 0) || ((N - ni) < (N - nj) && (N - ni) != 0)) {
// 限制条件,若传教士和野人数都小于0,或者剩下的传教士比野人少都直接return
return;
}
if (pre == 0) {
// 如果上一轮就截止了,pre == 0 代表已到终点状态
print(way);
return;
}
way.push_back(pre);
if (b == 1) { // 需前往对岸
// 传教士+野人=1
dfs(now, ni - 1, nj, 0, s, way);
dfs(now, ni, nj - 1, 0, s, way);
// 传教士+野人=2
dfs(now, ni - 2, nj, 0, s, way);
dfs(now, ni, nj - 2, 0, s, way);
dfs(now, ni - 1, nj - 1, 0, s, way);
}
else {
// 传教士+野人=1
dfs(now, ni + 1, nj, 1, s, way);
dfs(now, ni, nj + 1, 1, s, way);
// 传教士+野人=2
dfs(now, ni + 2, nj, 1, s, way);
dfs(now, ni, nj + 2, 1, s, way);
dfs(now, ni + 1, nj + 1, 1, s, way);
}
}
//Ques20201024
int main() {
int b = 1;
// 初始化b的值
int m, c;
int pre = -1;
set<int>s;
chooseN = 3;
N = c = m = 3;
vector<int>way;
dfs(pre, m, c, b, s, way);
for (auto it = ans.begin(); it != ans.end(); it++) {
cout << *it << endl;
}
}
运行效果
求解得到的最优路径共4条,如上图解空间中所示。
c. 这个问题的状态空间很简单,你认为是什么导致人们求解它很困难?
在求解过程中可能运过去两个人,然后又把同样的两个人运回来了(或者使用别的形式但是依旧是循环,即没有起到作用),所以要去除这种重复,这是问题的难解决的地方。最好方法是使用带有记忆的状态,如果之前遇到过这种状态就直接return,返回上一状态。