微软过桥问题

微软的过桥问题:4个人在晚上过一座小桥,过桥时必须要用到手电筒,只有一枚手电筒,每次最多只可以有两人通过, 4个人的过桥速度分别为1分钟、2分钟、5分钟、10分钟,试问最少需要多长时间4人才可以全部通过小桥?

思路:刚一看到这道题,思路比较狭窄,就拿着笔把人移过来再移过去,第一次做出的答案是19(错误答案).后来上网上去找参考答案,发现是17.网上的答案只给出得到17分钟的解决方式,并没有说出到底应该怎么样解这个答案才能百分百确定就是最少的长时?因此,开始乱想:穷举法,贪心法,参考《牧师与野人过河问题》,DFS,图论找最小路径法,乱蒙。最开始参照<牧师与野人>的算法,发现,无果。再用穷举法,发现转移状态实在太多,没有办法递归下去,然后用贪心选择,发现最后倒在vector.erase上面了,而且递归的时候没有办法恢复现场,因为需要在vector的中间删除一个元素,然后再恢复,并不像《字符串全排列》那样可以push_back,再pop_back就能恢复现场。最后想,既然无法恢复现场,那么不妨可以从一开始就不要破坏原来现场,而只是拷贝原来vector的一份拷贝,只对拷贝进行操作,那么递归返回时就没有必要恢复原来现场,当然原来现场也并没被破坏,这样一来,每次递归结束之后,我们可以得到一个过桥方案,递归堆栈中的内容被清除(当然对原来内容并没有任何影响,下一次可以继续正常递归而不需要担心原来内容被改变而带来的不确定后果)。其于这个方案可以得到下面的程序(贪心法)。

