第二十四周專案4 猴子選大王 約瑟夫問題

2021-06-28 23:10:16 字數 3470 閱讀 1734

一群猴子,編號是1,2,3 ...m,這群猴子(m個)按照1-m的順序圍坐一圈。從第1只開始數,每數到第n個,該猴子就要離開此圈,這樣依次下來,直到圈中只剩下最後乙隻猴子,則該猴子為大王。輸入m和n,輸出為大王的猴子是幾號。

提示1:(1)鍊錶解法:可以用乙個迴圈的單鏈表來表示這一群猴子。表示結點的結構體中有兩個成員:乙個儲存猴子的編號,乙個為指向下乙個人的指標,編號為m的結點再指向編號為1的結點,以此構成環形的鏈。當數到第n個時,該結點被刪除,繼續數,直到只有乙個結點。(2)使用結構陣列來表示迴圈鏈:結構體中設乙個成員表示對應的猴子是否已經被淘汰。從第乙個人未被淘汰的數起,每數到n時,將結構中的標記改為0,表示這只猴子已被淘汰。當數到陣列中第m個元素後,重新從第乙個數起,這樣迴圈計數直到有m-1被淘汰。

這是乙個約瑟夫問題。

約瑟夫問題是個有名的問題:n個人圍成一圈,從第乙個開始報數,第m個將被殺掉,最後剩下乙個,其餘人都將被殺掉。例如n=6,m=5,被殺掉的順序是:5,4,6,2,3,1。

分析:(1)由於對於每個人只有死和活兩種狀態,因此可以用布朗型陣列標記每個人的狀態,可用true表示死,false表示活。

(2)開始時每個人都是活的,所以陣列初值全部賦為false。

(3)模擬殺人過程,直到所有人都被殺死為止。

無論是用鍊錶實現還是用陣列實現都有乙個共同點:要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達o(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最後的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規,實施一點數學策略。

為了討論方便,先把問題稍微改變一下,並不影響原意:

問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。

我們知道第乙個人(編號一定是(m-1) mod n) 出列之後,剩下的n-1個人組成了乙個新的約瑟夫環(以編號為k=m mod n的人開始):

k k+1 k+2 ... n-2,n-1,0,1,2,... k-2

並且從k開始報0。

我們把他們的編號做一下轉換:

k --> 0

k+1 --> 1

k+2 --> 2

......

k-2 --> n-2

變換後就完完全全成為了(n-1)個人報數的子問題,假如我們知道這個子問題的解:例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k) mod n

如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是乙個倒推問題!好了,思路出來了,下面寫遞推公式:

令f表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n]

遞推公式

f[1]=0;

f=(f+m) mod i; (i>1)

有了這個公式,我們要做的就是從1-n順序算出f的數值,最後結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1

由於是逐級遞推,不需要儲存每個f。

#include using namespace std;

const int m = 3;

int main()

這個演算法的時間複雜度為o(n),相對於模擬演算法已經有了很大的提高。算n,m等於一百萬,一千萬的情況不是問題了。可見,適當地運用數學策略,不僅可以讓程式設計變得簡單,而且往往會成倍地提高演算法執行效率。

筆算解決約瑟夫問題

在m比較小的時候 ,可以用筆算的方法求解,

m=2即n個人圍成一圈,1,2,1,2的報數,報到2就去死,直到只剩下乙個人為止。

當n=2^k的時候,第乙個報數的人就是最後乙個死的,

對於任意的自然數n 都可以表示為n=2^k+t,其中tn mod 3則最後死的人為新一輪的第f(n-[n/3])-(n mod 3)人

3、新一輪第k個人對應原來的第 3*[(k-1)/2]+(k-1)mod 2+1個人

綜合1,2,3可得:

f(1)=1,f(2)=2,f(3)=2,f(4)=1,f(5)=4,f(6)=1,

當f(n-[n/3])<=n mod 3時 k=n-[n/3]+f(n-[n/3])-(n mod 3),f(n)=3*[(k-1)/2]+(k-1)mod 2+1

當f(n-[n/3])>n mod 3時 k=f(n-[n/3])-(n mod 3) ,f(n)=3*[(k-1)/2]+(k-1)mod 2+1

這種演算法需要計算 [log(3/2)2009]次 這個數不大於22,可以用筆算了

於是:第一圈,將殺死669個人,這一圈最後乙個被殺死的人是2007,還剩下1340個人,

第二圈,殺死446人,還剩下894人

第三圈,殺死298人,還剩下596人

第四圈,殺死198人,還剩下398人

第五圈,殺死132人,還剩下266人

第六圈,殺死88人,還剩下178人

第七圈,殺死59人,還剩下119人

第八圈,殺死39人,還剩下80人

第九圈,殺死26人,還剩下54人

第十圈,殺死18人,還剩36人

十一圈,殺死12人,還剩24人

十二圈,殺死8人,還剩16人

十三圈,殺死5人,還剩11人

十四圈,殺死3人,還剩8人

十五圈,殺死2人,還剩6人

f(1)=1,f(2)=2,f(3)=2,f(4)=1,f(5)=4,f(6)=1,

然後逆推回去

f(8)=7 f(11)=7 f(16)=8 f(24)=11 f(36)=16 f(54)=23 f(80)=31 f(119)=43 f(178)=62 f(266)=89 f(398)=130

f(596)=191 f(894)=286 f(1340)=425 f(2009)=634

#include using namespace std;

struct monkey

;int main()

else

p2->next=head; //最後乙隻再指向第一只,成了乙個圓圈

//下面要開始數了

p1=head;

for(i=1; inext; //圍成圈的,可能再開始從第一隻數,如果還未被淘汰的話

//找到了,

p2=p1->next; //p2將被刪除

//cout

p1=p2->next; //下一輪數數的新起點

delete p2; //將不在鍊錶中的結點放棄掉

第二十四周學習筆記

自監督關鍵點檢測和特徵描述子生成 自監督訓練方法 使用全卷積神經網路架構,乙個共享的encoder對進行編碼,兩個decoder分別檢測關鍵點和生成描述子 outperform lift in almost all metrics quantitatively scores strongly in ...

第二十四周專案6 點和距離

讀程式,寫出函式的定義,注意其中列舉型別的用法。enum symmetricstyle 分別表示按x軸,y軸,原點對稱 struct point double distance1 point p1,point p2 兩點之間的距離,如果用distance,將會與命名空間std中也已經定義的dista...

第二十四講專案2 油量監控

程式 include include csdn學院 2016級 檔名稱 myfun26.c 完成日期 2016年11月8日 問題描述 設計乙個程式,用於賽車油量的監控。該程式在賽車油量偏低 少於1 4,即0.25 時,警示車手應該注意 在油箱接近滿載 不低於3 4 時,提示提手不要停車。而對於其他情...