花了半个晚上写了一个深度优先求解人狼过河的程序

狼人过河问题:有三个人和三只狼要过河,河里有一只船同时能容纳最多两个生物
假设狼和人都会划船,但如果岸边(不包括船上)狼的数量比人多 ,狼就要吃人
问:怎么把三只狼和三个人安全运输到对岸

  1 // mytest.cpp : 定义控制台应用程序的入口点。
  2 //
  3
  4 #include "stdafx.h"
  5 #include <windows.h>
  6 #include <iostream>
  7 #include <string>
  8 #include <set>
  9 #include <map>
 10 #include <vector>
 11 using namespace std;
 12
 13 //打印结果相关函数
 14 void printState(char state);
 15 void printStates(const vector<char>& vecStates);
 16 //根据当前状态,获取下一步可能的所有状态函数
 17 void getNextStates(char currState, vector<char>& vecNextStates);
 18
 19 //检查状态是否符合要求的函数
 20 #define RET_IF_NOT_OK(cnt) do { if(cnt < 0 || cnt > 3) return false; } while(0)
 21 bool isStateOk(int leftPeopleCnt, int leftWolfCnt, int rightPeopleCnt, int rightWolfCnt)
 22 {
 23     RET_IF_NOT_OK(leftPeopleCnt);
 24     RET_IF_NOT_OK(leftWolfCnt);
 25     RET_IF_NOT_OK(rightPeopleCnt);
 26     RET_IF_NOT_OK(rightWolfCnt);
 27
 28     //岸边有人,且人的数量少于狼的数量
 29     if (leftPeopleCnt > 0 && leftPeopleCnt < leftWolfCnt) return false;
 30     if (rightPeopleCnt > 0 && rightPeopleCnt < rightWolfCnt) return false;
 31
 32     return true;
 33 }
 34
 35 //根据下一步船所在岸边和岸边狼/人数量生成下一步状态字节
 36 //这个函数与getNextStates紧密结合 详细用法请参考该函数调用逻辑
 37 char makeState(int leftPeopleCnt, int leftWolfCnt, bool bLeft)
 38 {
 39     //用一个字节第一位(左数,下同 )表示船在左边(0)还是在右边(1)
 40     //用第3、4代表人的数量,第7、8位代表狼的数量(00 ~ 11)
 41     //举例:0b00110011 表示左岸有三个人三只狼 船在左边
 42     //      0b10100001 表示左岸有三个人一只狼 船在右边
 43     char state = 0;
 44
 45
 46     //下一步船在左边
 47     if (bLeft)
 48     {
 49         //传进来的其实是下一步右边岸上人和狼的数量
 50         //需要推导出下一步左边岸上的人和儿狼的数量
 51         state = state | ((3 - leftPeopleCnt) << 4) | (3 - leftWolfCnt);
 52     }
 53     else
 54     {
 55         //下一步船要在右边了
 56         state = state | 0x80;
 57         state = state | (leftPeopleCnt << 4) | leftWolfCnt;
 58     }
 59
 60
 61     return state;
 62 }
 63
 64 //根据当前状态,获取下一步可能的所有状态函数
 65 void getNextStates(char state, vector<char>& vecNextStates)
 66 {
 67     bool bLeft = (state & 0x80) == 0;
 68     int leftPepleCnt = (state & 0x30) >> 4, rihgtPeopleCnt = 3 - leftPepleCnt;
 69     int leftWolfCnt = (state & 0x03), rightWolfCnt = 3 - leftWolfCnt;
 70     if (!isStateOk(leftPepleCnt, leftWolfCnt, rihgtPeopleCnt, rightWolfCnt)) return;
 71
 72     //定义指针指向当前船所在的岸边和对岸的人和狼数量
 73     //这样做的原因是左岸和右岸状态变化逻辑是一样的 可以减少逻辑重复的代码
 74     int *pShipPeopleCnt, *pNotShipPeopleCnt, *pShipWolfCnt, *pNotShipWolfCnt;
 75
 76     if (bLeft)
 77     {
 78         pShipPeopleCnt = &leftPepleCnt;
 79         pShipWolfCnt = &leftWolfCnt;
 80         pNotShipPeopleCnt = &rihgtPeopleCnt;
 81         pNotShipWolfCnt = &rightWolfCnt;
 82     }
 83     else
 84     {
 85         pShipPeopleCnt = &rihgtPeopleCnt;
 86         pShipWolfCnt = &rightWolfCnt;
 87         pNotShipPeopleCnt = &leftPepleCnt;
 88         pNotShipWolfCnt = &leftWolfCnt;
 89     }
 90
 91     //可能变化有五种,一个人/狼单独过河 两个人/狼过河 一只狼和一个人一起过河
 92     //根据人的思维,尽量先处理两个生物过河的状态,不行再处理单个生物过河的
 93     //两个人/狼过河
 94     if ( isStateOk(*pShipPeopleCnt - 2, *pShipWolfCnt, *pNotShipPeopleCnt + 2, *pNotShipWolfCnt) )
 95     {
 96         vecNextStates.push_back( makeState(*pShipPeopleCnt - 2, *pShipWolfCnt, !bLeft) );
 97     }
 98
 99     if ( isStateOk(*pShipPeopleCnt, *pShipWolfCnt - 2, *pNotShipPeopleCnt, *pNotShipWolfCnt + 2) )
100     {
101         vecNextStates.push_back( makeState(*pShipPeopleCnt, *pShipWolfCnt - 2, !bLeft) );
102     }
103
104     //一只狼和一个人一起过河
105     if ( isStateOk(*pShipPeopleCnt - 1, *pShipWolfCnt - 1, *pNotShipPeopleCnt + 1, *pNotShipWolfCnt + 1) )
106     {
107         vecNextStates.push_back( makeState(*pShipPeopleCnt - 1, *pShipWolfCnt - 1, !bLeft) );
108     }
109
110     //一个人/狼单独过河
111     if ( isStateOk(*pShipPeopleCnt - 1, *pShipWolfCnt, *pNotShipPeopleCnt + 1, *pNotShipWolfCnt) )
112     {
113         vecNextStates.push_back( makeState(*pShipPeopleCnt - 1, *pShipWolfCnt, !bLeft) );
114     }
115
116     if ( isStateOk(*pShipPeopleCnt, *pShipWolfCnt - 1, *pNotShipPeopleCnt, *pNotShipWolfCnt + 1) )
117     {
118         vecNextStates.push_back( makeState(*pShipPeopleCnt, *pShipWolfCnt - 1, !bLeft) );
119     }
120
121     //如果要避免一些看起来比较愚蠢的走法,可以在这里加上对状态优先级排序的逻辑
122 }
123
124 //狼人过河问题:有三个人和三只狼要过河,河里有一只船同时能容纳最多两个生物
125 //假设狼和人都会划船,但如果岸边(不包括船上)狼的数量比人多 ,狼就要吃人
126 //问:怎么把三只狼和三个人安全运输到对岸
127 int dualLangRenOnce(char currState, vector<char>& vecStates, set<char> closeTab)
128 {
129     //如果当前状态是目标状态,则退出递归
130     if ((currState & 0x33) == 0) return 0;
131
132     vector<char> vecNextState;
133     //获取下个状态,即考虑怎么安排狼和人过河
134     getNextStates(currState, vecNextState);
135     for (size_t i = 0; i < vecNextState.size(); i++)
136     {
137         if ( closeTab.find(vecNextState[i]) == closeTab.end() )
138         {
139             closeTab.insert(vecNextState[i]);
140             vecStates.push_back(vecNextState[i]);
141             //如果打算找全部可能解 这里就不需要return了
142             if (dualLangRenOnce(vecNextState[i], vecStates, closeTab) == 0) return 0;
143             vecStates.pop_back();
144         }
145     }
146
147     return 1;
148 }
149
150 void dualLangRen()
151 {
152     //用一个字节第一位(左数,下同 )表示船在左边还是在右边
153     //用第3、4代表人的数量,第7、8位代表狼的数量(00 ~ 11)
154     //举例:0b00110011 表示左岸有三个人三只狼 船在左边
155     //      0b10100001 表示左岸有三个人一只狼 船在右边
156     char state = 0x33;
157     //vector记录状态变化过程
158     vector<char> vecStates;
159     //set记录已经历过的状态,以免重复尝试造成无尽递归
160     set<char> closeTab;
161
162     vecStates.push_back(state);
163     closeTab.insert(state);
164     if (dualLangRenOnce(state, vecStates, closeTab) == 0)
165     {
166         printStates(vecStates);
167     }
168     else
169     {
170         cout << "问题无解!" << endl;
171     }
172
173     return;
174 }
175
176 void printState(char state)
177 {
178     bool bLeft = (state & 0x80) == 0;
179     int leftPepleCnt = (state & 0x30) >> 4;
180     int leftWolfCnt = (state & 0x03);
181
182     printf("人%d 狼%d %s 人%d 狼%d", leftPepleCnt, leftWolfCnt, (bLeft ? "左": "右"), 3 -leftPepleCnt, 3 - leftWolfCnt);
183 }
184
185 void printStates(const vector<char>& vecStates)
186 {
187     for(vector<char>::const_iterator it = vecStates.begin(); it != vecStates.end(); it++)
188     {
189         printState(*it);
190         printf("\n");
191     }
192 }
193
194 int _tmain(int argc, _TCHAR* argv[])
195 {
196     double start = GetTickCount();
197     dualLangRen();
198
199     //我自己机在win7 vs2008环境 debug模式是16毫秒 release不到1毫秒
200     printf("总共耗时 %f 毫秒\n", GetTickCount() - start);
201
202     //挂屏
203     system("pause");
204     return 0;
205 }

