摘要

本题需要解决的是一般情况下的传教士和野人问题(M-C问题)。通过对问题的一般化,我们用一个三元组定义了问题的状态空间,并根据约束条件制定了一系列的操作规则,最后通过两个启发式函数,来优化搜索过程,并通过讨论,探究两个函数是否能够求解到最优解。

导言

有N个传教士和N个野人来到河边渡河,河岸有一条船,每次至多可供k人乘渡。问传教士为了安全起见,应如何规划摆渡方案,使得任何时刻,河两岸以及船上的野人数目总是不超过传教士的数目(否则不安全,传教士有可能被野人吃掉)。即求解传教士和野人从左岸全部摆渡到右岸的过程中,任何时刻满足M(传教士数)≥C(野人数)和M+C≤k的摆渡方案。

实验过程

状态空间

我们用一个三元组(m,c,b)来表示河岸上的状态,其中m、c分别代表某一岸上传教士与野人的数目,b=1表示船在这一岸,b=0则表示船不在。
约束条件是: 两岸上M≥C, 船上M+C≤2。
由于传教士与野人的总数目是一常数,所以只要表示出河的某一岸上的情况就可以了,为方便起见,我们选择传教士与野人开始所在的岸为所要表示的岸,并称其为左岸,另一岸称为右岸。显然仅用描述左岸的三元组就足以表示出整个情况了。
综上,我们的状态空间可表示为:(ML,CL,BL),其中0≤ML,CL≤N,BL∈{0, 1}。
状态空间的总状态数为(N+1)×(N+1)×2,问题的初始状态是(N,N,1),目标状态是(0,0,0)。

操作规则

该问题主要有两种操作:从左岸划向右岸和从右岸划向左岸,以及每次摆渡的传教士和野人个数。
我们可以使用一个2元组(BM,BC)来表示每次摆渡的传教士和野人个数,我们用i代表每次过河的总人数,i = 1~k,则每次有BM个传教士和BC=i-BM个野人过河,其中BM= 0~i,而且当BM!=0时需要满足BM>=BC。则从左到右的操作为:(ML-BM,CL-BC,B = 1),从右到左的操作为:(ML+BM,CL+BC,B = 0)。
例如当N=3,K=2时,满足条件的(BM,BC)有:
(0,1)、(0,2)、(0,3)、(1,0)、(1,1)、(2,0)、(2,1)、(2,2)、(3,0)、(3,1)、(3,2)、(3,3)。
由于从左到右与从右到左是对称的,所以此时一共有24种操作。

搜索策略

  1. 为了避免重复,我们将搜索过的状态记录下来,之后避开搜索这个状态。
  2. 我们把满足条件的状态称为安全状态,首先要定义出安全状态,通过对问题的分析,不难得出只有满足以下条件之一的状态才是安全的(以左岸为例):
    1)传教士与野人的数目相等;
    2)传教士都在左岸;
    3)传教士都不在左岸。
    我们只对安全的状态进行深度优先搜索,直至找到一个合法的解。
  3. 由于每一次摆渡都有多种操作可以选择,因此我们定义以下启发式函数:
    F1(x) = ML + CL
    F2(x) = ML + CL – 2B
    其中F1(x)满足A算法条件的,F2(x)满足A*算法条件。
    在每次的摆渡中,优先选择F(x)大的操作进行搜索。

结果分析

1.摆渡方案结果示例

