算法题目打卡:Ques20201024

问题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,返回上一状态。 

猜你喜欢

转载自blog.csdn.net/Toky_min/article/details/109259297
今日推荐