在这学期一个课设的题目列表里看到了这道题,写完之后感觉好像可以发一下
这道题写起来没什么工作量,但写完它就几乎相当于完成了这门课的全部工作量,实在是件让人喜闻乐见的事。
题目其实下面就有学校给的详细解析,不过解析里基本上是大量的文字,因此我试图在此把关键信息提取出来,再放上代码,最后尝试分析一下算法。
核心思路:
- 使用一个整数表示河岸的状态,例如,其二进制表示为1101,则农夫,羊,白菜都在此岸
- 使用一个接受5个参数的递归函数,前两个参数分别为存放最终答案的集合和存放当前方案的字符串集合,第三,四个参数表示河两岸的状态,最后一个参数为存放出现过的状态的集合
函数返回条件:
- 河对岸状态为二进制的1111,将当前方案加入最终答案,返回
- 出现不安全的状态,如,农夫不在场时,狼和羊在场,返回
- 出现重复的状态,返回
额外条件:与农夫处于同一边的对象才能移动
递归函数如下
void def(vector<vector<string>> &ans, vector<string> &way, int side, int otherSide, vector<int> &masks) {
if (otherSide == 0xF) { //0xF = (二进制)1111
ans.push_back(way); //满足otherSide =(二进制)1111,将当前方案加进ans
return;
}
for (int i = 0; i < masks.size(); ++i) {
if (side == masks[i]) { //出现重复的状态,直接返回
return;
}
}
masks.push_back(side);
if (!(side & 1)) { //判断农夫是否在side
if (((side & 6) == 6) || (side & 12) == 12){ //判断side是否同时有狼和羊,或羊和白菜,6 = (二进制)110, 12 = 1100
return;
}
}
if (!(otherSide & 1)) {
if (((otherSide & 6) == 6) || (otherSide & 12) == 12) {
return;
}
}
way.push_back("-> famer ");
def(ans, way, side ^ 1, otherSide ^ 1, masks);
way.pop_back();
if ((side & 3) == 3 || (otherSide & 3) == 3) { //狼与农夫在同一边,即 (二进制) side & 0011 = 3 时,农夫才能把狼带到另一边
way.push_back("-> famer,worf "); //农夫带狼到河对岸,将这一步加入way
def(ans, way, side ^ 3, otherSide ^ 3, masks);
way.pop_back(); //回溯
}
if ((side & 5) == 5 || (otherSide & 5) == 5) {
way.push_back("-> famer,sheep ");
def(ans, way, side ^ 5, otherSide ^ 5, masks);
way.pop_back();
}
if ((side & 9) == 9 || (otherSide & 9) == 9) {
way.push_back("-> famer,grass ");
def(ans, way, side ^ 9, otherSide ^ 9, masks);
way.pop_back();
}
masks.pop_back(); //回溯,从masks中移除当前状态
return;
}
主函数
int main() {
vector<vector<string>> ans; //保存所有可行的方案
vector<string> way; //保存当前方案
vector<int> masks; //保存当前出现过的状态,本题中所有可能的状态已知,共2^4种,故可以用含16个bool的数组替代
int side = 0xF, otherSide = 0; //用两个二进制数分别表示河两岸的状态,初始值为二进制1111和0000
def(ans, way, side, otherSide, masks);
cout << "There are " << ans.size() << " ways to do that" << endl; //输出答案
for (int i = 0; i < ans.size(); ++i) {
for (int j = 0; j < ans[i].size(); ++j) {
cout << ans[i][j];
}
cout << endl;
}
return 0;
}
输出结果
(虽然不太熟练但尝试进行一下)算法浅析:
假设食物链的长度为 n
递归调用大概能形象化为以下二叉树
其深度等于河岸可能处于的状态的总数,即 2 ^ n, 由于每次调用函数都会创建变量side和otherSide的副本,占用的额外空间应为 O(2 ^ n), masks最多同时保存的整数数量也为 2 ^ n,所以总的空间复杂度仍为 O(2 ^ n)
用迭代法对上述 T(n) 进行求解,应有 T (n) < 4 ^ (2 ^ n) = 8 ^ n,因此时间复杂度为 O(8 ^ n)
(我对算法分析这块的内容实在是很不熟练,如果有大佬发现我什么地方写错了,还请指正,感谢)