商人过河问题
1.问题重述
三名商人各带一个随从乘船渡河,一只小船只能容纳二人,由他们自己划行。随从们密约,在河的任一岸,一旦随从的人数比商人多,就杀人越货。但是如何乘船渡河的大权掌握在商人们手中,商人们怎样才能安全渡河呢?
2.问题分析
该问题是一个多步决策的问题,商人的下一步决定方案依赖于此时的状态,同时商人可以选择的方案有多种可能,因此,要想顺利将商人送达到对岸,需要对每一步进行决策和假设,通过一步步推理,分析出最后的结果
3.模型假设
- 将商人和随从的数目抽象成具体数值
- 将河的此岸和彼岸抽象成两种不同的状态,比如说是0和1
4.符号说明
基于递归的方法
假设商人的人数是m
,随从的人数是n
,船的容量是k
。
5.模型的建立和求解
基于递归的方法
记第k次渡河前彼岸(和数学模型(第四版)姜启源 谢金星 叶俊的不一样)的商人数为x
,随从数为y
,
k
=
1
,
2
,
…
k=1,2,\dots
k=1,2,…,
x
=
0
,
1
,
2
,
…
,
m
x=0,1,2,\dots,m
x=0,1,2,…,m,
y
=
0
,
1
,
2
,
…
,
n
y=0,1,2,\dots,n
y=0,1,2,…,n,将二维向量
s
=
(
x
,
y
)
s=(x,y)
s=(x,y)定义为人员状态,安全渡河条件下的状态集合称为允许人员状态集合,记作
S
S
S
S
=
{
(
x
,
y
)
∣
x
>
y
∧
m
−
x
>
n
−
y
}
S=\{(x,y)|x>y \land m-x>n-y\}
S={(x,y)∣x>y∧m−x>n−y}
首先需要注意到,此岸的决策恰好与彼岸决策相反,因此,针对此岸和彼岸的状态,需要不同的决策策略。不妨令船在此岸的状态抽象成0
,船在彼岸的状态抽象成1
。记三元组x, y, d
表示彼岸的商人人数、随从人数以及此事船所在的河岸。于是,该事件的起始状态是(0, 0, 0)
,即彼岸没有商人和随从,所有人都在此岸,且船在此岸;事件的结束状态是(m, n, 1)
,即次岸没有商人和随从,所有人都在彼岸,且船在彼岸。
假设第k
次的状态是(x, y, d)
,那么他的下一次的状态只能是在
(
S
,
d
′
)
(S, d')
(S,d′)中,所以需要遍历所有可以到达的状态点即可。
6.附录
基于递归的方法的C++代码
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <stack>
#include <cstring>
#include <memory>
using namespace std;
typedef struct node
{
int x, y, d;
shared_ptr<struct node> prev;// 记录父节点的信息
node(shared_ptr<node> n = nullptr):prev(n) {}
}node;
const int N = 100, M = N * N, mask = 0x1;
int feasible_state[N][N];// 所有可行的位置点
int state[2][N][N];// 所有可行的状态点 判定状态是否遍历过 (0, 0, 0) 和 (0, 0, 1)是不同的状态
pair<int, int> action[2][N * N];// 所有可行的决策策略
int m, n, k;// 商人、随从的数量和船的容量
int total_action = 0, all_cnt = 0, ans_cnt = 0, min_lens = 0x3f3f3f3f;// 所有的决策的数目,所有可行方案的数量,最少决策方案的数量,最少方案的决策步数
vector<stack<node>> answer_tmp;
vector<vector<node>> answer;// 所有解的存放
node head_state;// 起点
void init();
void run(shared_ptr<node> now);
bool same(shared_ptr<node> now);
void show_ans();
int main()
{
printf("请输入商人、随从、船容量的信息:<m n k>\n");
scanf("%d%d%d", &m, &n, &k);
init();
run(make_shared<node>(head_state));
show_ans();
system("pause");
return 0;
}
/**
* 初始化所有合法的行动
* 初始化所有合法的状态
* */
void init()
{
// action[0][]是去向的行动
// action[1][]是回向的行动
// 使用pair存二维的方向
int method_cnt = 0;
for(int r = 1; r <= k; r++)
for(int j = 0; j <= r; j++)
action[0][method_cnt] = {j, r - j}, action[1][method_cnt++] = {-j, j - r};
total_action = max(total_action, method_cnt);
// 所有可达点都是1
for(int j = 0; j <= n; j++)
{
feasible_state[0][j] = feasible_state[m][j] = feasible_state[0][j] = feasible_state[m][j] = 1;
for(int i = 0; i <= m - n; i++)
feasible_state[i + j][j] = feasible_state[i + j][j] = 1;
}
// 初试状态是 (0, 0, 0)
// 终点状态是 (m, n, 1)
head_state.prev = nullptr, head_state.x = head_state.y = head_state.d = 0;
}
/**
* 核心代码
* 根据所有可行的行动中找到所有到达的合法点进行递归
* */
void run(shared_ptr<node> now)
{
shared_ptr<node> last = make_shared<node>(now->prev), to(new node);
for(int i = 0; i < total_action; i++)
{
to->x = now->x + action[now->d][i].first;
to->y = now->y + action[now->d][i].second;
to->d = ~now->d & mask;
to->prev = now;
if(to->x < 0 || to->x > m || to->y < 0 || to->y > n) continue;// 出界
if(to->x == m && to->y == n)// 到达终点需要保存这个方案
{
shared_ptr<node> tmp(to);
stack<node> s;
while(tmp != nullptr)
{
s.push(*tmp);
tmp = tmp->prev;
}
answer_tmp.push_back(s);
all_cnt++;
continue;
}
if(feasible_state[to->x][to->y] == 1 && (last == nullptr || !same(to)))// 到达可行点且没有绕回去
{
run(to);
}
}
}
/**
* 判定是否出现重路
* */
bool same(shared_ptr<node> now)
{
memset(state, 0, sizeof(state));
for(; now != nullptr; now = now->prev)
if(state[now->d][now->x][now->y] == false)
state[now->d][now->x][now->y] = true;
else
return true;
return false;
}
/**
* 展示所有点
* */
void show_ans()
{
ofstream answer_file;
answer_file.open("./answer_file.txt", ios::out | ios::trunc);
for(auto stk : answer_tmp)
{
vector<node> vec;
while(!stk.empty())
{
vec.push_back(stk.top());
stk.pop();
}
answer.push_back(vec);
}
for(auto vec : answer)
min_lens = min(min_lens, (int)vec.size());
for(auto vec : answer)
if(vec.size() == min_lens)
ans_cnt++;
if(answer_file.is_open())
{
for(auto vec : answer)
{
if(vec.size() == min_lens)
{
for(node n : vec)
answer_file << "(" << n.x << "," << n.y << "," << n.d << ")->";
answer_file << "\n";
}
}
answer_file.close();
}
else
{
printf("一共有 %d 中最短解法\n", ans_cnt);
for(auto vec : answer)
{
if(vec.size() == min_lens)
{
for(node n : vec)
printf("(%d,%d,%d)->", n.x, n.y, n.d);
printf("\n");
}
}
}
}
参考 商人过河问题