約瑟夫環問題的原來描述爲,設有編號爲1,2,……,n的n(n>0)我的圍成一個圈,從第1我的開始報數,報到m時中止報數,報m的人出圈,再從他的下一我的起從新報數,報到m時中止報數,報m的出圈,……,如此下去,直到全部人所有出圈爲止。當任意給定n和m後,設計算法求n我的出圈的次序。 稍微簡化一下。 算法
問題描述:n我的(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。 spa
思路:容易想到的就是用環鏈表來作,構建一個環鏈表,每一個結點的編號爲0, 1, ...... n-1。每次從當前位置向前移動m-1步,而後刪除這個結點。最後剩下的結點就是勝利者。給出兩種方法實現,一種是自定義鏈表操做,另外一種用是STL庫的單鏈表。不難發現,用STL庫能夠提升編寫速度。 設計
struct ListNode { int num;//編號 ListNode *next; }; //自定義鏈表實現 int JosephusProblem_Solution1(int n, int m) { if(n < 1 || m <1)return -1; ListNode *pHead = new ListNode();//頭結點 ListNode *pCurrentNode = pHead;//當前結點 ListNode *pLastNode = NULL;//前一個結點 unsigned i; //構造環鏈表 for(i = 1; i<n ; i++) { pCurrentNode->next = new ListNode(i); pCurrentNode = pCurrentNode->next; } pCurrentNode->next = pHead; //循環遍歷 pLastNode = pCurrentNode; pCurrentNode = pHead; while(pCurrentNode->next != pCurrentNode) { //前進m-1步 for(i = 0; i < m-1; i++) { pLastNode = pCurrentNode; pCurrentNode->next = pLastNode; } //刪除報m-1的數 pLastNode->next = pCurrentNode->next; delete pCurrentNode; pCurrentNode = pLastNode->next; } //釋放空間 int result = pCurrentNode->num; delete pCurrentNode; return result; }
//使用標準庫STL int JosephusProblem_Solution2(int n,int m) { if(n<1||m<1)return -1; list<int> listInt; unsigned i; for(i=0;i<n;i++) listInt.push_back(i); list<int>::iterator iterCurrent = listInt.begin(); while(listInt.size()>1) { //前進m-1步 for(i=0;i<m-1;i++) { if(++iterCurrent == listInt.end()) iterCurrent = listInt.begin(); } //臨時保存刪除的結點 list<int>::iterator iterDel = iterCurrent; if(++iterCurrent == listInt.end()) iterCurrent = listInt.begin(); //刪除結點 listInt.erase(iterDel); } return *iterCurrent; }上述方法的效率很低,其時間複雜度爲O(mn)。當m和n很大時,很難在短期內得出結果。不過好處就是能夠給出n我的出圈的次序。
下面利用數學推導,若是能得出一個通式,就能夠利用遞歸、循環等手段解決。下面給出推導的過程: code
(1)第一個被刪除的數爲(m-1)%n; 遞歸
(2)第二論的開始數字爲k,那麼這n-1個數構成的約瑟夫環爲k,k+1,k+2,...k-3,k-2作一個簡單映射。 數學
(p(x)=(x-k)%n) it
k--->0 io
k+1--->1 ast
k+2--->2 class
---
---
k-2--->n-2
這是一個n-1我的的問題,若是能從n-1我的的問題的解退出n我的問題的解,從而獲得一個遞推公式,那麼問題就解決了。假如咱們已經知道了n-1我的時,最後勝利者的編號爲x,利用映射關係逆推,就能夠得出n我的時,勝利者的編號爲(x+k)%n。其中k=m%n。代入(x+k)%n<=>(x+(m%n))%n<=>(x%n + (m%n)%n)%n<=> (x%n+m%n)%n <=> (x+m)%n
(3)第二個被刪除的數爲(m-1)%n-1
(4)假設第三輪的開始數字爲o,那這n-2個數構成的約瑟夫環爲o,o+1,o+2,...,o-3,o-2。繼續作映射
p(x)=(y-o)%(n-2)
o--->0
o+1--->1
o+2--->2
---
---
o-2--->n-3
這是一個n-2我的的問題。假設最後勝利者爲y,那麼n-1我的時,勝利者爲(y+o)%(n-1),其中o等於m%(n-1)。代入可得(y+m)%(n-1)
要獲得n-1我的問題的解,只須要獲得n-2我的問題的解,倒退下去。只有一我的時,勝利者就是編號0.小面給出遞推式:
f(1)=0;
f(i)=(f[i-1]+m)%i;(i>1)
有了遞推公式,實現就很是簡單了,給出循環的兩種實現。再次代表使用標準庫的便捷性。
int JosephusProblem_Solution3(int n, int m) { if(n<1||m<1)return -1; int *f = new int[n+1]; f[0]=f[1]=0; for(unsigned i=2;i<n;i++) f[i]=(f[i-1]+m)%i;//按遞推公式進行計算 int return = f[n]; delete []f; return result; }
int JosephusProblem_Solution4(int n, int m) { if(n<1||m<1)return -1; vector<int> f(n+1,0); for(unsigned i = 2;i<=n;i++) f[i]=(f[i-1]+m)%i; return f[n]; }