一.作业任务
修道士和野人问题:设有三个修道士和3个野人来到河边,打算用一条船从河的左岸渡到河的右岸去。但该船每次只能装载两个人,在任何岸边野人的数目都不得超过修道士的人数,否则修道士就会被野人吃掉。假设野人服从任何一种过河安排,请规划出使全部6人安全过河的方案。
问题提示:应用状态空间表示和搜索方法时,可用(Nm,Nc)来表示状态描述,其中Nm,Nc分别为传教士和野人的人数。初始状态为(3,3),而可能的中间状态为(0,1),(0,2),(0,3),(1,1),(2,1),(2,2),(3,0),(3,1),(3,2)等。
编程实现修道士和野人问题算法,演示算法运行过程(即过河的各步方案)和结果。

二.运行环境
Windows10,codeblocks,myeclipse。
三.算法介绍
修道士与野蛮人问题是深度优先回溯算法问题。回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。用回溯算法解决问题的一般步骤:
1、 针对所给问题,定义问题的解空间,它至少包含问题的一个(最优)解。
2 、确定易于搜索的解空间结构,使得能用回溯法方便地搜索整个解空间 。
3 、以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
深度优先遍历的情况和八皇后类似,不过在本问题中要考虑船在两岸的不同情况。本次设计除了满足3名修道士三名野蛮人船只容量为2的情况外,由于该情况可以不考虑船上野蛮人更多的情况(甚至就可以直接初始化船只情况),故拓展了任意数量修道士野蛮人船只容量的问题,并特意编写了两个启发式函数来缩短搜索代价,其中第二个启发式函数为a*算法,并将不同方式的搜索代价求出以比对。
后来也做了java程序界面,由于代码较长单独附在文件夹内不在报告内。
四.程序分析
在深度优先回溯求解时,没有设置变量求解代价和完成步数变量,后来直接再该代码上加入启发式函数,出现卡死(递归太大),后来重现写了一个专门讨论启发函数代价的代码,在改代码中,修改了很多地方包括输出格式、中间过程保存方式、及结构的使用。对于启发式函数的讨论如下:
启发式函数部分加入三个函数:

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;
}

其中f1为普通启发式函数,f2为a算法,find_max函数为使用俩启发式函数以减少工作量的函数,在设计启发式函数的过程中参考了csdn上某博主的代码,但是该博主最后代码设计出现逻辑写反了的错误,已经和他反应该问题:

