递归用法之“海盗分赃难题”
海盜分贓難題:
十個海盜要瓜分100枚金幣,為此他們擬定了以下規(guī)則。
從船長到廚子每個海盜由高到低共分十個等級,分配權在最高等級的海盜手里。他可以任意分配每個海盜的所得,但必須取得一半或一半以上海盜(包括自己在內(nèi))的支持,否則他將被同伴處死。處死之后分配權將轉移到下一個等級最高的海盜手里,當然,他也將面臨同樣艱難的選擇。
基于海盜們貪婪而兇殘的本性,每個沒有分配權的海盜都想分得更多的金幣和處死自己的上級。但相對后者而言,多分得一枚金幣也許更有吸引力。我們假定所有的海盜都是深思熟慮的老手,他們能精確地計算自己未來的得失,從而根據(jù)利益最大原則支持或反對上級的分配。
問:作為一號海盜的船長有什么辦法為自己分得最多的金幣而不被處決?
問題的背景:
“海盜分贓”算是一道比較經(jīng)典的智力測試題,也是我所見過的最有挑戰(zhàn)性的一道。據(jù)說你能在半小時內(nèi)解開說明你很了不起,至少不會輸給那些“深思熟慮的老手”。對啦,我的意思是說所有的海盜必須是知道答案的,否則本題的假設不能成立,我最欣賞這一點。
解題的關鍵:
解題的關鍵在于反向推理。
假設只剩下九號和十號海盜,按規(guī)則應該由九號海盜分配。猜會怎么著?九號一定會把100枚金幣統(tǒng)統(tǒng)據(jù)為己有,因為這時已經(jīng)沒有任何力量可以阻止他這么做了,他給自己投一票就能達到50%的支持率。
由此,“深思熟慮”的十號海盜一定會明白處死八號自己最好的結果也將是一無所得。
現(xiàn)在假定由八號來分贓,十號一定會這么想:“他至少應該分給我一枚金枚,否則我一定投票弄死他。”
“深思熟慮”的八號也一定不難猜到十號的心理動向,因為根據(jù)推理,十號的打算是必然的結果。于是他決定用最小的代價——1枚金幣——去賄賂十號海盜。如此,加上他自己的支持,2比1,他肯定死不了!所以九號海盜一毛錢也別想撈。
按照上面的思路可以一直逆推回一號海盜,船長可以根據(jù)船員們的心理作出對自己最有利的分配方案。
遞歸的作用:
如果要求我們用程序來模擬這一過程該怎么辦?
遞歸。對!大多數(shù)訓練有素的程序員一定會先想到遞歸。因為它實在就是遞歸類問題的典范。
按照我們的分析過程,我把偽代碼寫在下面:
/**DivideSpoils為遞歸函數(shù)*參數(shù)num為參與分贓的海盜數(shù)*分贓成功函數(shù)則返回分贓結果,否則函數(shù)返回空值*/ DivideSpoils (num)if num=2 thenrst[0]=100rst[1]=0return rstendifrst=DivideSpoils(num-1)if rst=空值 thenreturn 空值else/*重新分配,使至少一半的海盜感到滿意*/rst=Redivide(rst,num-1)return rstendifend/**Redivide負責對金幣重新分配,使至少一半的海盜感到滿意*oldrst為num個海盜的分配結果*分配成功則返回num+1個海盜的分配結果,否則函數(shù)返回空值*/ Redivide (oldrst,num)rst := 1X(num+1)維數(shù)組s := 1Xnum維數(shù)組half=[num/2] //[x]為不大于num/2的最大整數(shù)/*對oldrst從小到大排列,只需排出前第half位,排列后的下標存入s中*/s[i]={k, oldrst[s[k-1]]<=oldrst[k]} when i<halfs[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 空值elsereturn rstendifend
由以上代碼來看,遞歸的停止條件來自我們上面基于兩個海盜分贓結果的假設。我們似乎可以進一步簡化它:
if num=1 thenrst[0]=100return rst endif這樣做程序也能工作,并得到了正確答案。可想見遞歸是門很靈活的藝術。
問題的擴展:
分贓問題很簡單是不是?不簡單!還記得三個海盜分贓時我們準確分析了十號的心理么?是不是還遺漏什么了?是,九號怎么想的我們完全沒有去考慮,這種粗暴的態(tài)度可能會給分配者帶來無法預料的災難!
怎么說?我們不妨設想當九號推斷出自己將受到不公正待遇的時候,他可能會私下跟十號協(xié)商。他可能信誓旦旦地保證:“嘿,兄弟,我們把八號給做了,剩下的金幣四六分怎么樣?” 盡管他在履行承諾后的所得要低于自己的部下,但這比起空手而歸卻要好得很多,況且他們還結果了一個上級。
八號在得知這種情況后一定很恐慌,因為他已經(jīng)完全不能決定自己的命運了。他必須盡最大可能去賄賂十號,60金幣?70?100?!!!他驚恐地睜大了眼睛,他怎么會知道九號承諾了多少,也許是100金幣?九號認定要取自己性命了?!
我們可以想像這時十號肯定頗為滿意,他似乎惟一要做的就是不動聲色,看誰出的價位更能打動他。其實不然,因為八號可能會轉而與九號密談,比如說之前九號承諾干掉八號以后自己只拿40金,現(xiàn)在八號承諾給九號41金,九號就會反過到擁護8號...
到此為止海盜分贓問題就已經(jīng)不再是單純的智力測試了,而應該配得上另一個更體面的名字:博弈論。
最后海盜們會不會達成某個滿意的協(xié)議,我現(xiàn)在還不知道,這個問題打發(fā)給以后的空閑時間。
c語言源代碼:
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
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的递归用法之“海盗分赃难题”的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: The Definitive Guide
- 下一篇: win7或xp下常用命令