#include<vector>
#include<iostream>
using namespace std;void removeKeyFromVec(vector<int>& veci,int key)
{for(vector<int>::iterator iter=veci.begin(); iter!=veci.end(); ++iter){if( *iter == key){iter = veci.erase(iter);//erase的使用要小心return ;}}
}void CrossBridge(vector<int>& src, vector<int>& des, int size, int TimeSum)
{//程序出口,当只有2个人时,直接过桥if(size == 2){cout<<"A->B:"<< src[0] << " AND " << src[1]<<endl;cout<<"Time : "<<TimeSum + max(src[0], src[1]) <<endl;cout<<endl;return ;} //从size大小的数列中找出两个数的组合,例1,2,5有(1,2),(1,5),(2,5) 3种组合for(int i=0; i<size; i++){for(int j=i+1; j<size; j++){cout<<"i="<<i<<" j="<<j<<endl;//只操作src的拷贝,否则递归之后需要还原现场,较麻烦vector<int> srcTemp(src) ;vector<int> desTemp(des) ;int goTime1 = srcTemp[i] ;int goTime2 = srcTemp[j] ;//2人过桥desTemp.push_back(goTime1);desTemp.push_back(goTime2) ;cout<<"A->B: " << goTime1 <<" AND "<< goTime2 <<endl;removeKeyFromVec( srcTemp ,goTime1);removeKeyFromVec( srcTemp ,goTime2);int minBackTime = desTemp[0];for(int k=0 ; k<desTemp.size(); k++){if( desTemp[k] < minBackTime){minBackTime = desTemp[k];}}//1人返回srcTemp.push_back(minBackTime);removeKeyFromVec(desTemp,minBackTime);cout<< "B->A:" << minBackTime <<endl; //不能直接用max( srcTemp[i], srcTemp[j] )//因为srcTemp[j]可能越界(之前的removeFromVec会使Vec减小)CrossBridge(srcTemp, desTemp, srcTemp.size(), TimeSum + max(goTime1, goTime2 ) + minBackTime);}}
}void main()
{int source[] = {1,2,5,10};vector<int> v_source ;//= null;vector<int> v_destination ;// = null;int time_total = 0;int size=sizeof(source)/sizeof(source[0]);for(int i=0; i<size; i++){v_source.push_back(source[i]) ;}CrossBridge( v_source, v_destination, size, 0);
}

结果如下:

i=0 j=1
A->B: 1 AND 2
B->A:1
i=0 j=1
A->B: 5 AND 10
B->A:2
A->B:1 AND 2
Time : 17i=0 j=2
A->B: 5 AND 1
B->A:1
A->B:10 AND 1
Time : 19i=1 j=2
A->B: 10 AND 1
B->A:1
A->B:5 AND 1
Time : 19i=0 j=2
A->B: 1 AND 5
B->A:1
i=0 j=1
A->B: 2 AND 10
B->A:2
A->B:1 AND 2
Time : 20i=0 j=2
A->B: 2 AND 1
B->A:1
A->B:10 AND 1
Time : 19i=1 j=2
A->B: 10 AND 1
B->A:1
A->B:2 AND 1
Time : 19i=0 j=3
A->B: 1 AND 10
B->A:1
i=0 j=1
A->B: 2 AND 5
B->A:2
A->B:1 AND 2
Time : 20i=0 j=2
A->B: 2 AND 1
B->A:1
A->B:5 AND 1
Time : 19i=1 j=2
A->B: 5 AND 1
B->A:1
A->B:2 AND 1
Time : 19i=1 j=2
A->B: 2 AND 5
B->A:2
i=0 j=1
A->B: 1 AND 10
B->A:1
A->B:2 AND 1
Time : 20i=0 j=2
A->B: 1 AND 2
B->A:1
A->B:10 AND 1
Time : 20i=1 j=2
A->B: 10 AND 2
B->A:2
A->B:1 AND 2
Time : 21i=1 j=3
A->B: 2 AND 10
B->A:2
i=0 j=1
A->B: 1 AND 5
B->A:1
A->B:2 AND 1
Time : 20i=0 j=2
A->B: 1 AND 2
B->A:1
A->B:5 AND 1
Time : 20i=1 j=2
A->B: 5 AND 2
B->A:2
A->B:1 AND 2
Time : 21i=2 j=3
A->B: 5 AND 10
B->A:5
i=0 j=1
A->B: 1 AND 2
B->A:1
A->B:5 AND 1
Time : 23i=0 j=2
A->B: 1 AND 5
B->A:1
A->B:2 AND 1
Time : 23i=1 j=2
A->B: 2 AND 5
B->A:2
A->B:1 AND 2
Time : 24

由上面,哪个用时最少一目了然。

在另外一篇博客中,找到了我最想做的一种办法,而且分析得一针见血。现在引用原文如下:

http://blog.csdn.net/drzhouweiming/article/details/1340741

这个问题如果用图论来建模的话,就可以以4个人在桥两端的状态来作为节点来构造一个有向图,如下图所示,以已经过桥了的人的状态作为图的节点,初始时没有人过桥,所以以空表示,第一轮有两个人过桥,有6种可能的组合,(1,2)(1,5)(1,10)(2,5)(2,10)(5,10),从空的状态转换到这些状态的需要的时间分别为2,5,10,5,10,10分钟,时间就作为有向边的权值。当有两个人过桥后,需要一个人拿手电筒回去接其他人,这时有四种可能的情况,分别是1,2,5,10中的一人留在了河的对岸,(1,2)这种状态只能转换到(1)(2)两种状态,对应的边的权值分别为2,1分钟,(1,2)转换到(1)时也就是2返回了,返回需要耗时2分钟,以此类推可以建立以下的图论模型。

要求出最少需要多长时间4人全部通过小桥实际上就是在图中求出(空)节点到(1,2,5,10)节点间的最短路径。

根据Dijkstra最短路径算法很容易求出其最短路径,如图中的粗线所示。

这样总时间为2+1+10+2+2=17分钟

所以能够活学图论的话,这类智力问题就变成了图论的入门级的问题。

(引用完)

简单说明一下,图中结点状态是以右岸的状态为基础的,例如,一开始右岸还没有人过桥,则右岸为NULL,从NULL可以转移的状态有,左岸过去{1,2},{1,5},{1,10},{2,5},{2,10},{5,10}.则从NULL可以转移到上述状态,则空结点连接到这些结点上,依此类推,可以建立一整个图,再对图找最短路径即可。

代码:(用邻接矩阵存图)http://blog.csdn.net/linraise/article/details/9957057

Dijkstra算法三部曲

1.初始化

2.贪心选择当前最短结点,即最小dist[i]对应的i

3.更新dist[]和path[]

/*** DIJKSTRA(简单版) 单源最短路径算法(不允许存在负边)* 输入:(1)图g;        // 有向图或者无向图 *         (2)源点s。 * 输出:(1)源点s到各点的最短路径长dist; *         (2)源点s到各点的最短路径path。* 结构: 图g用邻接矩阵表示,最短路径长dist用数组表示。 * 算法:Dijkstra算法  * 复杂度:O(|V|^2) */
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <functional>
#include <climits>
using namespace std;int n;                    // n : 顶点个数
vector<vector<int> > g; // g : 图(graph)(用邻接矩阵(adjacent matrix)表示)
int s;                    // s : 源点(source)
vector<bool> known;        // known : 各点是否知道最短路径
vector<int> dist;        // dist : 源点s到各点的最短路径长
vector<int> path;        // path : 各点最短路径的前一顶点void Dijkstra()            // 贪心算法(Greedy Algorithm)
{known.assign(n, false);dist.assign(n, INT_MAX);path.resize(n);            // 初始化known、dist、path。 dist[s] = 0;            // 初始化源点s到自身的路径长为0。 for (;;){int min = INT_MAX, v = s;for (int i = 0; i < n; ++i)if (!known[i] && min > dist[i])min = dist[i], v = i;    // 寻找未知的最短路径长的顶点v, if (min == INT_MAX) break;        // 如果找不到,退出; known[v] = true;                // 如果找到,将顶点v设为已知, for (int w = 0; w < n; ++w)        // 遍历所有v指向的顶点w, if (!known[w] && g[v][w] < INT_MAX && dist[w] > dist[v] + g[v][w])dist[w] = dist[v] + g[v][w], path[w] = v;    // 调整顶点w的最短路径长dist和最短路径的前一顶点 path。 }
}void Print_SP(int v)
{if (v != s) Print_SP(path[v]);cout << v << " ";
}int main()
{n = 22;g.assign(n, vector<int>(n, INT_MAX));g[0][1] = 2; g[0][2] = 5,g[0][3]=10,g[0][4]=5,g[0][5]=10,g[0][6]=10;g[1][7] = 2; g[1][8] = 1; g[2][7] = 5; g[2][9] = 1; g[3][7] = 10; g[3][10] = 1; g[4][8] = 5; g[4][9] = 2; g[5][8] = 10; g[5][10] = 2; g[6][9] = 10;g[6][10] = 5;g[7][11]=5;g[7][12]=10;g[7][13]=10;g[8][11]=5;g[8][12]=10;g[8][14]=10;g[9][11]=2;g[9][13]=10;g[9][14]=10;g[10][12]=2;g[10][13]=5;g[10][14]=5;g[11][15]=5;g[11][16]=2;g[11][18]=1;g[12][15]=10;g[12][17]=2;g[12][19]=1;g[13][16]=10;g[13][17]=5;g[13][20]=1;g[14][18]=10;g[14][19]=5;g[14][20]=2;g[15][21]=10;g[16][21]=10;g[17][21]=5;g[18][21]=10;g[19][21]=5;g[20][21]=2;s = 0;Dijkstra();copy(dist.begin(), dist.end(), ostream_iterator<int>(cout, " ")); cout << endl;for (int i = 0; i < n; ++i)if(dist[i] != INT_MAX){cout << s << "->" << i << ": ";Print_SP(i); cout << endl; }return 0;
}

倒水问题

从庞果网的一道题切入。来源http://hero.pongo.cn/Question/Details?ID=70&ExamID=68

题目详情

有两个容器,容积分别为A升和B升,有无限多的水,现在需要C升水。
我们还有一个足够大的水缸,足够容纳C升水。起初它是空的,我们只能往水缸里倒入水,而不能倒出。
可以进行的操作是:
把一个容器灌满;
把一个容器清空(容器里剩余的水全部倒掉,或者倒入水缸);
用一个容器的水倒入另外一个容器,直到倒出水的容器空或者倒入水的容器满。
    问是否能够通过有限次操作,使得水缸最后恰好有C升水。

输入:三个整数A, B, C,其中 0 < A , B, C <= 1000000000

输出:0或1,表示能否达到要求。

思路:

这道题可以抽象成这样描述:求方程Ax+By=C(1).的整数解x,y。

到这里需要一些备用知识:扩展的欧几里德算法。

参考资料:

http://baike.baidu.com/view/1478219.htm

http://zh.wikipedia.org/wiki/%E8%BC%BE%E8%BD%89%E7%9B%B8%E9%99%A4%E6%B3%95

http://zh.wikipedia.org/wiki/%E6%89%A9%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95

定理:
对于不完全为 0 的非负整数 a,b,gcd(a,b)表示 a,b 的最大公约数,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by(2)。

由上面(1)(2)式子可以看得出,二者存在一个桥梁:ax+by.那么这道题就可以转化成为求Factor*gcd(a,b)=C是否存在整数解Factor,即gcd(a,b)是否是C的因数。

代码比较容易:

int euclid1(int m,int n)
{ if(n == 0)return m;else return euclid1(n, m % n);
}
int euclid2(int m,int n)
{int r;do{r=m % n;m=n;n=r;}while(r);return m;
}
bool can(int a,int b,int c) {  int res;res=euclid1(a,b);if(c%res==0)return true;  else return false;
}

上面的代码只能求出能否倒水成功,但是,如果可以倒水成功,那么如何求出倒水方案呢?

先看一个简单的例子:用5升的容器和7升的容器如何倒出3升水?

我们可以这么做,穷举--用小容量的容器不停地往大容器倒水,直至倒出3升水先止。

A:5升    B:7升 (1)

0              0

5              0  注满A

0              5  A倒往B

5              5  注满A

3              7  A倒往B,完成。

也可以这么写(2)

5 % 7 = 5

10 % 7=3,完成

现在用程序去模拟(1)的倒水注水过程。

#include<stdio.h>void WaterProblem(int A,int B,int C)
{int A_water = 0 ;int B_water = 0 ;while(1){if( A_water == 0) //A空,则注满{A_water=A ; printf("A:%d  B:%d  注满A容器\n",A_water,B_water ) ;}else{if( A_water > (B-B_water) ){A_water -= (B-B_water); //A倒出一部分B_water = B ;           //注满B   printf("A:%d  B:%d A容器中的水倒入B容器\n",A_water,B_water );B_water = 0 ; //倒空Bprintf("A:%d  B:%d   B容器的水清空\n",A_water,B_water );}else //A_water <=(B-B_water){B_water += A_water ;  //将A中所有水倒进BA_water = 0 ;           //A中水已经倒空printf("A:%d  B:%d    A容器中的水倒入B容器\n",A_water,B_water );}}if( A_water == C || B_water == C)break;}
}
void main()
{WaterProblem(5,7,3);
}

解答如下:

http://module77.is-programmer.com/user_files/Module77/File/watersol.swf右键点击在新窗口打开,csdn貌似不支持flash。

flash中的的位置就是容器的一个状态,与边平行走就相当于取水或倒水,走斜线就相当于从一个杯子往

另一个里面倒水,整个倒水的过程就像撞球来回的反弹一样,球可以到达的边界点就是可解的。用类似的

方法可以很轻松的解决这类问题了。

微软过桥问题Dijkstra/倒水问题相关推荐

  1. 微软过桥问题的图论解法

       微软过桥问题的图论解法 微软的过桥问题说的是4个人在晚上过一座小桥,过桥时必须要用到手电筒,只有一枚手电筒,每次最多只可以有两人通过, 4个人的过桥速度分别为1分钟.2分钟.5分钟.10分钟,试 ...

  2. 过桥问题和倒水问题算法

    过桥问题和倒水问题都是笔试面试中的热门智力题,不但微软.GOOGLE.百度.腾讯等公司采用,甚至在IQ测试与公务员考试中都能见到.本文不但教你如何快速用手算来解决这两种问题,并且教你如何用程序代码来计 ...

  3. ios笔试题算法_微软笔试题-Dijkstra算法

    Dijkstra算法是典型的算法.Dijkstra算法是很有代表性的算法.Dijkstra一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPEN, CLOSE表的方式,这里均采用永久和临 ...

  4. 过桥问题——图论解法

    微软过桥问题的图论解法 微软的过桥问题说的是4个人在晚上过一座小桥,过桥时必须要用到手电筒,只有一枚手电筒,每次最多只可以有两人通过, 4个人的过桥速度分别为1分钟.2分钟.5分钟.10分钟,试问最少 ...

  5. 信息奥赛一本通1232:Crossing River

    [题目描述] 几个人过河,每次过两人一人回,速度由慢者决定,问过河所需最短时间. [输入] 输入t组数据,每组数据第1行输入n,第2行输入n个数,表示每个人过河的时间. [输出] 输出t行数据,每行1 ...

  6. 测试驱动需求分析--需求文档评审实例

    相关文章链接如下: 微软过桥问题与测试人员素养 等价类分法 新解 测试用例设计中的NP难题 C/C++代码检视实例 90%程序员写不出无BUG的二分查找程序?                  需求文 ...

  7. 2015阿里巴巴实习生招聘笔试题,带答案,欢迎一起来讨论哇!

    欢迎对本文提出补充建议,可以在以下平台上我留言. 笔试面试交流群:167676365 个人博客站点:www.anycodex.com/blog/ Csdn博客站点:http://my.csdn.net ...

  8. N人过桥问题的求解(微软试题)

    2019独角兽企业重金招聘Python工程师标准>>> N人过桥问题的求解(微软试题) 微软面试题过桥问题在IT业内几乎已变成一个众所周知的问题,问题如下:     4个人在晚上过一 ...

  9. Dijkstra算法 ——四人过桥

    有一天晚上,有四个人需要通过架在山谷间的危桥,任意时刻最多只能有两个人在桥上,过桥需要一盏闪光灯,这些人只有一盏闪光灯.如果单独过桥他们分别需要10.5.2.1分钟,如果两人同时过桥则所需时间是较慢者 ...

最新文章

  1. 目标检测新技能!引入知识图谱:Reasoning-RCNN
  2. 服务器根目录文件配置文件,在文档根目录中存储安装和配置文件
  3. Javascript删除JSON元素
  4. 20 Very Useful Java Code Snippets
  5. 零代码入门GitHub,图形化交互让你轻松存代码 | 附Git GUI推荐
  6. MySQL执行计划解读
  7. Html,xhtml,xml的定义和区别
  8. 电压越低采集的ad值反而变大_80多条关于AD转换设计的经验总结
  9. .net core 生成html,ASP.NET Core 中如何将 .cshtml 视图文件生成为 .cs 文件
  10. python web为什么不火_如何用纯 Python 写交互式 Web 应用?
  11. 字符驱动之按键(四:poll机制)
  12. ajax兼容写法,Ajax的兼容性问题
  13. 闭包 装饰器 偏函数
  14. JDK API下载
  15. Shiro-单点登录原理
  16. linux中运行屏幕分辨率,Linux设置显示器分辨率与刷新率
  17. Python学习:垃圾回收机制
  18. vertica MySQL_Vertica数据库 安装 | 学步园
  19. 初识MIMO(六):MU-MIMO的仿真
  20. x86架构鼻祖-i8086

热门文章

  1. 流浪地球2的科学幻想与现实中的未来计算机科技
  2. 2005年11月网络工程师试题
  3. 【工业机器人】两分钟读懂工业机器人的设计过程
  4. Stata数据处理:FRED数据导入问题的解决方案
  5. 精度超越ConvNeXt的新CNN——HorNet:通过递归门控卷积实现高效高阶的空间信息交互
  6. 莫斯科计划在 17 万个监控摄像头中引入人脸识别;广东农行“刷脸取款”实现 1600 多个网点全覆盖...
  7. Python:爬了下链家的小区数据,为数据分析弄素材~
  8. jenkins + Gitlab + dingding 钉钉通知
  9. 电源服务器原理,破坏性拆解一个HP服务器1300W的电源 【精华】
  10. 六、Django-Registration-Redux的基本使用