[轉]約瑟夫環的了解
原地址CSDN https://blog.csdn.net/okasy/article/details/79503398 轉一哈 嚇死我了一enter沒了
c++有配套習題
題目:
Josephus有過的故事:39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓。於是決定了自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺。然後下一個重新報數,直到所有人都自殺身亡為止。然而Josephus 和他的朋友並不想遵從,Josephus要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。
對於這個題目大概兩種解法:
#include<iostream> using namespace std; /************約瑟夫問題****************/ typedef struct CLinkList { int data; struct CLinkList *next; }node; int main() { ///建立循環鏈表 node *L,*r,*s; L = new node; r =L; int n = 41,i; int k = 3; for(i = 1;i<=n;i++) //尾插法建立鏈表{ s = new node; s->data = i; r->next = s; r= s; } r->next =L->next; //讓最後一個結點指向第一個有數據結點 node *p; p = L->next; delete L; //刪除第一個空的結點 ///模擬解決約瑟夫問題 while(p->next != p) //判斷條件:因為最後肯定剩下一個人, 循環鏈表的最後一個數據的next還是他本身 { for(i = 1;i<k-1;i++) { p = p->next; //每k個數死一個人 } cout<<p->next->data<<"->"; p->next = p->next->next; //將該節點從鏈表上刪除。 p = p->next; } cout<<p->data<<endl; return 0; }
二、公式法
我們假設這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,以此類推,則利用以下公式可以推導出每次剩下的人。
f(1)=0; f(i)=(f[i-1]+m)%i;(i>1)
代碼如下:
#include<iostream> using namespace std; ///推導公式方法 int yuesefu(int n,int m){ if(n == 1){ return 0; //這裏返回下標,從0開始,只有一個元素就是剩余的元素0 } else{ return (yuesefu(n-1,m) + m) % n; //我們傳入的n是總共多少個數 } } int main(void){ int a=41,b=3; //遞歸求最後一個存活的編號 //使用從正向思考 cout<<"最後一個人是"<<yuesefu(a,b)+1<<endl; //反向思考,從自殺的最後一人向前 int result = 2; //第一個自殺的人3號 cout<<3; //每次自殺的都是2號,但是不同的2號換算到最初序號所需的的次數是不同的 //外循環是循環不同的換算次數 for(int i = a; i >= 2 ; i-- ) { result = 2; //內循環是開始換算 for(int j = i; j <= a; j++) { result = (result+b) %j; } cout<<"->"<<result+1;//0開始變1開始,所以加1 } return 0; }
結果是一樣的:
---------------------
作者:okasy
來源:CSDN
原文:https://blog.csdn.net/okasy/article/details/79503398
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
[轉]約瑟夫環的了解