約瑟夫問題
約瑟夫問題表述如下:假定有N個人圍成一個環,並對每個人進行順序編號,我們設定一個數字M,M<=N,從第一個人開始報數,報到M後這個人出列,剩下的人繼續從1開始報數,報到M出列,依次進行下去,直到所有的人都出列。
模擬約瑟夫全過程:
模擬約瑟夫問題的全過程,記錄出列的人的順序,模擬約瑟夫問題的方法可以用鏈表也可以用數組,在此我們用數組的方式給出模擬約瑟夫問題的過程:
假定共有N個人,我們申請一個大小爲N的數組,這個數組每個位置依次對應着編號爲1、2、3、....、N的人,則數組編號爲 i 的下一個人對應的編號爲 (i+1) % N,這裏我們要注意的是數組的下標是從0開始的,因此數組編號對應的人的編號需+1。
我們設定數組存儲的值爲0/1,首先初始化數組內的值爲0,代表數組內所有人都沒有出列,1代表該位置的人出列了。模擬約瑟夫問題的過程就是循環遍歷數組:初始化一個標誌位flag=0,從第0個人開始,每遍歷一個0,flag+1,遍歷到1,flag不變,直到flag=M時,將當前編號的人出列,即當前位置的數組置1,而後flag歸零,循環繼續,推出循環的條件時數組內0的數目爲0。
給出約瑟夫問題C++代碼:
class Solution {
public:
void Josephus(int N,int M){
//初始化一個約瑟夫環數組,初始化0
int* circleArray = (int*)calloc(sizeof(int),N);
int flag = 0;//記錄約瑟夫每個子過程的計數;
int index = 0;//當前遍歷的位置
int alive = N;//剩餘未出列的人數,用於循環判斷
//模擬約瑟夫過程
while(alive>0){
flag = flag + (1-circleArray[index]);//index位置沒有出列,flag+1,index出列,flag+0
//達到了M,當前報數的人出列
if(flag == M){
circleArray[index] = 1;//出列
flag = 0;//重新計數
alive--;//剩餘人數-1
//出列的人對應的真實編號未index+1
printf("%d", index+1);
}
//繼續遍歷
index = (index +1) % count;
}
free(circleArray);
}
};
模擬約瑟夫問題的全過程是一個循環遍歷的過程,隨着N和M的增大,算法複雜度爲O(NM),一種典型的約瑟夫問題不要求把出隊的全過程都給出結果,只需要給出最後一個出列的人即可。
假定1、2、3、...、N的人,編號爲0 .... N-1,設第k=M%N個人(編號爲k-1)出列後剩下的N-1個人組成了一個新的約瑟夫環,編號分別爲:
k、k+1、k+2、...、N-2、N-1、0、1、2、...、k-2 (1)
對這個約瑟夫環中的數重新從0開始編號:
k =》0 k+1 =》1 k+2=》2 ....... k-2 =》N-2 (2)
假定當前N-1個人對應對應的問題結果是x,則根據公式(2),如果箭頭右邊爲x,對應左邊的值爲k+x,因此結果N個人的情況的結果爲(x+k)%N = (x+M%N)%N,化簡上式爲:
(x+M)%N;
由此我們就得到了遞推公式:
f[1] = 0; //當一個人的時候,出隊人員編號爲0
f[n] = (f[n-1] + M)%N //m表示每次數到該數的人出列,n表示當前序列的總人數
class Solution {
public:
int LastRemaining_Solution(unsigned int n, unsigned int m){
if(n==0)
return -1;
if(n==1)
return 0;
else
return (LastRemaining_Solution(n-1,m)+m)%n;
}
};