样例1:
请输入N:3
请输入k:2
找到的解为:
0个传教士和2个野人从左岸乘船至右岸
左岸有3个传教士和1个野人
右岸有0个传教士和2个野人0个传教士和1个野人从右岸乘船至左岸
左岸有3个传教士和2个野人
右岸有0个传教士和1个野人0个传教士和2个野人从左岸乘船至右岸
左岸有3个传教士和0个野人
右岸有0个传教士和3个野人0个传教士和1个野人从右岸乘船至左岸
左岸有3个传教士和1个野人
右岸有0个传教士和2个野人2个传教士和0个野人从左岸乘船至右岸
左岸有1个传教士和1个野人
右岸有2个传教士和2个野人1个传教士和1个野人从右岸乘船至左岸
左岸有2个传教士和2个野人
右岸有1个传教士和1个野人2个传教士和0个野人从左岸乘船至右岸
左岸有0个传教士和2个野人
右岸有3个传教士和1个野人0个传教士和1个野人从右岸乘船至左岸
左岸有0个传教士和3个野人
右岸有3个传教士和0个野人0个传教士和2个野人从左岸乘船至右岸
左岸有0个传教士和1个野人
右岸有3个传教士和2个野人0个传教士和1个野人从右岸乘船至左岸
左岸有0个传教士和2个野人
右岸有3个传教士和1个野人0个传教士和2个野人从左岸乘船至右岸
左岸有0个传教士和0个野人
右岸有3个传教士和3个野人样例2:
请输入N:5
请输入k:3
找到的解为:
0个传教士和2个野人从左岸乘船至右岸
左岸有5个传教士和3个野人
右岸有0个传教士和2个野人0个传教士和1个野人从右岸乘船至左岸
左岸有5个传教士和4个野人
右岸有0个传教士和1个野人0个传教士和2个野人从左岸乘船至右岸
左岸有5个传教士和2个野人
右岸有0个传教士和3个野人0个传教士和1个野人从右岸乘船至左岸
左岸有5个传教士和3个野人
右岸有0个传教士和2个野人0个传教士和2个野人从左岸乘船至右岸
左岸有5个传教士和1个野人
右岸有0个传教士和4个野人0个传教士和1个野人从右岸乘船至左岸
左岸有5个传教士和2个野人
右岸有0个传教士和3个野人3个传教士和0个野人从左岸乘船至右岸
左岸有2个传教士和2个野人
右岸有3个传教士和3个野人1个传教士和1个野人从右岸乘船至左岸
左岸有3个传教士和3个野人
右岸有2个传教士和2个野人3个传教士和0个野人从左岸乘船至右岸
左岸有0个传教士和3个野人
右岸有5个传教士和2个野人0个传教士和2个野人从右岸乘船至左岸
左岸有0个传教士和5个野人
右岸有5个传教士和0个野人0个传教士和3个野人从左岸乘船至右岸
左岸有0个传教士和2个野人
右岸有5个传教士和3个野人0个传教士和2个野人从右岸乘船至左岸
左岸有0个传教士和4个野人
右岸有5个传教士和1个野人0个传教士和3个野人从左岸乘船至右岸
左岸有0个传教士和1个野人
右岸有5个传教士和4个野人0个传教士和2个野人从右岸乘船至左岸
左岸有0个传教士和3个野人
右岸有5个传教士和2个野人0个传教士和3个野人从左岸乘船至右岸
左岸有0个传教士和0个野人
右岸有5个传教士和5个野人

2.使用启发式函数所花费的实际费用示例:

N,k 使用F1(x)花费的实际费用 使用F2(x)花费的实际费用
N=3,k=2 11 11
N=5,k=3 15 15
N=25,k=5 95 95
N=50,k=5 195 195
N=100,k=10 409 409

下面我们来讨论两个启发式函数求解该问题时能否得到最优解:
首先,F1(x)=M+C不满足A*条件,比如状态(1, 1, 1),F1(x)=M+C=1+1=2,而实际上只要一次摆渡就可以达到目标状态,其最优路径的耗散值为1,所以不满足A*的条件。
  而F2(x)=M+C-2B是满足A*条件的,证明如下:
  先考虑船在左岸的情况。如果不考虑限制条件,也就是说,船一次可以将k个人从左岸运到右岸,然后再有一个人将船送回来。这样,船一个来回可以运过河k-1人,而船仍然在左岸。而最后剩下的k个人,则可以一次将他们全部从左岸运到右岸。所以,在不考虑限制条件的情况下,也至少需要摆渡ceil((2*N-k)/(k-1))*2+1次。其中分子上的”-k”表示剩下k个留待最后一次运过去。除以”k-1”是因为一个来回可以运过去k-1人,需要(2*N-k)/(k-1)个来回,而”来回”数不能是小数,需要向上取整。而乘以”2”是因为一个来回相当于两次摆渡,所以要乘以2。而最后的”+1”,则表示将剩下的k个运过去,需要一次摆渡。
  再考虑船在右岸的情况。同样不考虑限制条件。船在右岸,需要一个人将船运到左岸。因此对于状态(M,C,0)来说,其所需要的最少摆渡数,相当于船在左岸时状态(M+1,C,1)或(M,C+1,1)所需要的最少摆渡数,再加上第一次将船从右岸送到左岸的一次摆渡数。因此所需要的最少摆渡数为:(M+C+1)-2+1 。其中(M+C+1)的”+1”表示送船回到左岸的那个人,而最后边的”+1”,表示送船到左岸时的一次摆渡。