运行结果:

这是最近第二次写了,复习一遍数据结构之后的作品

之前没有复习前写过另一版,逻辑比较混乱,有空再贴上来对比

时间: 2024-10-13 12:23:30

花了半个晚上写了一个深度优先求解人狼过河的程序的相关文章

为数据挖掘小组写的一个用于造数据的小程序

最近有个数据挖掘的项目,要求在文本里面写入随机字母并且要1000W个 于是就写了个程序用来造数据并记录一下 程序写的时候遇到的问题 1 未考虑内存溢出的情况,大批量的把数据写入导致内存溢出 以后需要谨慎对待 目前完整版 package test; import java.io.File; import java.io.FileWriter; import java.io.IOException; import org.apache.commons.io.FileUtils; import org

写了一个百度网盘资源搜索程序

一.引言 古语有云:“活到老学到老”.表达出一种“生命不止,学习不止”的学习进取精神,是一种乐观的激励!作为程序员更应该如此,否则就会被淘汰.今天我所要讲的不是如何去学习,而是怎么在网上找到我们所需求的资源. 二.百度网盘搜索方法 找资源的方法有很多,如:百度,谷歌搜索.而我讲的是如何搜索“百度网盘”里面的资源. 具体方法:在搜索引擎中搜索:site:pan.baidu.com 关键词,如下图.亲测必应,谷歌可以. 三.写了一个程序 为了自己搜索方便,索性写了一个程序去爬取必应的搜索结果,如下图

