約瑟夫問題Josephus problem

約瑟夫問題:經典算法

已知n個人(以編號1,2,3…n分別表示)圍坐在一張圓桌周圍。從編號爲k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。

例如:n = 9, k = 1, m = 5
【解答】出局人的順序爲5, 1, 7, 4, 3, 6, 9, 2, 8。


/*************************************************
//約瑟夫環,時間複雜度O(n*m)
**************************************************/
# include <iostream>
using namespace std;

int main()
{
    int n=10, m=2,k=1;//n是人數(編號1,2,……,x),m是出列號,k是起始人編號,在這裏定義爲1,因爲都是從第一個人開始

    int a[10000];
    while(cin>>n)
    {
        if(n==0) break;
        cin>>m;
        //number是已經出列的人數,j用來是報數值即j=1,2···m
        int j=0, number=0;
        for (int i=1;i<=n;i++)
        {
            a[i]=1;
        }

        while (number<n) 
        {
            for (int i=1;i<=n;i++) 
            {
                if (a[i]==1) 
                {
                    j++;
                    if (j==m) //滿足出列號
                    {
                        a[i]=0;
                        //格式輸出
                        if (number<n-1)
                        {
                            cout<<i<<",";
                        }
                        else
                            cout<<i<<endl;

                        j=0;
                        number++;
                    }
                }
            }//end for i
        }//end while 
    }//end while cin

}

代碼出自:http://blog.csdn.net/iamyina/article/details/4126054
還有一種使用循環鏈表模擬執行流程的解法,請參考:http://blog.csdn.net/eagleest/article/details/8091351


約瑟夫問題:數學推理

本文地址:http://hi.baidu.com/anywei/item/294351b5f432f144ba0e12f2

約瑟夫環是一個數學的應用問題:已知n個人(以編號1,2,3…n分別表示)圍坐在一張圓桌周圍;從編號爲k的人開始報數,數到m的那個人出列;他的下一個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。

前幾天,在一篇文章中得知了約瑟夫環的問題。然後,就涉及瞭解決辦法。這個問題,在許多計算機或者關於數據結構的書中都有提及,而其中的解決辦法便是使用循環鏈表——無論這個循環鏈表是使用指針還是數組實現,模擬約瑟夫環的進行,最後得到解決方案。具體方法肯定早有某人披露。但是,令人深感奇妙的還是這個問題的數學解決。在不統計解決過程,即不統計每次都需要出列哪個序號時,就可以應用數學遞推公式,直接得出最後剩下的那個人的序號。

使用數學方式,奇妙是很奇妙,也確實讓人感受到數學的魅力。可是,在我查找如何推導出這個公式的時候,網上的解決方案都是在我覺得很關鍵的地方一筆帶過。“衆所周知”,“顯而易見”,“很簡單的”……諸如此類。我悲劇的鬱悶了。所以在大家都陶醉在“數學真奇妙啊”的飄飄然中時,我惴惴不安。因爲我只感受到了數學帶給我的困惑。

對着網上的推導思考良久,終於茅塞頓開。所以趕緊寫下來,以防以後再次悲劇和鬱悶。並且希望以後看到這篇文章的人,能和我一樣擺脫困惑。

先總結一下約瑟夫環的遞推公式:

f[1]=0; f[i]=(f[i-1]+m)%i; (i>1)
f[1]=1; f[i]=(f[i-1]+m)%i (i>1); if(f[i]==0) f[i]=i;
P(1, m, k)=1 (i = 1); P(i, m, k)=[P(i - 1, m, k ) + m - 1] % i + 1 (i > 1, 此處先減1是爲了讓模i的值不爲0)

那麼這三個公式有什麼不同?

首先可以肯定的是這三個公式都正確。公式1,得到的是以0~n-1標註的最終序號;公式2,3得到的就是正常的1~n的序號;並且公式2和公式3其實是一個意思。下面我們就分別推導三個公式,並且推導的過程中,你也就能明白這三個公式的共同點和不同點。

公式1的推導:——————————

給出一個序列,從0~n-1編號。其中,k代表出列的序號的下一個,即k-1出列。

a 0, 1, …, k-1, k, k+1, …, n-1

那麼,出列的序號是(m-1)%n,k=m%n(這個可真的是顯而易見)。出列k-1後,序列變爲

b 0, 1, …, k-2, k, k+1, …, n-1

然後,我們繼續從n-1後延長這個序列,可以得到

c` 0, 1, …, k-2, k, k+1, …, n-1, n, n+1, …, n+k-2

我們取從k開始直到n+k-2這段序列。其實這段序列可以看作將序列b的0~k-2段移到了b序列的後面。這樣,得到一個新的序列

c k, k+1, …, n-1, n, n+1, …, n+k-2

好了,整個序列c都減除一個k,得到

d 0, 1, …, n-2

c序列中的n-1, n, n+1都減除個k是什麼?這個不需要關心,反正c序列是連續的,我們知道了頭和尾,就能知道d序列是什麼樣的。

這樣你看,從序列a到序列d,就是一個n序列到n-1序列的變化,約瑟夫環可以通過遞推來獲得最終結果。ok,繼續向下。

剩下的就是根據n-1序列遞推到n序列。假設在n-1序列中,也就是序列d中,我們知道了最終剩下的一個序號是x,那麼如果知道了x轉換到序列a中的編號x`,不就是知道了最終的結果了麼?