综合船在左岸和船在右岸两种情况下,所需要的最少摆渡次数用一个式子表示为:M+C-2B。其中B=1表示船在左岸,B=0表示船在右岸。由于该摆渡次数是在不考虑限制条件下,推出的最少所需要的摆渡次数。因此,当有限制条件时,最优的摆渡次数只能大于等于该摆渡次数。所以启发函数F2(x)是满足A*条件的。
因此,在有解的情况下,F2(x)在求解本问题时总能找到最优解。对于F1(x),当从左向右摆渡时, F1(x)=F2(x)=M+C,当从右向左摆渡时,F1(x)=M+C,F2(x)=M+C-2,即F1(x)=F2(x)+2,由于我们优先搜索F(x)较大的状态空间,而通过两个函数的关系我们可以知道,他们状态空间的转移是完全一致的。故虽然F1(x)不满足A*条件,但是在本问题中,它也是总能找到最优解。

C++代码

#include <iostream>
#include <vector>
#include <cmath>using namespace std;int X, Y;
int k;struct node
{int q[3];
};vector<node> s;
int q[500][3];
//用于存放搜索结点,q[][0]是左岸传教士人数
//q[][1]是左岸野蛮人人数,q[][2]是左岸船的数目
//q[][3]用于搜索中的父亲结点序号。
int ans=0;int op_num = 0;
int go[500][2];
int fx[500][500];//安全状态:左岸中,传教士都在or都不在or传教士人数等于野人人数
int is_safe(int state[3])
{if ((state[0]==0||state[0]==X||state[0]==state[1])&&(state[1]>=0)&&(state[1]<=Y)){return 1;}return 0;
}//是否到达目标状态
int is_success(int state[3])
{if (state[0]==0&&state[1]==0)return 1;return 0;
}//该状态是否已经访问过
int vis(int state[3])
{for (vector<node>::iterator it = s.begin(); it != s.end(); it++)if ((*it).q[0] == state[0] && (*it).q[1] == state[1] && (*it).q[2] == state[2])return 1;return 0;
}int f1(int state[3])
{return state[0]+state[1];
}int f2(int state[3])
{return state[0]+state[1]-2*state[2];
} int find_max(int cur)
{int max = -1;int op = -1;for (int j = 0; j < op_num; j++)//分别考虑可能的动作{if (fx[cur+1][j] > max){max = fx[cur+1][j];op = j;}           }if (max == -1)op = -1;return op;
}//过河操作
int search(int cur)
{if (is_success(q[cur])){ans = cur;return 1;}int state[3];int j;//cout<<"第"<<cur<<"层搜索"<<endl;//获取当前搜索结点//cout<<"展开结点"<<cur<<":"<<q[cur][0]<<' '<<q[cur][1]<<' '<<q[cur][2]<<endl;if (q[cur][2])//船在左边{for (j = 0; j < op_num; j++)//分别考虑可能的动作{state[0]=q[cur][0]-go[j][0];state[1]=q[cur][1]-go[j][1];state[2]=0;//船到了右边fx[cur+1][j]=f2(state);}j = find_max(cur);while (j != -1){fx[cur+1][j] = -1;state[0]=q[cur][0]-go[j][0];state[1]=q[cur][1]-go[j][1];state[2]=0;//船到了右边if (is_safe(state)&&!vis(state))//如果是安全状态//判断与之前展开结点是否相同{node nd;nd.q[0]=q[cur+1][0]=state[0];nd.q[1]=q[cur+1][1]=state[1];nd.q[2]=q[cur+1][2]=state[2];s.push_back(nd);//cout<<"合法结点:"<<state[0]<<' '<<state[1]<<' '<<state[2]<<endl;       if (search(cur+1))return 1;} j = find_max(cur);       }}else    //船在右边{for (j = 0; j < op_num; j++)//分别考虑可能的动作{state[0]=q[cur][0]+go[j][0];state[1]=q[cur][1]+go[j][1];state[2]=1;fx[cur+1][j]=f2(state);}j = find_max(cur);while (j != -1){fx[cur+1][j] = -1;state[0]=q[cur][0]+go[j][0];state[1]=q[cur][1]+go[j][1];state[2]=1; //船回到左边if (is_safe(state)&&!vis(state))//如果是安全状态且与之间状态不同{node nd;nd.q[0]=q[cur+1][0]=state[0];nd.q[1]=q[cur+1][1]=state[1];nd.q[2]=q[cur+1][2]=state[2];s.push_back(nd);//cout<<"合法结点:"<<state[0]<<' '<<state[1]<<' '<<state[2]<<endl;if(search(cur+1))return 1;}j = find_max(cur);}}return 0;
}int main()
{int n;cout<<"请输入N:";cin>>n;cout<<"请输入k:";cin>>k;X = Y = n;int state[3];//初始状态 node nd;nd.q[0]=state[0]=q[0][0]=X;nd.q[1]=state[1]=q[0][1]=Y;nd.q[2]=state[2]=q[0][2]=1;s.push_back(nd);//初始化操作cout<<"合法的操作组有:"<<endl;for (int i = 1; i <= k; i++)for ( int j = 0; j <= i; j++){if (j >= i-j || j == 0){go[op_num][0] = j;go[op_num][1] = i-j;cout<<go[op_num][0]<<' '<<go[op_num][1]<<endl;op_num++;               }           } cout<<endl;if (!search(0)){cout<<"无解"<<endl;return 0;}cout<<"找到的解为:"<<endl;for (int i = 0; i <= ans; i++){//cout<<q[i][0]<<' '<<q[i][1]<<' '<<q[i][2]<<endl;if (i > 0){cout<<abs(q[i][0]-q[i-1][0])<<"个传教士和"<<abs(q[i][1]-q[i-1][1])<<"个野人";if (q[i][2])cout<<"从右岸乘船至左岸"<<endl; elsecout<<"从左岸乘船至右岸"<<endl; cout<<"左岸有"<<q[i][0]<<"个传教士和"<<q[i][1]<<"个野人"<<endl; cout<<"右岸有"<<n-q[i][0]<<"个传教士和"<<n-q[i][1]<<"个野人"<<endl<<endl; }}cout<<"本次搜索所花费的费用:"<<ans<<endl;     return 0;
}

