狼羊菜问题的算法思想和C++实现
狼羊菜问题
把【狼、羊和蔬菜】这三样东西安全地送至河的对面,你能用的工具只有身边的一艘小船。已知,这艘小船很小,当你坐在里面时,其只能搭载【狼、羊和蔬菜】这三样东西中的一样,而如果没有你的看管,狼会吃掉羊,羊也会吃掉蔬菜,但他们都不会乱跑。那么,你需要怎样做才能成功完成任务呢?
设计思路
-
用二进制表示狼羊菜在两岸的状态。例如:0101011,前三位表示对岸有羊,中间三位表示岸这边有狼和菜。最后一位代表此时人在对岸。
-
使用状态数组标记已访问过的状态
-
定义冲突:为111, 110, 011。
-
函数toB, toA相互调用,枚举每种状态之间的转移。
代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
// 状态数组,标记已访问的状态
bool bk[1 << 7];
// 用于存储合法过程
vector<pair<int, char> >res;
// 用于标记解的个数
int lun = 0;
// 定义冲突
bool haveConflict(int status) {
if (status == 0x06) // 狼和羊在一起
return true;
if (status == 0x03) // 羊和菜在一起
return true;
if (status == 0x07) // 全都在一起
return true;
return false;
}
// 定义结束条件,即这边岸上没有物体
bool haveNoOne(int status) {
return !status;
}
// 用于输出合法答案
void outputAns() {
printf("Solution %d: \n", ++lun);
const char *board[3] = {"Vegetable", "Sheep", "Wolf"};
for (vector<pair<int, char> >::iterator it = res.begin(); it != res.end(); it++) {
if (it->first == -1) {
printf("People\t%c\n", it->second);
} else {
printf("People & %s\t%c\n", board[it->first], it->second);
}
}
printf("\n\n");
}
// 声明转移函数
void toA(int);
void toB(int);
// 去到对岸
void toB(int status) {
// 如果曾有过这种状态,就跳过返回
if (bk[status << 1]) return;
// 标记这种状态已经走过
bk[status << 1] = true;
// 人直接过到对岸
if (!haveConflict(status & 0x07)) {
// 将状态压栈
res.push_back(make_pair(-1, '<'));
// 递归考虑回程
toA(status);
// 状态还原(出栈)
res.pop_back();
}
// 考虑带谁
for (int i = 0; i < 3; i++) {
// 如果这个东西还在,且拿走它后这边没有冲突,就递归考虑回程
if ((status & (1 << i)) && !haveConflict(status & ~(1 << i))) {
// 将状态压栈
res.push_back(make_pair(i, '<'));
// 递归考虑回程
toA((status | (1 << (i + 3))) & ~(1 << i));
// 状态还原(出栈)
res.pop_back();
}
}
}
// 返程状态转移(类似去对岸)
void toA(int status) {
if (bk[status << 1 | 1]) return;
bk[status << 1 | 1] = true;
// 如果对岸已经没人了(这时候人在对岸),即代表找到了一种解,输出
if (haveNoOne(status & 0x07)) {
outputAns();
return;
}
// 人直接过到对岸
if (!haveConflict(status >> 3)) {
// 将状态压栈
res.push_back(make_pair(-1, '>'));
// 递归
toB(status);
// 状态还原(出栈)
res.pop_back();
}
// 考虑带谁
for (int i = 0; i < 3; i++) {
// 如果这个东西还在,且拿走它后这边没有冲突,就递归
if ((status & (1 << (i + 3))) && !haveConflict((status >> 3) & ~(1 << i))) {
// 将状态压栈
res.push_back(make_pair(i, '>'));
// 递归
toB((status | (1 << i)) & ~(1 << (i + 3)));
// 状态还原(出栈)
res.pop_back();
}
}
}
int main(int argc, char const *argv[]) {
// 初始化状态标记数组
memset(bk, false, sizeof(bk));
// 初始状态是狼羊菜都在一侧,即000111,即0x07
toB(0x07);
return 0;
}