农夫过河 递归 回溯 输出字符串

        在这学期一个课设的题目列表里看到了这道题,写完之后感觉好像可以发一下74cb7353a91e455d80dbd500c03357c8.png

        这道题写起来没什么工作量,但写完它就几乎相当于完成了这门课的全部工作量,实在是件让人喜闻乐见的事。

        题目其实下面就有学校给的详细解析,不过解析里基本上是大量的文字,因此我试图在此把关键信息提取出来,再放上代码,最后尝试分析一下算法。

核心思路:

  1. 使用一个整数表示河岸的状态,例如,其二进制表示为1101,则农夫,羊,白菜都在此岸
  2. 使用一个接受5个参数的递归函数,前两个参数分别为存放最终答案的集合和存放当前方案的字符串集合,第三,四个参数表示河两岸的状态,最后一个参数为存放出现过的状态的集合

 函数返回条件:

  1. 河对岸状态为二进制的1111,将当前方案加入最终答案,返回
  2. 出现不安全的状态,如,农夫不在场时,狼和羊在场,返回
  3. 出现重复的状态,返回

 额外条件:与农夫处于同一边的对象才能移动

递归函数如下

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;
}

输出结果f1a54f537f404abdbde29b7172ea1fb8.png

 (虽然不太熟练但尝试进行一下)算法浅析:

    假设食物链的长度为 n 

        递归调用大概能形象化为以下二叉树d90e359c779d428d91a929fea2f54d25.png

        其深度等于河岸可能处于的状态的总数,即 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)

(我对算法分析这块的内容实在是很不熟练,如果有大佬发现我什么地方写错了,还请指正,感谢)

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值