【人工智能】传教士和野人问题(M-C问题)相关推荐

  1. C语言版,传教士与野人渡河问题,使用深度优先搜索法求解(DFS),变态版,随便输入人数和船的最大载人数,人工智能经典题目,简单易懂,注释到位,没有bug

    目录 一.问题描述 二.迟来的代码 运行截图 三.简单分析 一.问题描述 有n个传教士和n个野人准备渡河,但只有一条能容纳c个人的小船,为了防止野人侵犯传教士,要求无论在何处,传教士的人数不得少于野人 ...

  2. 传教士与野人过河问题 人工智能实验算法

    问题描述 有 N 个传教士和 N 个野人来到河边渡河,河岸有一条船,每次至多可供 k 人乘渡.问:传教士为了安全起见,应如何规划摆渡方案,使得任何时刻, 河两岸以及船上的野人数目总是不超过传教士的数目 ...

  3. 人工智能实验二——prolog语言求解渡河问题(传教士和野人渡河,农夫渡河问题)实现详解

    农夫渡河问题求解 这两个问题都是渡河问题,思路和方式是一样的:给出求解Prolog代码: 问题描述 一个农夫带着一匹狼.一只羊.一颗白菜要过河, 只有一条船而且 农夫每次最多只能带一个动物或物品过河, ...

  4. 【C++】人工智能实验一 猴子摘香蕉/传教士与野人(含完整代码与状态迁移图)

    文章目录 一.猴子摘香蕉问题 1.问题描述 2.解题思路 3.实验结果及分析 实验结果一 实验结果二 实验结果三 4.实验结果 5.实验代码 二.传教士(牧师)与野人问题 1.问题描述 2.实验步骤 ...

  5. 传教士与野人问题的C++搜索实现

      人工智能引论课第一次作业:使用搜索解决传教士与野人问题. 题目要求   河岸的一侧有野人与传教士各 m m m个,有一条能容纳 n n n个人的船.传教士与野人都会划船,现在要求任何情况下,野人数 ...

  6. AI传教士和野人渡河问题-实验报告

    一 题目要求: 设有m个传教士和n个野人来到河边,打算乘一只船从左岸渡到右岸去,该船每次最多载3人.在任何时候,如果野人人数超过传教士人数,那么野人就会把传教士吃掉.他们怎样才能用这条船安全地把所有人 ...

  7. 野人与传教士过河java_传教士和野人过河(经典MC问题)

    这个问题本来是<人工智能技术导论>第三章的课后题,今天上午考试正巧考到了这道题,要我们画状态转换图,我之前思考过一点,所以写出的状态表示应该没有问题,但这些状态太多了.......,十来种 ...

  8. 【算法】传教士和野人问题

    有N个传教士和N个野人来到河边准备渡河,河岸有一条船,每次至多可供k人乘渡.问传教士为了安全起见,应如何规划摆渡方案,使得任何时刻,在河的两岸以及船上的野人数目总是不超过传教士的数目.即求解传教士和野 ...

  9. 传教士与野人过河问题(A*搜索 C++)

    传教士与野人过河问题: 任意时刻,左岸.右岸.船上如果传教士人数少于野人人数,传教士就会被野人吃掉.当然野人会划船.传教士人数为0也是可以的. 启发函数 f=g+h.  g当前结点所在解空间树的深度. ...

  10. python深度优先搜索传教士和野人_传教士和野人问题解题思路

    传教士和野人渡河问题 刘宪国050422023 野人过河问题描述如下:有三个传教士和三个野人过河,只有一条能装下两个人的船,在河的任何一方或者船上,如果野人的人数大于传教士的人数,那么传教士就会有危险 ...