用TX-1C的开发板写了一个简易的计算器

前不久看完了矩阵键盘和数码管的应用,自己动手花了一天时间写了一个简单的计算器 . 下面贴上源代码,大家可以测试一下. 上面4*4的矩阵键盘,作用分别是: 1 2 3 + 4 5 6 — 7 8 9 * on/c 0 = / 其中矩阵键盘的部分基本是照着书本的代码改的,然后自己做了部分修改. 由于是直接用 int 变量做的计算,而如果用 long 变量的话又要用到大数的运算,所以只能计算小于65536的数. 计算的步骤是这样的: (主操作数)(操作符)(被操作数)(=) //main.c #inc

聊聊程序员如何学习英语单词:写了一个记单词的小程序

背景: 关于英文对程序员的重要性,就不多说了! 英语的学习,有很多,今天也不聊多,只聊英语单词! 关于单词的记忆,找过很多方法,下载过很多软件. 如图(其它不好用的都卸载了): 上图算是我以前用过软件,注意,是以前哦~~~ 意思就是没有坚持下来~~~~ 随时间的推移,最后它们还是被我遗忘了~~~ 为什么???不能:坚持!坚持!坚持! 学习思考: 一直在找方法: 1:下载过联想记忆法.背文章记单词,词根,各种视频~~~ 2:连单词的数据库都网上下载了一份了,期望从数据库的直接记忆单词快些~~~ 通

