C語言用陣列1. 簡單約瑟夫環問題: N個人,編號從1~N圍成一圈,輸入一個數T,從1號開始報數,報到T的人出圈;下一人又從1開始報數,下一個報到T的人出圈,輸出出圈順序。 考慮問實現約瑟夫環問題
1. 簡單約瑟夫環問題:
N個人,編號從1~N圍成一圈,輸入一個數T,從1號開始報數,報到T的人出圈;下一人又從1開始報數,下一個報到T的人出圈,輸出出圈順序。
考慮問題:
報到T的人出圈,怎麼表示出圈?要麼刪除對應的標號,其他的標號前移(如果是陣列結構,要依次移動元素,效率低;如果是連結串列,刪除結點也比較麻煩);要麼設定狀態標誌位(宣告一個狀態陣列status[N],status[i] ==0時表示未出圈,出圈時將對應第i號人的status置為出圈的次數;即status[i]=count)
解決了表示出圈的問題,那如何出圈?如果報數T大於總人數N時,需要取餘才能將報號的範圍限制在標號為0~N-1中。
流程呢?
while(出圈人數<總人數)
{
從start下標依次查詢status為0的下標(需要儲存start下標)
計數
判斷計數是否等於T
若計數等於T
出圈,更新對應下標的status,出圈人數加1
}
- void joseph()
- {
- int T,N;
- scanf("%d%d",&T,&N);
-
//T為出列週期,N為N個人,即環的元素個數
- int status[1000];
- memset(status,0,sizeof(status));
- int start,end;
- start = -1;
- int count = 0;
- while(count<N)
- {
- int i=0;
- while(1)
- {
- start = (start+1) % N;
- if(status[start] == 0)
- {
-
i++;
- }
- if(i == T)
- {
- ++count;
- status[start]=count;
- break;
- }
- }
- }
- for(int k=0;k<N;k++)
- printf("%d",status[k]);
- }
2. 複雜約瑟夫環
1~N個人構成一圈,每個人手中有個號碼,讀入一個數T,從第一個人開始報數,報到T的人出圈;下一個人接著從1報數,報到出圈人手裡的號碼的人出圈,依次進行。
與簡單約瑟夫環不同的是T變了;每出圈一個,要更新對應的T;
- void rand_joseph()
- {
- //如果約瑟夫環中的數是使用者輸入的資料時
- int N,data[1000],status[1000];
- memset(data,0,sizeof(data));
- memset(status,0,sizeof(status));
- int i=0;
- scanf("%d",&N);
- while(i<N)
- {
- scanf("%d",&data[i]);
- i++;
- }
- int T;
- scanf("%d",&T);//第一個週期由使用者輸入,其他的是出隊使用者對應的值
- int count = 0;
- int index = -1;
- while(count<N)
- {
- int i=0;
- while(1)
- {
- index = (index+1)%N;
- if(status[index] == 0 )
- {
- i++;
- }
- if(i == T)
- {
- ++count;
- status[index] = count;
- T = data[index];
- break;
- }
- }
- }
- for(int i=1;i<=N;i++)
- {
- for(int j=0;j<N;j++)
- if(status[j] == i)
- printf("第%d個出列的是第%d人\n",i,j+1);
- }
- }
3. 如果要求最後出圈的人的編號,還有簡單的方法:
無論是用連結串列實現還是用陣列實現都有一個共同點:要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達O(nm),當n,m非常大(例如上百萬,上千萬)的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最後的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規,實施一點數學策略。
為了討論方便,先把問題稍微改變一下,並不影響原意:
問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。
我們知道第一個人(編號一定是m%n-1) 出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m%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
k-1 --> n-1
變換後就完完全全成為了(n-1)個人報數的子問題,假如我們知道這個子問題的解:例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k)%n
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式:
令f[i]表示i個人玩遊戲報m退出最後勝利者的編號,最後的結果自然是f[n]
遞推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f[i]的數值,最後結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1
由於是逐級遞推,不需要儲存每個f[i],程式也是異常簡單:
- #include <stdio.h>
- int main()
- {
- int n, m, i, s = 0;
- printf ("N M = ");
- scanf("%d%d", &n, &m);
- for (i = 2; i <= n; i++)
- {
- s = (s + m) % i;
- }
- printf ("\nThe winner is %d\n", s+1);
- }