2022-09-09 整点简单的问题看看。
问题描述
河的左岸有 个传教士和 个野人,有一条能载 人的船。要求在过河的过程中任意一个时刻,要求两岸都必须满足以下两个条件中至少一个:
- 没有传教士;
- 传教士的数量大于等于野人的数量。
在此约束条件下,试设计一个渡河方案,使得 个人最终都渡到河的右岸。
形式化表述
状态 是一个五元组,满足 ,其中 表示左岸的传教士个数, 表示左岸的野人个数, 表示右岸的传教士个数, 表示右岸的野人个数, 表示现在船在左岸还是在右岸。
那么,初始状态为 ,即 个人都在左岸且船也在左岸;我们想要达到的目标状态为 ,即 个人都在右岸且船也在右岸。
我们对安全状态(即合法状态)的定义为:
我们不妨用一个二元组 表示可以采取的行动,其中 表示过河的传教士的数量, 表示过河的野人的数量。由于至少要有一个人划船,因此总共可以采取的行动有五种:
- 两个野人过河,即 ;
- 两个传教士过河,即 ;
- 一个野人和一个传教士过河 ;
- 一个野人过河 ;
- 一个传教士过河 。
在一个状态 下能够采用某个行动 的条件是人数足够调遣且目标状态不会产生危险,即:
其中目标状态 的定义为:
这样我们就建立了一个形式化的搜索树模型,考虑到一共只有 种可能的状态,我们可以将这些状态编码为一个整数 以便状态判重。
广度有限搜索 (BFS) 实现
/* 传教士野人过河问题 */
/* 针对 N = 3, K = 2 的解法 */
#include <algorithm>
#include <cassert>
#include <iostream>
#include <queue>
#include <stack>
const int L = 0; // 船在河的左岸 B = 0
const int R = 1; // 船在河的右岸 B = 1
struct Action { // 定义一个动作
int M, S;
Action(int _M, int _S) { // 初始化工作
M = _M; S = _S;
}
};
struct Status { // 定义一个状态
int M[2]; // M[L] 表示左岸的传教士个数, M[R] 表示右岸的传教士个数
int S[2]; // S[L] 表示左岸的野人 个数, S[R] 表示右岸的野人 个数
int B ; // B = L 表示船在左岸,B = R 表示船在右岸
Status(int M_L, int S_L, int M_R, int S_R, int B_N) { // 初始化工作
M[L] = M_L; M[R] = M_R;
S[L] = S_L; S[R] = S_R; B = B_N;
}
int HASH() const { // 获取状态的哈希值
return 2*(M[L] * 4 + S[L]) + B;
}
int SAFE() const { // 检查一个状态是否安全
for(int i = std::min(L, R); i <= std::max(L, R); i ++) { // 检查两岸安全性
if(M[i] < S[i] && M[i] != 0)
return false;
}
return true;
}
Status AIM(const Action& A) const { // 根据当前状态和移动方式,生成下一步状态
Status nxt = *this;
nxt.M[ B] -= A.M;
nxt.S[ B] -= A.S;
nxt.M[!B] += A.M;
nxt.S[!B] += A.S; // 将对应数量的野人和传教士渡到对岸
nxt.B = !B; // 将船渡到对岸
return nxt;
}
bool AVAI(const Action& A) const { // 判断当前状态能够施加行动 A
return M[B] >= A.M && S[B] >= A.S && AIM(A).SAFE();
}
Status(int hash) { // 从哈希值加载一个状态
B = hash & 1; hash >>= 1;
S[L] = hash & 3; hash >>= 2; S[R] = 3 - S[L];
M[L] = hash & 3; hash >>= 2; M[R] = 3 - M[L];
/* 检查哈希值是否合法 */
assert(hash == 0);
}
void SHOW() const { // 输出信息到屏幕
std::cout << "(" << M[L] << "," << S[L] << ","
<< M[R] << "," << S[R] << "," << B << ")" << std::endl;
}
};
bool operator==(const Status& S1, const Status& S2) { // 判断状态相同
for(int i = std::min(L, R); i <= std::max(L, R); i ++) {
if(S1.M[i] != S2.M[i] || S1.S[i] != S2.S[i])
return false;
}
return S1.B == S2.B;
}
const Action actions[] = {
Action(2, 0),
Action(0, 2),
Action(1, 1),
Action(1, 0),
Action(0, 1)
};
const Status S_BEGIN(3, 3, 0, 0, L);
const Status S_END (0, 0, 3, 3, R);
const int StatusCnt = 32;
int vis[StatusCnt] = {}; // 记录每个状态是否被访问过
int pre[StatusCnt] = {}; // 记录每个状态的前一状态
int main() {
/* BFS */
std::queue<Status> Q;
Q.push(S_BEGIN); vis[S_BEGIN.HASH()] = true;
while(!Q.empty()) {
Status Stmp = Q.front(); Q.pop(); // 弹出一个待拓展状态
for(const Action& Atmp: actions) {// 枚举一个
if(Stmp.AVAI(Atmp)) { // 可以实施当前行为
Status nxt = Stmp.AIM(Atmp);
int hash = nxt.HASH();
if(!vis[hash]) { // 更新最短路径
vis[hash] = true;
pre[hash] = Stmp.HASH();
Q.push(nxt);
if(nxt == S_END) { // 找到了答案
break;
}
}
}
}
}
/* 输出答案 */
int hashNow = S_END.HASH();
if(!vis[hashNow]) {
std::cout << "SOLUTION NOT FOUND!" << std::endl;
}else {
std::stack<Status> STK;
while(hashNow != S_BEGIN.HASH()) {
Status SNow(hashNow);
STK.push(SNow);
hashNow = pre[hashNow];
}
STK.push(S_BEGIN);
int cnt = 0;
while(!STK.empty()) { // 正向输出路径
std::cout << cnt << "\t";
STK.top().SHOW(); STK.pop();
cnt += 1;
}
}
return 0;
}
1024code 链接:1024code.com/codecubes/J…