递归用法之“海盗分赃难题”

海盗分赃难题:

  十个海盗要瓜分100枚金币,为此他们拟定了以下规则。

  从船长到厨子每个海盗由高到低共分十个等级,分配权在最高等级的海盗手里。他可以任意分配每个海盗的所得,但必须取得一半或一半以上海盗(包括自己在内)的支持,否则他将被同伴处死。处死之后分配权将转移到下一个等级最高的海盗手里,当然,他也将面临同样艰难的选择。

  基于海盗们贪婪而凶残的本性,每个没有分配权的海盗都想分得更多的金币和处死自己的上级。但相对后者而言,多分得一枚金币也许更有吸引力。我们假定所有的海盗都是深思熟虑的老手,他们能精确地计算自己未来的得失,从而根据利益最大原则支持或反对上级的分配。

  问:作为一号海盗的船长有什么办法为自己分得最多的金币而不被处决?


问题的背景:

  “海盗分赃”算是一道比较经典的智力测试题,也是我所见过的最有挑战性的一道。据说你能在半小时内解开说明你很了不起,至少不会输给那些“深思熟虑的老手”。对啦,我的意思是说所有的海盗必须是知道答案的,否则本题的假设不能成立,我最欣赏这一点。


解题的关键:

  解题的关键在于反向推理。

  假设只剩下九号和十号海盗,按规则应该由九号海盗分配。猜会怎么着?九号一定会把100枚金币统统据为己有,因为这时已经没有任何力量可以阻止他这么做了,他给自己投一票就能达到50%的支持率。

  由此,“深思熟虑”的十号海盗一定会明白处死八号自己最好的结果也将是一无所得。

  现在假定由八号来分赃,十号一定会这么想:“他至少应该分给我一枚金枚,否则我一定投票弄死他。”

  “深思熟虑”的八号也一定不难猜到十号的心理动向,因为根据推理,十号的打算是必然的结果。于是他决定用最小的代价——1枚金币——去贿赂十号海盗。如此,加上他自己的支持,2比1,他肯定死不了!所以九号海盗一毛钱也别想捞。

  按照上面的思路可以一直逆推回一号海盗,船长可以根据船员们的心理作出对自己最有利的分配方案。


递归的作用:

  如果要求我们用程序来模拟这一过程该怎么办?

  递归。对!大多数训练有素的程序员一定会先想到递归。因为它实在就是递归类问题的典范。

  按照我们的分析过程,我把伪代码写在下面:

/*
 *DivideSpoils为递归函数
 *参数num为参与分赃的海盗数
 *分赃成功函数则返回分赃结果,否则函数返回空值
 */
DivideSpoils (num)
    if num=2 then
        rst[0]=100
        rst[1]=0
        return rst
    endif

    rst=DivideSpoils(num-1)
    if rst=空值 then
        return 空值
    else
        /*重新分配,使至少一半的海盗感到满意*/
        rst=Redivide(rst,num-1)
        return rst
    endif

end

/*
 *Redivide负责对金币重新分配,使至少一半的海盗感到满意
 *oldrst为num个海盗的分配结果
 *分配成功则返回num+1个海盗的分配结果,否则函数返回空值
 */
Redivide (oldrst,num)
    rst := 1X(num+1)维数组
    s := 1Xnum维数组
    half=[num/2]  //[x]为不大于num/2的最大整数

    /*对oldrst从小到大排列,只需排出前第half位,排列后的下标存入s中*/
    s[i]={k, oldrst[s[k-1]]<=oldrst[k]} when i<half
    s[i]=i when i>=half

    /*贿赂一半的海盗,使他们能多分一枚金币*/
    rst[s[i]+1]=oldrst[s[i]]+1 when i<half
    /*余下的另一半一分钱不给*/
    rst[s[i]+1]=0 when i>=half
    /*余下金币则归自己*/
    rst[0]=余下金币

    if rst[0]<0 then  //rst[0]<0说明没有足够金币贿赂部下,分配失败
        return 空值
    else
        return rst
    endif

