商人渡河问题(MATLAB版)
目录
- 一、问题描述
- 二、算法思想
- 三、如何实现
- 四、流程图
- 1. 主程序
- 2. 递归函数 crossRiver
- 五、源程序代码
- 1. 主程序 DFS.m
- 2. 过河函数 crossRiver.m
- 3. 输出解的函数 showSolution.m
- 六、求解结果
- 七、小结
更新于 2020.08.03
一、问题描述
3 名商人各带 1 名随从过河(从西岸到东岸),一只小船最多能容纳 2 人。随从们约定:在河的任意一岸,若随从人数多于商人人数,就杀人越货. 但商人们知道了他们的约定,并且掌握着过河大权,他们该采取怎样的策略才能安全过河?
二、算法思想
这个问题实际上是一个迷宫问题,为什么这样说呢?请听我慢慢道来.
首先,我们将每次渡河前西岸的人员分布和船所在的位置统称为一个"状态",用 [ u , v , 1 / 0 ] \small [u,v,1/0] [u,v,1/0] 表示,其中 u , v u,v u,v 表示商人、随从在西岸的人数,末分量表示船的位置:1 表示船在西岸,0 表示船在东岸.将符合条件的状态挑出来组成允许状态集合.
其次,船最多可容纳两个人. 将渡河方案用 [ u , v ] \small [u,v] [u,v] 表示,其中 u , v u,v u,v 分别表示上船的商人数和随从数,并将其称为决策变量,决策变量构成的集合称为决策变量集合.
共有 20 种允许状态和 5 个决策变量.
初始状态为 [ 3 , 3 , 1 ] \small [3,3,1] [3,3,1]:3 名商人、3 名随从在西岸,船停靠在西岸;
终止状态为 [ 0 , 0 , 0 ] \small [0,0,0] [0,0,0]:0 名商人、0 名随从在西岸,船停靠在东岸,此时商人和随从全部到达东岸,这样就确定了"迷宫"的入口和出口.
从一个状态变换到另一个状态是通过决策变量实现的,这里的决策变量也就相当于"迷宫"中的一段路.
所以,我们的任务就是选择可行的路径,从"迷宫"的入口走到出口.
现在你应该觉得这好像是个迷宫问题,但心中应当还存有怀疑,因为只有入口和出口的话,并不能称得上是迷宫问题,那还有什么其他的特点呢?
想想我们是怎样解决迷宫问题的?从入口出发,沿某一方向前进,若能走通,则继续往前走;如果不能走通或是有某一分叉可以抵达出口,则沿原路退回到刚刚的分叉点,换个方向继续前进. 重复这个过程,直至探索出所有可能的通路.
这个问题也是这样的,从初始状态开始,尝试某种渡河方案,若能到达未经过的允许状态,则采取该渡河方案;如果不能到达任何一种允许状态或者是能够抵达终止状态,则原路返回至刚刚的状态,尝试其他的渡河方案. 重复这个过程,直到探索出所有的渡河方案.
读到这儿,你可能会感觉到,这真的就是一个迷宫问题. 好,既然你认同了,咱就继续往下说.
三、如何实现
怎样利用程序来解决这类问题呢?栈+回溯.
栈(什么是栈?你可以把它简单地想象成桶装薯片(只有一端开口),你只能从上面的薯片依次往下吃才能吃到最后一个,而不能直接吃到最后一个),具有"后进先出"的特性,所以我们用它来存储经过(或已标记)的状态。
初始情况:起始状态已被标记,放在栈中. 考虑某一当前状态(已标记),则回溯法的基本思想是:
- 若当前状态是终止状态,则输出路径,之后进行回溯,即返回上一层,去除当前状态标记,当前状态出栈,返回上一状态,上一状态继续尝试没有试过的决策变量;
- 若当前状态不是终止状态,则依次尝试决策变量:
(1). 若当前决策变量能使我到达某个未标记的允许状态,则将在其上面做标记,而后移动到那个状态(此时当前状态已发生改变),进行递归调用,在调用语句之后,消除那个状态的标记,将其从栈中移出;
(2). 若当前决策变量不能使我到达未标记的允许状态,则尝试下一决策变量;
当前状态下所有的决策变量(不论可不可行)都试过之后,进行回溯;
正是这种回溯机制,保证了所有可行"路径"均能被找到.
四、流程图
1. 主程序
2. 递归函数 crossRiver
五、源程序代码
1. 主程序 DFS.m
clear;clc;
% 商人过河问题% global 用于声明全局变量
global State D SS;
% State 允许状态集合
% D 决策变量集合
% SS 状态标记集合m = 3; % 商人数
n = 3; % 随从数
max = 2; % 船所能容纳的最大人数% 1.设置允许状态集合{[u,v,1/0]}
% u,v 分别表示商人、随从在西岸的人数
% 末分量:1 表示船在西岸,0 表示船在东岸State = [];
numS = 0; % 允许状态的数目
% 设定符合要求的人员状态
% 任意一岸,商人数不小于随从数
% 或者,某一岸商人数为 0
for u = 0:mfor v = 0:nif ((u >= v && (m-u) >= (n-v)) || u == 0 || u == m) numS = numS + 1;State(numS,:) = [u,v]; endend
end
% 设定船的状态
% [u,v,1] 表示船在西岸,[u,v,0] 表示船在东岸
State = [State, ones(numS,1);State, zeros(numS,1)];
numS = numS*2; % 状态数翻倍
disp('允许状态集合:');
State
fprintf('共%d种允许状态\n\n', numS);% 2.设定决策变量集合
% max: 船所能容纳的最大人数
% [u,v]: u,v 分别表示船上商人和随从人数
D = [];
global numD;
numD = 0; % 决策变量个数
for u = 0:mfor v = 0:nif ((u+v) >= 1 && (u+v) <= max)numD = numD + 1;D(numD,:) = [u,v];endend
end
disp('决策变量集合:');
D
fprintf('共%d个决策变量\n\n',numD);% 3.设置状态访问标记集合
% 对状态进行编号: 1 ~ numS
% SS(i) == 1,表示 i 号状态已访问;
% SS(i) == 0,表示 i 号状态未访问;
SS = zeros(numS,1);global pos_end;
global pos_passed k count;
% pos_end 终止状态编号
% pos_passed 留下访问标记的状态编号
% k 留下访问标记的状态数目
% count 解的个数% 初始状态(3,3,1),编号:
pos_begin = find(ismember(State, [3,3,1], 'rows') == 1);
% 终止状态(0,0,0),编号:
pos_end = find(ismember(State, [0,0,0], 'rows') == 1);% 4.对参数进行初始化
count = 0;
SS(pos_begin) = 1; % 在初始位置留下访问标记
pos_passed(1) = pos_begin; % 留下访问标记的状态编号
k = 1;% 5.调用递归函数
crossRiver(pos_begin); if count == 0fprintf('No solution.\n');
end
2. 过河函数 crossRiver.m
function crossRiver(pos_currentS)
% pos_currentS 当前状态编号% 全局变量
global State D numD SS;
% State 允许状态集合
% numD 决策变量数目
% D 决策变量集合
% SS 状态标记集合
global pos_passed pos_end;
% pos_passed 留下访问标记的状态编号
% pos_end 终止状态编号
global k;
% k 留下访问标记的状态数目,初值为1if pos_currentS == pos_end % 终止情况showSolution();
else % 非终止情况for i = 1:numDpossibleS = zeros(1,3); % 事先为可能的状态分配空间 possibleS(1,1:2) = State(pos_currentS,1:2) + ((-1)^(State(pos_currentS,3)))*D(i,:);% 船从西岸到东岸,西岸人数减少;从东岸到西岸,西岸人数增加.possibleS(1,3) = 1 - State(pos_currentS,3);% 船的状态也随之改变% 按行判断可能的状态是否属于允许状态集合sign = ismember(State, possibleS, 'rows'); % 如果可能的状态属于允许状态集合且未被标记,则进行访问% 这样做可以避免回到经过的状态,否则程序将陷入死循环if sum(sign) == 1[pos_next,~] = find(sign == 1); % pos_next:可能状态的编号if SS(pos_next) == 0 % 若未被标记 SS(pos_next) = 1; % 则进行标记 k = k + 1;pos_passed(k) = pos_next; % 将其添加到标记点组成的集合中crossRiver(pos_next); % 调用自身,进行递归 SS(pos_next) = 0; % 消除标记k = k - 1; % 将其从标记点组成的集合中移出endend end
end
3. 输出解的函数 showSolution.m
function showSolution()
% 全局变量
global State pos_passed k count;
% State 允许状态集合
% pos_passed 留下访问标记的状态编号
% k 留下访问标记的状态数目,初值为1
% count 解的个数,初值为0count = count + 1;
fprintf('Solution %d...\n', count);
fprintf('\t\t West East\n');
for i = 1:k % 输出经过的状态fprintf(' State%2d:(%d,%d)', i, State(pos_passed(i),1), State(pos_passed(i),2));if State(pos_passed(i),3) == 1 % 船在西岸fprintf('★_____'); % ★ 表示船else % State(pos_passed(i),3) == 0 % 船在东岸fprintf('_____★');endfprintf('(%d,%d)\n', 3 - State(pos_passed(i),1), 3 - State(pos_passed(i),2));% 输出渡河方案if i ~= kif State(pos_passed(i),3) == 1fprintf(' Plan\t:--(%d,%d)->\n', State(pos_passed(i),1) - State(pos_passed(i+1),1), State(pos_passed(i),2) - State(pos_passed(i+1), 2));elsefprintf(' Plan\t:<-(%d,%d)--\n', State(pos_passed(i+1),1) - State(pos_passed(i),1), State(pos_passed(i+1),2) - State(pos_passed(i),2));endend
end
fprintf('\n');
end
六、求解结果
七、小结
这是一个经典的过河问题,本质就是在各允许状态之间寻找符合要求的路径,考虑到可能有多条路径,需要使用栈保存经过的状态,以便回溯. 几点心得:
- 合适的数据结构——栈,对于此问题的处理起到了至关重要的作用;
- 对允许状态进行编号,方便编程,同时增强了程序的通用性;
- 采用标记的方式记录经过的状态,与直接删除经过的状态相比,有着很大的优越性,一是保证了回溯的进行,二是避免了删除操作;
- 类似的问题还有八皇后问题、人狗鸡米过河问题以及其他版本的过河问题;
Plus: 如有错误、可以改进的地方、或任何想说的,请在评论区留言!
商人渡河问题(MATLAB版)相关推荐
- 渡河问题matlab程序,商人渡河问题(MATLAB版)
写得比较详尽,基本上解释清楚了,慢慢看能够看懂. 目录一.题目要求二.算法思想三.如何编程四.流程图1. 主程序2. 递归函数crossRiver五.源程序代码1. 主程序 DFS.m2. 过河函数 ...
- 商人渡模型matlab及模型假设,商人和仆人渡河
2019-10-28 商人和商代有关吗|商人和两个骗子的故事 富商预备了大批货物准备到另一个城市销售.两个骗子装成生意人与他同行.走了一天以后,两个骗子当夜商量如何骗富商的财物,但是,他俩都想独吞,各 ...
- 商人渡模型matlab及模型假设,商人过河问题
商人过河问题 一.三名商人各带一名随从的情况 1. 问题(略) 2. 模型假设 ① 当一边岸满足随从数大于商人数,但商人数为0时仍为一种安全状态: ② 小船至多可容纳2人,且渡河时由随从(或者商人)来 ...
- 数学实验matlab 韩明,(面向21世纪)数学实验(MATLAB版)
[亲,本库图书均为正版新书,因部分图书库存量极少,属于绝版书,有可能因未及时更新库存造成缺货的现象,若遇到缺货情况我们深表歉意并会及时给您退款,不用担心哦!谢谢理解与支持,祝您购物愉快!] 基本信息 ...
- 156 13.67用matlab答案,数学实验(MATLAB版韩明版)5.1,5.3,5.5,5.6部分答案
<数学实验(MATLAB版韩明版)5.1,5.3,5.5,5.6部分答案>由会员分享,可在线阅读,更多相关<数学实验(MATLAB版韩明版)5.1,5.3,5.5,5.6部分答案(1 ...
- 【资源分享】数字图像处理MATLAB版冈萨雷斯+中文高清版+随书源码链接
写在这里的初衷,一是备忘,二是希望得到高人指点,三是希望能遇到志同道合的朋友. 目录 1.数字图像处理MATLAB版冈萨雷斯+中文高清版 2.数字图像处理MATLAB版冈萨雷斯随书源码 1.数字图像处 ...
- 龙格库塔法matlab求解微分方程组,微分方程组的龙格库塔公式求解matlab版.pdf
微分方程组的龙格库塔公式求解matlab版 微分方程组的龙格-库塔公式求解matlab版 南京大学 王寻 1. 一阶常微分方程组 考虑方程组 y'f x,y,z , y x y ...
- hog svm 视频 matlab,matlab版hog+svm图像二分类
开始接触svm分类器是opencv中的使用起来很方便,后来根据实际需要需要使用matlab版的,以前没怎么接触过,开始有点头大,不知从何下手,查阅相关例子后,就开始 开始接触svm分类器是opencv ...
- 线性代数 matlab,实用大众线性代数(MATLAB版)
实用大众线性代数(MATLAB版) 作者:陈怀琛 著 出版时间:2014年版 内容简介 传统的线性代数源于数学家,教理论不教应用.工科需要的是应用,却学不到,2009年由本书作者牵头在19个大学实施了 ...
最新文章
- centos 7 安装 mantisbt-2.12.0 —— 安装LAMP环境、安装mantisbt-2.12.0
- Linux疑难杂症解决方案100篇(十)-uptime命令查看linux系统负载
- BZOJ 4802: 欧拉函数(大数因数分解算法 Pollard_rho 和素数测试算法 Miller_Rabin)
- Java在生活中的应用盘点!
- Django annotate: 外键字段解析问题
- Java Ajax jsonp 跨域请求
- ListView中加入Button后,Button的点击事件和ListView的点击事件冲突
- UITableViewCell自适应高度
- chrome 设置user agent
- jmeter脚本写个小demo(html论坛自动发帖、json龙果学院-前后端分离)
- 官宣:Linux 内核主要贡献者 Linaro「喜提」新任 CEO!
- vue中ast生成render
- 降低人工智能成本50% 阿里云推出新一代异构实例GN5i
- 【经验之谈】掌握这13个PyTorch Trick,让你做实验行云流水~
- 《你不可不知的50个建筑学知识》之哥特式建筑 1
- java ftp 卡死_ftpclient卡死问题
- nginx ajax 错误500,聊聊nginx报错499问题
- 一夜之间就能肝完的学生信息管理系统
- Unity音频可视化
- 中国高端制造进展神速,又一项新科技产品即将取得全球第一!
热门文章
- IT从业人员面试经典70问答
- eagleeye_EagleEye简介:户外视频监控分析和面部识别软件
- GBase 8c产品架构
- ITパスポート5天学习笔记④_Rx
- .net mvc ef 视图未定义主键问题
- linux环境下gitea使用,linux一键安装gitea
- Can I debug relocated code at source-level with DS-5 Debugger?
- 王者荣耀服务器维护到什么时候7月9号,王者荣耀7月9号更新产生的新变化
- 2018网易互娱笔试1——花砖拼接C++
- 阿里云RDS数据库外网连接和内网连接有什么不同?