关于启发式函数:由于每一次摆渡都有多种操作可以选择,因此我们定义以下启发式函数:
F1(x) = ML + CL
F2(x) = ML + CL – 2B
其中F1(x)满足A算法条件的,F2(x)满足A
算法条件。
在每次的摆渡中,优先选择F(x)大的操作进行搜索。
首先,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人,需要(2N-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
条件,但是在本问题中,它也是总能找到最优解。
简言之,每次采取措施,都以运送最多人数到目标方案为前提!
五.界面截图
1、回溯求解所有情况(修道士3人野蛮人3人船只容量2):

2、拓展(任意数目,修道士4人野蛮人4人船只容量4为例)

3、启发式算法,在我的思路中,fx数组用来储存启发式搜索的判断,其中fx[cur+1][j]=2n-f2(state)为最低代价求解,改为fx[cur+1][j]=-f2(state)可获得最大代价求解的情况,下面做比较:
最低代价输入:

其中下面每行四个数字为我储存的每次搜索过程,其行数即搜索代价,两次结果:
启发式:

改写a
算法求出最大代价:

可以看到搜索性能差异非常明显。
4、根据a*改出来的java程序截图:

六.结果分析
本次作业通过查询资料了解了修道士和野蛮人问题的由来和含义,然后采用回溯算法来完成了这个项目的主体部分,简要输出结果的同时,拓展了更多数量问题和启发式算法。
七.作业体会
纸上得来终觉浅,亲手实践方能将知识真正理解。透彻修道士与野蛮人算法虽然不算是难度偏高的例子,但是对于我这样刚刚了解人工智能的大学生而言,拥有恰到好处的挑战性,通过努力还是可以完成算法并实现它的展示。这个过程让我复习了回溯思想并加深了我对人工智能的理解,此外,对于启发式函数的探索更是让我受益匪浅。
八.参考资料
《人工智能(21世纪高等学校计算机专业实用规划教材)》清华大学出版社
《修道士与野蛮人问题》百度百科
九.附件(源代码)
1、遍历所有方式:

#include <iostream>
#include <vector>
#include <stdio.h>
int x,y,z,w=0,idea=0;//x为修道士初始数目,y为野蛮人初始数目,z为船容量,w为船只载客方式数量,idea为解数目
using namespace std;
int way[100][2];
vector <string> hist;
void print() //打印输出
{for(int i=0;i<hist.size();i++)cout<<hist[i]<<endl<<endl;
}
int corr(int frair_num,int bar_num,int boat)
{if(frair_num<0||frair_num>x) return 0; //修道士数据非法if(bar_num<0||bar_num>x) return 0;     //野蛮人数据非法if((frair_num<bar_num&&frair_num)||(x-frair_num&&(x-frair_num)<(y-bar_num))) return 0;   //修道士被野蛮人吃掉的情况if(boat!=0&&boat!=1) return 0;char text[50];sprintf(text, "frair_num=%d, bar_num=%d, boat(初始河岸为1,目标河岸为0)=%d", frair_num,bar_num,boat);for(int i=0; i<hist.size(); i++)          //此情况之前出现过if(text == hist[i])return 0;hist.push_back(text);return  1;}void search(int frair_num,int bar_num,int boat)
{if((frair_num==0)&&(bar_num==0)&&(boat==0)){idea++;cout<<"第"<<idea<<"种解法如下"<<endl;print();return;}for(int i=0;i<w;i++){if(boat==1){int f=frair_num-way[i][0];int b=bar_num-way[i][1];int t=boat-1;if(corr(f,b,t)){search(f,b,t);hist.pop_back();}}if(boat==0){int f=frair_num+way[i][0];int b=bar_num+way[i][1];int t=boat+1;if(corr(f,b,t)){search(f,b,t);hist.pop_back();}}}
}
int main()
{char text[50];cout<<"请输入修道士数量,野蛮人数量及船只容量"<<endl;cin>>x>>y>>z;sprintf(text, "frair_num=%d, bar_num=%d, boat(初始河岸为1,目标河岸为0)=%d", x,y,1);hist.push_back(text);for(int i=z;i>0;i--)  //船上修道士数量非0{for(int j=0;j<=i,j<=z-i;j++){way[w][0]=i;way[w][1]=j;w++;}}for(int j=1;j<=z;j++)  //船上修道士数量为0{way[w][0]=0;way[w][1]=j;w++;}cout<<"船只上的可行载客方式:"<<endl;for(int j=0;j<w;j++){cout<<"船上修道士"<<way[j][0]<<"人,"<<"野蛮人"<<way[j][1]<<"人。"<<endl<<endl;}search(x,y,1);cout<<"总方案数量为"<<idea<<endl;return 0;}

2、启发式:

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;int X, Y;
int k;
int n;int dai=0;
struct node
{int q[3];
};vector<node> s;
int q[500][4];
//用于存放搜索结点,q[][0]是左岸传教士人数
//q[][1]是左岸野蛮人人数,q[][2]是左岸船的数目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;//船到了右边cout<<abs(q[cur][0])<<" "<<abs(q[cur][1])<<" "<<1<<" "<<j<<endl; dai++;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; //船回到左边cout<<abs(q[cur][0])<<" "<<abs(q[cur][1])<<" "<<0<<" "<<j<<endl; dai++;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()
{cout<<"请输入修道士野蛮人数:";cin>>n;cout<<"请输入船只容量:";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;cout<<"本次搜索所花费的费用:"<<dai<<endl;return 0;
}

3、见文件夹

修道士和野人问题:所有解、启发求解、简单界面相关推荐

  1. 1. A星算法解决修道士与野人问题

    A星算法解决修道士与野人问题 1. 运行环境 CPU:I5-10400 内存:16GB 系统:Win10 64位专业版,20H2 IDE:Vistual Studio 2019专业版 2. 问题描述 ...

  2. 修道士与野人问题——C++源代码,伪代码,详细分析

    前言:这一个经典的问题,可以把问题转换成数据结构中的 图 来解决.本博客节选自我去年7月份的数据结构报告 问题描述 假设有 n 个修道士和 n 个野人准备渡河,但只有一条能容纳 c 人的小船,为了防止 ...

  3. 修道士和野人java_野人与修道士问题——详细分析与C++源代码

    前言:这一个经典的问题,可以把问题转换成数据结构中的 图 来解决.本博客节选自我去年7月份的数据结构报告 问题描述 假设有 n 个修道士和 n 个野人准备渡河,但只有一条能容纳 c 人的小船,为了防止 ...

  4. 用状态空间方法求解修道士与野人问题

    目录 一.状态空间表示法回顾 1.问题状态空间的构成 2.用状态空间表示问题的步骤 二.使用状态空间法求解修道士与野人问题 1.问题描述 2.状态空间求解步骤 总结 一.状态空间表示法回顾 状态空间表 ...

  5. 使用dfs求解修道士和野人问题

    原文链接: 使用dfs求解修道士和野人问题 上一篇: js Set 的使用 下一篇: VMware 挂载U盘 1.问题描述 :这是一个古典问题.假设有n个道士和n个野人准备渡河.但只有一条能容纳c人的 ...

  6. 修道士和野人过河问题 A*算法 人工智能

    /** * 2014-08-25 by Liy * 修道士和野人过河问题,一共有3个修道士和3个野人,1条船 * 1.船最多可乘坐2人 * 2.两岸边 野人的数量不能多于修道士的数量,否则修道士会被吃 ...

  7. 人工智能实践作业-修道士和野人过河问题

    人工智能实践作业-修道士和野人过河问题: 用编程语言编写和调试一个基于深度优先搜索法的解决"野人与传教士过河"问题的程序.目的是学会运用知识表示方法和搜索策略求解一些考验智力的简单 ...

  8. 修道士和野人java_修道士和野人问题

    休闲时刻看看神经网络方面的书,发现了修道士和野人的问题,不禁勾引起我写算法的欲望,曾经的三只大老虎三只小老虎过河问题.人狼羊白菜过河问题.汉诺塔.哈夫曼等等各种算法瞬间在脑海中约隐约现,修道士和野人问 ...

  9. 2.修道士和野人问题

    2.修道士和野人问题:设有三个修道士和3个野人来到河边,打算用一条船从河的左岸渡到河的右岸去.但该船每次只能装载两个人,在任何岸边野人的数目都不得超过修道士的人数,否则修道士就会被野人吃掉.假设野人服 ...

最新文章

  1. TCP与UDP通信协议及Java实现
  2. Linux 下安装和配置TSM 7.1
  3. bzoj 1705: [Usaco2007 Nov]Telephone Wire 架设电话线【dp】
  4. KDT#91 DW/BI系统的营销(二)
  5. 函数学习-bool()
  6. Silverlight 用DependencyProperty 自定义ImageButton控件 定义属性
  7. 4.2.1 路由算法与路由协议概述(静态路由和动态路由---距离-向量路由算法---链路状态路由算法、层次路由)
  8. Spark 运行机制
  9. python输入一个人的名字_怎样用c语言做到输入一个人的名字才会输出一个心?
  10. 韩顺平循序渐进学java 第13讲 抽象类.接口
  11. 配置 aws cli_AWS CLI教程–如何安装,配置和使用AWS CLI了解您的资源环境
  12. linux上mysql分区磁盘位置_Linux下Oracle软件、数据文件等所在的磁盘分区空间不足的解决思路...
  13. 决策树 结构_决策树模型是什么?5步了解好看模型图
  14. oracle如何读取到从n行到m行的数据_关系型数据库进阶之查询优化
  15. 不插字段,直接利用OracleSpatial计算
  16. Ultra Edit中编辑并一键运行Ansys命令流
  17. 塔科夫服务器修改器,逃离塔科夫修改器+15项
  18. 迭代模型(Iterative Model)
  19. Xcode build解密
  20. 【黄啊码】微信朋友圈的几分钟/几小时前如何实现

热门文章

  1. Git Bash中出现:error: failed to push some refs to ‘https://gitee.com/xxx/xxx.git‘错误的解决方法
  2. 【Delphi】ADOConnection连接数据库、ADOQuery执行SQL语句、StringGrid填充数据集
  3. toad与oracle建立数据库,用Toad for Oracle创建数据库表空间和用户
  4. 如何赢得消费者信任?
  5. python语言是一个优秀的面向对象语言_Python语言是面向对象的
  6. DoS网络攻击的类型
  7. Vue3 - filters 过滤器为什么被移除放弃?取而代之的解决方案又是什么?
  8. Java超级工具_Java 16个超级实用的工具类
  9. Pandas-DataFrame使用
  10. kotlin小悟-这个继承有点不一样