end

  

由以上代码来看,递归的停止条件来自我们上面基于两个海盗分赃结果的假设。我们似乎可以进一步简化它:

if num=1 then
    rst[0]=100
    return rst
endif

这样做程序也能工作,并得到了正确答案。可想见递归是门很灵活的艺术。


问题的扩展:

  分赃问题很简单是不是?不简单!还记得三个海盗分赃时我们准确分析了十号的心理么?是不是还遗漏什么了?是,九号怎么想的我们完全没有去考虑,这种粗暴的态度可能会给分配者带来无法预料的灾难!

  怎么说?我们不妨设想当九号推断出自己将受到不公正待遇的时候,他可能会私下跟十号协商。他可能信誓旦旦地保证:“嘿,兄弟,我们把八号给做了,剩下的金币四六分怎么样?” 尽管他在履行承诺后的所得要低于自己的部下,但这比起空手而归却要好得很多,况且他们还结果了一个上级。

  八号在得知这种情况后一定很恐慌,因为他已经完全不能决定自己的命运了。他必须尽最大可能去贿赂十号,60金币?70?100?!!!他惊恐地睁大了眼睛,他怎么会知道九号承诺了多少,也许是100金币?九号认定要取自己性命了?!

  我们可以想像这时十号肯定颇为满意,他似乎惟一要做的就是不动声色,看谁出的价位更能打动他。其实不然,因为八号可能会转而与九号密谈,比如说之前九号承诺干掉八号以后自己只拿40金,现在八号承诺给九号41金,九号就会反过到拥护8号...

  到此为止海盗分赃问题就已经不再是单纯的智力测试了,而应该配得上另一个更体面的名字:博弈论。

  最后海盗们会不会达成某个满意的协议,我现在还不知道,这个问题打发给以后的空闲时间。


c语言源代码:

ContractedBlock.gif ExpandedBlockStart.gif View Code
#include<stdio.h>
#include
<stdlib.h>

const int COINS=100;
const int PIRATES=10;

int* divi_spoils(int);
int* redivide(int*,int);
int* rank(int*,int,int);

int main(void){
int i;
int* rst;

rst
=divi_spoils(PIRATES);
if(rst){
for(i=0;i<PIRATES;i++)
printf(
"Pirate<%d>: %d coins\n",i+1,rst[i]);
}
else{
printf(
"Be Executed!\n");
}

free(rst);
return 0;
}

int* divi_spoils(int total){
int* rst=NULL;

if(total==1){
rst
=(int*)malloc(total*sizeof(int));
rst[
0]=COINS;
return rst;
}

rst
=divi_spoils(total-1);
if(rst==NULL)
return NULL;
else{
rst
=redivide(rst,total-1);
return rst;
}
}

int* redivide(int* old_rst, int n){
int i,left=COINS,half=n/2;
int* s,* rst;

s
=rank(old_rst,n,half);
rst
=(int*)malloc((n+1)*sizeof(int));
for(i=0;i<half;i++){
rst[s[i]
+1]=old_rst[s[i]]+1;
left
-=rst[s[i]+1];
}
for(i=half;i<n;i++)
rst[s[i]
+1]=0;
free(s);
free(old_rst);

if(left<0){
free(rst);
return NULL;
}
else{
rst[
0]=left;
return rst;
}
}

int* rank(int* data, int n, int len){
int i,j,tmp;
int* s;

s
=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
s[i]
=i;

for(i=0;i<len;i++)
for(j=n-1;j>i;j--)
if(data[s[j]]<data[s[j-1]]){
tmp
=s[j];
s[j]
=s[j-1];
s[j
-1]=tmp;
}

return s;
}


  

转载于:https://www.cnblogs.com/keenlog/archive/2011/09/03/2165757.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值