原生js写的一个弧形菜单插件

弧形菜单是一种半弧式或者全弧形菜单,是一种不同于传统横向或者竖向菜单形式的菜单.最近在网上看到好多人写出了这种效果,于是也尝试自己写了一个. 实现方式:原生态js 主要结构: 1.参数合并 1 var defaultPra = { 2 mainMenuId: "ArcMenu",//主菜单id 3 menuBoxId:"menuBox",//菜单包裹id 4 position: "",//弧形菜单 5 customPosition:"0

速记, 自己写得一个笔记本应用

速记, 自己写得一个带有提醒功能的笔记本应用. FanQiang下载: https://play.google.com/store/apps/details?id=com.revfycd.colorfullDay 特点是解决的其他应用输入时间的麻烦方式, 使用一种口语化的方式完成时间的解析, 比如输入"明天12点", 就会意当前时间为准, 计算得到明天12点. 再比如"下周3XX点", "20分钟后", "X点半"...都可以

自己写的一个帧处理代码,请多多指教

这段时间在一个项目中负责程序与下位机USB设备的通讯.将接收到的USB数据做帧处理之后做成一个完整的帧送入队列中等待上层应用对帧数据进行解析. 相信很多人在做与下位机通讯的项目的时候,都会为帧处理烦恼.因为上位机在接收数据的时候,由于收到操作系统调度的影响,有时候收到的是半帧,有时候收到的是一帧半数据.如果不做帧处理的话,就会严重丢包. 在项目中我写了一个帧处理代码,经过测试验证很稳定.拿出来分享一下,也请大家多多指教. 我们项目中的协议是<<.................>>

C# 写的一个生成随机汉语名字的小程序

最近因为要做数据库相关的测试,频繁使用到测试数据,手动添加太过于麻烦,而且复用性太差,因此干脆花了点时间写了一个生成随机姓名和相关数据的类,贴在这里,有需用的同志们可以参考一下.代码本身质量不好,也不算规范,各取所需莫要取笑-.- public class GetRandomName { private static string firstName = @"赵,钱,孙,李,周,吴,郑,王,冯,陈,褚,卫,蒋, 沈,韩,杨,朱,秦,尤,许,何,吕,施,张,孔,曹,严,华,金,魏,陶,姜, 戚,谢

自己写的一个 java swing 的闹钟

上一周新入职,把代码down下来之后,领导和我讲了一些大概的业务以及代码流程(领导是技术出身),领导让我自己先看看代码,然后我看了两天,觉得已经完全可以接任务了,但是领导却迟迟没有分配任务给我,虽然他们几个同事比较忙,但是我好像也帮不上什么忙.然后,在那一天的那个晚上,回到住处之后,突然想到一个之前想到的问题,平时我起床都是靠的手机闹钟的,可是万一某一天手机挂了,那我起床不是有问题了吗,但有一个好处就是我有笔记本,那么我可以在笔记本上面装一个闹钟呀,然后我就萌生了自己写一个闹钟的想法,虽然这两天