1. 程式人生 > >約瑟夫環問題的兩種解法(詳解)【轉】

約瑟夫環問題的兩種解法(詳解)【轉】

題目:

 Josephus有過的故事:39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓。於是決定了自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺。然後下一個重新報數,直到所有人都自殺身亡為止。然而Josephus 和他的朋友並不想遵從,Josephus要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。

對於這個題目大概兩種解法:

一、使用迴圈連結串列模擬全過程

  1. #include<iostream>

  2. using namespace std;

  3. /************約瑟夫問題****************/

  4. typedef struct CLinkList

  5. {

  6. int data;

  7. struct CLinkList *next;

  8. }node;

  9. int main()

  10. {

  11. ///建立迴圈連結串列

  12. node *L,*r,*s;

  13. L = new node;

  14. r =L;

  15. int n = 41,i;

  16. int k = 3;

  17. for(i = 1;i<=n;i++) //尾插法建立連結串列

  18. {

  19. s = new node;

  20. s->data = i;

  21. r->next = s;

  22. r= s;

  23. }

  24. r->next =L->next; //讓最後一個結點指向第一個有資料結點

  25. node *p;

  26. p = L->next;

  27. delete L; //刪除第一個空的結點

  28. ///模擬解決約瑟夫問題

  29. while(p->next != p) //判斷條件:因為最後肯定剩下一個人, 迴圈連結串列的最後一個數據的next還是他本身

  30. {

  31. for(i = 1;i<k-1;i++)

  32. {

  33. p = p->next; //每k個數死一個人

  34. }

  35. cout<<p->next->data<<"->";

  36. p->next = p->next->next; //將該節點從連結串列上刪除。

  37. p = p->next;

  38. }

  39. cout<<p->data<<endl;

  40. return 0;

  41. }

二、公式法

    我們假設這41個人編號是從0開始,從1開始報數,第3個人自殺。

1、最開始我們有這麼多人:

[ 0 1 2 3 4 5 ... 37 38 39 40 ]

2、第一次自殺,則是(3-1)%41=2 這個人自殺,則剩下:

[ 0 1 3 4 5 ... 37 38 39 40 ]

3、然後就是從編號為3%41=3的人開始從1報數,那麼3號就相當於頭,既然是頭為什麼不把它置為0,這樣從它開始就又是與第1,2步一樣的步驟了,只是人數少了一個,這樣不就是遞迴了!!!就可以得到遞迴公式。想法有了就開始做:

4、把第2步中剩下的人編號減去3對映為:

[ -3 -2 0 1 2 ... 34 35 36 37 ]

5、出現負數了,這樣不利於我們計算,既然是環形,37後面報數的應該是-3,-2,那麼把他們加上一個總數(相當於加上360度,得到的還是它)

[ 38 39 0 1 2 3 ... 34 35 36 37 ]

6、這樣就是一個總數為40個人,報數到3殺一個人的遊戲。

這次自殺的是第5步中的(3-1)%40=2號,但是我們想要的是第2步中的編號(也就是最初的編號)

那最初的是多少?對應回去是5;

這個5是如何得到的呢?是(2+3)%41得到的。大家可以把第5步中所有元素對應到第2步都是正確的。

7、接下來是

[ 35 36 37 38 0 1 2... 31 32 33 34 ]

自殺的是(3-1)%39=2,先對應到第5步中是(2+3)%40=5,對應到第2步是(5+3)%41=8。

8、這下看出來規律了把:

我們是正著推的,如果反過來推導,每次剩下的人的編號為f(i),剩一個人的時候編號一定為0,兩個人為0,1,以此類推,則利用以下公式可以推匯出每次剩下的人。

  1. f(1)=0;

  2. f(i)=(f[i-1]+m)%i;(i>1)

程式碼如下:

  1. #include<iostream>

  2. using namespace std;

  3. ///推導公式方法

  4. int yuesefu(int n,int m){

  5.         if(n == 1){

  6.                 return 0; //這裡返回下標,從0開始,只有一個元素就是剩餘的元素0

  7.         }

  8.         else{

  9.                 return (yuesefu(n-1,m) + m) % n; //我們傳入的n是總共多少個數

  10.         }

  11. }

  12. int main(void){

  13.         int a=41,b=3;

  14.         //遞迴求最後一個存活的編號

  15.         //使用從正向思考

  16.         cout<<"最後一個人是"<<yuesefu(a,b)+1<<endl;

  17.         //反向思考,從自殺的最後一人向前

  18.         int result = 2;

  19.         //第一個自殺的人3號

  20.         cout<<3;

  21.         //每次自殺的都是2號,但是不同的2號換算到最初序號所需的的次數是不同的

  22.         //外迴圈是迴圈不同的換算次數

  23.         for(int i = a; i >= 2 ; i-- )

  24.         {

  25.             result = 2;

  26.             //內迴圈是開始換算

  27.             for(int j = i; j <= a; j++)

  28.             {

  29.                 result = (result+b) %j;

  30.             }

  31.                cout<<"->"<<result+1;//0開始變1開始,所以加1

  32.         }

  33.         return 0;

  34. }

結果是一樣的:

--------------------- 本文來自 啊啦啦工業 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/okasy/article/details/79503398?utm_source=copy