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