下面我們就開始推導出序列a中x的序號是什麼。

d->c,這個變換很容易,就是x+k;

c->b,這個變換是網上大家都一帶而過的,也是令我鬱悶的一個關鍵點。從b->c,其實就是0~k-2這段序列轉換爲n~n+k-2這段序列,那麼再翻轉回去,簡單的就是%n,即(x+k)%n。%n以後,k~n-1這段序列值不會發生變化,而n~n+k-2這段序列則變成了0~k-2;這兩段序列合起來,就是序列b。

於是乎,我們就知道了,x=(x+k)%n。並且,k=m%n,所以x=(x+m%n)%n=(x+m)%n。公式1就出來了:f[i]=(f[i-1]+m)%i。當然,i=1就是特殊情況了,f[1]=0。這裏還有一個小問題。也許你會迷惑爲什麼x`=(x+m%n)%n=(x+m)%n中的%n變成公式中f[i]=(f[i-1]+m)%i中的%i?其實這個稍微想想就能明瞭。我們%n就是爲了從序列c轉換到序列b——這是在n-1序列轉換成n序列時%n;那麼從n-2轉換到n-1呢?不是要%(n-1)了嗎?所以這個值是變量,不是常量。

好了,這個最後需要注意的就是從一開始,我們將n序列從0~n-1編號,所以依據公式1得出的序號是基於0開始的

這樣子就有了約瑟夫環的變形:就是一共n個人,查到m的人出圈,求最後圈裏的人是幾號。

int fun(int n, int m)
{
    int i, r = 0;
    for (i = 2; i <= n; i++)
        r = (r + m) % i;
    return r+1;
}

這個算法的時間複雜度爲O(n),相對於模擬算法已經有了很大的提高。算n,m等於一百萬,一千萬的情況不是問題了。可見,適當地運用數學策略,不僅可以讓編程變得簡單,而且往往會成倍地提高算法執行效率。


約瑟夫問題:線段樹

本段出自:http://blog.csdn.net/acceptedxukai/article/details/6926431,原文解釋更加詳細,推薦閱讀

設計一個算法,時間複雜度要求O(nlogn)時間,使給定的整數n和m,輸出(n,m)-Josephus排列。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <ctime>
using namespace std;
#define LL long long
const int N = 10005;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

int sum[N<<2];
int tree[N<<2][2];

void PushUp(int rt)
{
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}

void build(int l,int r,int rt)
{
    tree[rt][0] = l;
    tree[rt][1] = r;
    if(l == r)
    {
        sum[rt] = 1;
        return;
    }
    int m = (l+r)>>1;
    build(lson);
    build(rson);
    PushUp(rt);
}

int update(int p,int rt)
{
    sum[rt] --;
    if(tree[rt][0] == tree[rt][1])
    {
        sum[rt] = 0;
        return tree[rt][0];//從絕對位置剔除
    }
    if(p <= sum[rt<<1]) return update(p,rt<<1);
    else return update(p-sum[rt<<1],rt<<1|1);
    PushUp(rt);
}

int main()
{

    int n,m;
    while(~scanf("%d %d",&n,&m))
    {
        build(1,n,1);
        for(int i = 1 ; i <= 15 ;  i ++) cout<<sum[i]<<" "<<tree[i][0]<<" "<<tree[i][1]<<endl;
        int pos = 1;
        int seq = 1;
        for(int i = 0 ; i < n ; i++)
        {
            seq = (seq + m - 1) % sum[1];//seq 只是相對位置
            if(seq == 0) seq = sum[1];
            cout<<"seq = "<<seq<<"; ";
            pos = update(seq,1);
            cout<<pos<<" ";
        }
    }
    return 0;
}

約瑟夫問題:遞歸算法

本段地址:http://www.cnblogs.com/yangyh/archive/2011/10/30/2229517.html
假設下標從0開始,0,1,2 .. m-1共m個人,從1開始報數,報到k則此人從環出退出,問最後剩下的一個人的編號是多少?

現在假設m=10

0 1 2 3 4 5 6 7 8 9 k=3

第一個人出列後的序列爲:

0 1 3 4 5 6 7 8 9

即:

3 4 5 6 7 8 9 0 1(*)

我們把該式轉化爲:

0 1 2 3 4 5 6 7 8 (**)

則你會發現: ((*)+3)%10則轉化爲()式了

也就是說,我們求出9個人中第9次出環的編號,最後進行上面的轉換就能得到10個人第10次出環的編號了

設f(m,k,i)爲m個人的環,報數爲k,第i個人出環的編號,則f(10,3,10)是我們要的結果

當i=1時, f(m,k,i) = (m+k-1)%m

當i!=1時, f(m,k,i)= ( f(m-1,k,i-1)+k )%m

所以程序如下:

int fun(int m,int k,int i){

    if(i==1)
        return (m+k-1)%m;
    else
        return (fun(m-1,k,i-1)+k)%m;

}
int main(int argc, char* argv[])
{

    for(int i=1;i<=10;i++)
        printf("第%2d次出環:%2d\n",i,fun(10,3,i));
    return 0;
}

執行結果:

第 1次出環: 2
第 2次出環: 5
第 3次出環: 8
第 4次出環: 1
第 5次出環: 6
第 6次出環: 0
第 7次出環: 7
第 8次出環: 4
第 9次出環: 9
第10次出環: 3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章