最新文章

  1. .net中调用esb_都是应用集成,ESB和集成引擎对医疗业务场景的“口味”为何不同?...
  2. ashx+jQuery,一个轻量级的asp.net ajax解决方案
  3. ios开发值json数据文件的存取
  4. 【Java】 5.9 初始化块
  5. Serval and Parenthesis Sequence
  6. 微信公众号开发 [03] 结合UEditor实现图文消息群发功能
  7. 安装deepin_国产系统Deepin深度不是华为笔记本也能安装
  8. C/C++预处理宏的总结
  9. python 列表数据类型 200221
  10. 学习OpenCV2——卡尔曼滤波(KalmanFilter)详解
  11. 计算机发展初期 承载信息的媒体,兰州大学《多媒体技术基础》18秋平时作业3(含答案)...
  12. 计算机提示无法访问手机tf卡,电脑(手机)无法读取内存卡(存储卡)的解决方法大全...
  13. 我看QQ与360之争
  14. flutter APP自动更新
  15. 化工原理计算机辅助设计,化工原理课程设计心得三篇
  16. 算POTCAR要上心!
  17. 数字计算机模拟人脑,人造突触问世 计算机模拟人脑不是梦
  18. 洛谷P1478 陶陶摘苹果(升级版)视频题解
  19. 统计学习导论(ISLR)(三):线性回归(超详细介绍)
  20. jacob 给word 指定位置添加超级链接

热门文章

  1. vue加d3js实现3d饼图
  2. 2023年计算机考研院校推荐50所
  3. android开发一款app的流程
  4. python 导入自定义包
  5. python批量处理图片统一尺寸
  6. PHP(euc) + Smarty(euc) で、UTF-8やSJIS出力する方法(解決策とまとめ)
  7. 12月21诛仙服务器维护,12月31日全服停机更新维护公告
  8. 2022年中职网络空间安全国赛竞赛题解析仅代表自己的建议——2022年中职网络安全国赛竞赛试题1解析
  9. SMTP 发送邮件错误码和解决方法
  10. 蓝桥本第九届省赛刷题记录