算法数据结构和算法分析数据结构和算法

经典算法应用之六---过桥问题和过河问题

2016-10-19  本文已影响949人  爱情小傻蛋

过桥问题和倒水问题都是笔试面试中的热门智力题,不但微软、GOOGLE、百度、腾讯等公司采用,甚至在IQ测试与公务员考试中都能见到。本文不但教你如何快速用手算来解决这两种问题,并且教你如何用程序代码来计算这两种问题。绝对让你大有收获。
一.过桥问题
在漆黑的夜里,四位旅行者来到了一座狭窄而且没有护栏的桥边。如果不借助手电筒的话,大家是无论如何也不敢过桥去的。不幸的是,四个人一共只带了一只手电筒,而桥窄得只够让两个人同时通过。如果各自单独过桥的话,四人所需要的时间分别是1,2,5,8分钟;而如果两人同时过桥,所需要的时间就是走得比较慢的那个人单独行动时所需的时间。问题是,你如何设计一个方案,让用的时间最少。
这个题目被微软、GOOGLE、百度、华硕、建设银行等很多公司用作笔试题或面试题。当然也有用在IQ测试中。
解答其实也容易,能者多劳这四个字就足以形容解答方案了——用时短的人必须要多跑几趟以便传递手电筒。
设这四个人叫做A,B,C,D,他们所需要的时间分别是1,2,5,8分钟。
第一步:A和B过桥,花费2分钟。
第二步:A回来,花费1分钟。
第三步:C和D过桥,花费8分钟。
第四步:B回来,花费2分钟。
第五步:A和B过桥,花费2分钟。
这样只要花费2+1+8+2+2=15分钟,下面再来考虑如何用程序来解决这类问题,在写程序之前还有个细节要考虑下,比如A,B,C,D四个人所需要的时间分别是1,8,9,10分钟。
方案一
第一步:A和B过桥,花费8分钟。
第二步:A回来,花费1分钟。
第三步:C和D过桥,花费10分钟。
第四步:B回来,花费8分钟。
第五步:A和B过桥,花费8分钟。
一共要8+1+10+8+8=35分钟。
方案二
第一步:A和B过桥,花费8分钟。
第二步:A回来,花费1分钟。
第三步:A和C过桥,花费9分钟。
第四步:A回来,花费1分钟。
第五步:A和D过桥,花费10分钟。
一共要8+1+9+1+10=29分钟。
因此可以得出更加细化的解决方案——要么是最快者将最慢的2个送过桥,要么是最快的2个将最慢的2个送过桥。即将过桥的人按其过桥的时间从小到大排列,设为A,B,……Y,Z。其中A和B是最快的二个,Y和Z是最慢的二个。那么就有二种方案:
方案一 最快者将最慢的2个送过桥
第一步:A和Z过桥,花费Z分钟。
第二步:A回来,花费A分钟。
第三步:A和Y过桥,花费Y分钟。
第四步:A回来,花费A分钟。
这四步后总人数就减小2个,花费时间为A + A + Y + Z分钟。
方案二 最快的2个将最慢的2个送过桥
第一步:A和B过桥,花费B分钟。
第二步:A回来,花费A分钟。
第三步:Y和Z过桥,花费Z分钟。
第四步:B回来,花费B分钟。
这四步后总人数同样减小2个,花费时间为A + B + B + Z分钟。
这样,每次比较一下这二种方案就能将总人数减小2。然后我们再考虑一些边界情况:
有三个人过桥设为A,B,C(已经排好序,下同)。应该花费A + B + C分钟。
有二个人过桥设为A,B。那么肯定是花费B分钟。
有一个人过桥设为A。肯定花费A分钟。

分析至此,代码很容易写出,对输入的数据可以用快速排序来排下序(关于qsort的使用,可以参见《使用VC库函数中的快速排序函数》)。

//热门智力题 - 过桥问题  
const int MAXN = 1000;  
int cmp(const void *x,const void *y)  
{  
    return *(int *)x-*(int *)y;  
}  
int main()  
{  
    cout<<"热门智力题 - 过桥问题"<<endl;  
    cout<<"  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n"<<endl;    
  
    int n, i, sum, a[MAXN];  
    cout<<"请输入人数:";  
    cin>>n;  
    cout<<"请输入每个人过桥时间,以空格分开"<<endl;  
    for (i = 0; i < n; i++)  
        cin>>a[i];  
    qsort(a, n, sizeof(a[0]), cmp);  
    sum = 0;  
    for (i = n - 1; i > 2; i = i - 2)  
    {    
        //最小者将最大2个送走或最小2个将最大2个送走  
        if (a[0] + a[1] + a[1] + a[i] < a[0] + a[0] + a[i - 1] + a[i])  
            sum = sum + a[0] + a[1] + a[1] + a[i];  
        else  
            sum = sum + a[0] + a[0] + a[i - 1] + a[i];    
    }  
    if (i == 2)  
        sum = sum + a[0] + a[1] + a[2];  
    else if (i == 1)   
        sum = sum + a[1];  
    else   
        sum = a[0];  
    cout<<"最短过桥时间为:"<<sum<<endl;  
    return 0;  
}  

程序运行结果如下:

二.倒水问题
这个题目的版本非常之多,有微软版的,腾讯版的,新浪版的等等,最常见的是给你一个容量为5升的桶和一个容量为3升的桶,水不限使用,要求精确得到4升水。
解法肯定有很多,可以用宽度优先搜索(BFS),也可以用穷举法。穷举法实现比较方便,其基本思想是用:用小桶容量的倍数对大桶的容量进行取余。比如3升的桶和5升的桶得到4升水可以这样做:
3 % 5 = 3
6 % 5 = 1
9 % 5 = 4
成功得到4升水。(PS:上面的过程用如何用文字描述了?)
同样,用7升的桶和11升的桶得到2升水可以这样做:
7 % 11 = 7
14 % 11 = 3
21 % 11 = 10
28 % 11 = 6
35 % 11 = 2
成功得到2升水。
哈哈,有了这个基本思想在用笔算答案时简直是遇神杀神,遇佛杀佛,又方便又快速!如果要求用程序来实现如何做了?easy,将倒水问题的基本思想用易于编程的话来翻译下——不断用小桶装水倒入大桶,大桶满了立即清空,每次判断下二个桶中水的容量是否等于指定容量。有了这个倒水问题的编程指导方针后代码非常容易写出:

//热门智力题 - 打水问题  
//基本思想:用小桶容量的倍数对大桶的容量进行取余。  
//指导方针:不断用小桶装水倒入大桶,大桶满了立即清空,  
//每次判断下二个桶中水的容量是否等于指定容量。  
const string OPERATOR_NAME[7] = {  
    "装满A桶","装满B桶",  
    "将A桶清空","将B桶清空",  
    "A桶中水倒入B桶","B桶中水倒入A桶",  
    "成功"  
};  
int main()  
{  
    cout<<"热门智力题 - 打水问题"<<endl;  
    cout<<"  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n"<<endl;  
  
    int    a_volume, b_volume, goal_volume;  
    vector record;       //记录操作步数  
    int    ai;  
    int    i, a_water, b_water;  
  
    cout<<"请输入A桶容量,B桶容量,目标容量:";  
    cin>>a_volume>>b_volume>>goal_volume;  
    a_water = b_water = 0; //A桶,B桶中有多少升水  
    char szTemp[30];  
    while (true)  
    {  
        if (a_water == 0)//A桶没水,就装满水  
        {  
            a_water = a_volume;  
            sprintf(szTemp, "         A:%d  B:%d", a_water, b_water);   
            record.push_back(OPERATOR_NAME[0] + szTemp);//fill A  
        }  
        else  
        {  
            //如果A桶的水比(B桶容量-B桶的水)要多  
            if (a_water > b_volume - b_water)  
            {  
                //A桶的水==A桶的水+B桶的水-B桶容量  
                a_water = a_water + b_water- b_volume;  
                b_water = b_volume;      //B桶的水装满了  
                sprintf(szTemp, "  A:%d  B:%d", a_water, b_water);   
                record.push_back(OPERATOR_NAME[4] + szTemp);//A->B     
                if (a_water == goal_volume)  
                    break;  
                b_water = 0;            //将B桶清空  
                sprintf(szTemp, "       A:%d  B:%d", a_water, b_water);   
                record.push_back(OPERATOR_NAME[3] + szTemp);  
            }  
            else  
            {  
                //B桶的水==A桶的水+B桶的水  
                b_water += a_water;   
                a_water = 0;  
                sprintf(szTemp, "  A:%d  B:%d", a_water, b_water);  
                record.push_back(OPERATOR_NAME[4] + szTemp);//A->B  
                if (b_water == goal_volume)   
                    break;  
            }  
        }  
    }  
    record.push_back(OPERATOR_NAME[6]); //success  
    cout<<"\n---------------------------------------------------"<<endl;  
    cout<<"一个可行的倒水方案如下"<<endl;  
    vector::iterator pos;  
    for (pos = record.begin(); pos != record.end(); pos++)  
        cout<<*pos<<endl;  
    cout<<"---------------------------------------------------"<<endl;  
    return 0;  
}  

程序运行结果如下:



注意这里只是给出一个可行的倒水方案,不一定是最优解。另外倒水问题要注意下像用2升的桶和4升的桶得到3升水这种不可解的情况,这种不可解情况在用本文中对倒水问题所总结的基本思想计算时会得到循环数列。其根本原因是二个桶容量的最大公约数无法被目标容量所整除,如6升的桶和9升的桶无法得到2升水是因为6和9的最大公约数是3即GCD(6,9)=3而3无法整除2。
倒水问题也也容易推广到多个桶的情况,分析方法和上文差不多,这里就不再赘述了。

过桥问题和倒水问题就讲解到此,相信以后这二类问题都不会再困扰大家了_


推荐阅读:
经典算法应用之一----归并排序(微软笔试题)
经典算法应用之二----基数排序(google笔试题)
经典算法应用之三----应用二中题目的升华
经典算法应用之四(上)---基本位操作之算法篇
经典算法应用之四(中)---基本位操作之算法篇
经典算法应用之四(下)---百度面试题
经典算法应用之五---随机生成和为S的N个正整数
经典算法应用之六---过桥问题和过河问题
经典算法应用之七----10亿数据中取最大的100个数据

上一篇下一篇

猜你喜欢

热点阅读