/*
  Name: 约瑟夫环问题算法集锦
  Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处
  Author: goal00001111搜集整理
  Date: 03-12-08 18:14
  Description:
有编号从1到N的N个人坐成一圈报数,报到M的人出局,下一位再从1开始, 如此持续,
直止剩下一位为止,报告此人的编号X。
输入N,M,求出X。
共搜集整理了7类10种算法,对于初学者和算法爱好者来说——看了绝对值!
*/
#include<iostream>
#include <time.h>

using namespace std;

int Fun_1(int n, int m);
int Fun_2(int n, int m);
int Fun_3(int n, int m);
int Fun_4(int n, int m);
int Fun_5(int n, int m);
int Fun_6(int n, int m);
int Fun_7(int n, int m);
int Fun_8(int n, int m);
int Fun_9(int n, int m);
int Fun_10(int n, int m);

int main(int argc, char* argv[])
{
    int n, m;
    //cout << "Input max, step: ";
//    cin >> n >> m;
    time_t startTime;
    time_t endTime;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_1(i, 8);
    time(&endTime);
    cout << "No.1 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_2(i, 8);
    time(&endTime);
    cout << "No.2 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_3(i, 8);
    time(&endTime);
    cout << "No.3 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_4(i, 8);
    time(&endTime);
    cout << "No.4 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_5(i, 8);
    time(&endTime);
    cout << "No.5 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_6(i, 8);
    time(&endTime);
    cout << "No.6 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_7(i, 8);
    time(&endTime);
    cout << "No.7 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_8(i, 8);
    time(&endTime);
    cout << "No.8 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_9(i, 8);
    time(&endTime);
    cout << "No.9 time = " << difftime(endTime, startTime) << endl;
   
    time(&startTime);
    for (int i=10; i<11; i++)
       cout << Fun_10(i, 8);
    time(&endTime);
    cout << "No.10 time = " << difftime(endTime, startTime) << endl;
   
    system("pause");
    return 0;
}

//采用了设置个人编号和计数器方法,屏蔽已经出圈人的位置
int Fun_1(int n, int m)
{
    int *a = new int[n]; //设置一个数组用来存储n个人的编号
   
    for (int i=0; i<n; i++) //注意C语言中的数组下标从0开始
    {
        a[i] = i + 1;
    }
    int s = 1; //累计出圈的人数,设初值为1,那最后一个就不用出圈了
    int count = 0; //计数器,数到m就归零
   
    while(s < n)
    {
        for (int i=0; i<n; i++)//只要还有超过1个人在圈里,就不断的遍历数组
        {
            if (a[i] == 0) //编号为0,表示此人已经出圈
                continue;
            count++;    //报一次数
            if (count == m) //报数为m的那个人出圈
            {
                a[i] = 0; //设此位置编号为0,表示此人已经出圈
                ++s; //出圈人增加一个
                count = 0; //计数器归零
            }
        }
    }
    int pos;
    for (int i=0; i<n; i++) //遍历数组,看看还有谁没有出圈,找的就是他
    {
        if (a[i] != 0)
        {
            pos = a[i];
            break;
        }
    }
    delete []a;
    return pos;
}

//采用了计数器方法,也使用了数组,但数组中存储的不是个人的编号,而是是否出圈的信息
int Fun_2(int n, int m)
{
    int *a = new int[n]; //设置一个数组用来存储n个人是否出圈,1表示在圈内,0表示在圈外
   
    for (int i=0; i<n; i++) //首先设置所有人都在圈内
    {
        a[i] = 1;
    }
    int s = 1; //累计出圈的人数,设初值为1,那最后一个就不用出圈了
    int count = 0; //计数器,数到m就归零
   
    while(s < n)
    {
        for (int i=0; i<n; i++)//只要还有超过1个人在圈里,就不断的遍历数组
        {
            count += a[i]; //若a[i]=0,就直接跳过了
            if (count == m) //报数为m的那个人出圈
            {
                a[i] = 0; //设此位置编号为0,表示此人已经出圈
                ++s; //出圈人增加一个
                count = 0; //计数器归零
            }
        }
    }
    int pos;
    for (int i=0; i<n; i++) //遍历数组,看看还有谁没有出圈,找的就是他
    {
        if (a[i] == 1)
        {
            pos = i + 1;
            break;
        }
    }
    delete []a;
    return pos;
}

//采用了数组队列的方法,每出圈一个人,就从队列中删除
int Fun_3(int n, int m)
{
    int *a = new int[n]; //设置一个数组用来存储n个人的编号
   
    for (int i=0; i<n; i++) //注意C语言中的数组下标从0开始
    {
        a[i] = i + 1;
    }
    int front=0, rear=n-1;  //对头front指向第一个元素,队尾rear指向最后一个元素
    int count = 0; //计数器,数到m就归零
   
    while ((front) != (rear))//当队列元素还有一个,注意这里不需要队列为空,而是留一个元素
    {
        count++;    //报一次数
        if (count == m) //报数为m的那个人出圈
        {
            front = ++front % n; //将该元素从队列中删除
            count = 0; //计数器归零
        } 
        else //否则把对头的元素放到队尾去
        {
            rear = ++rear % n;//队尾前移,以存储从对头转来的元素
            a[rear] = a[front];
            front = ++front % n; //对头前移,指向新的元素
        }           
    }
    delete []a;
    return a[front]; 
}

//很巧妙的方法,不用屏蔽位置, 需要计数器,采用数组,但数组中存储的不是本人的编号,而是是下一个人的编号 
int Fun_4(int n, int m)
{
    int *a = new int[n]; //设置一个数组用来存储n个人是否出圈,1表示在圈内,0表示在圈外
   
    for (int i=n-2; i>=0; i--) //存储下一个人的编号
    {
        a[i] = i + 1;
    }
    a[n-1] = 0; //最后一个人的下一位是第一个人
    int s = 0; //累计出圈的人数,设初值为1,那最后一个就不用出圈了
    int count = 0; //计数器
    int pos; //当前位置
    int nextPos = 0;//下一位置
   
    while(s < n)
    {
        pos = nextPos;//获取当前位置
        nextPos = a[pos];//获取当前位置的下一位置
        count++;
        if (count == m)//改变当前位置的下一位置
        {
            count = 0; //计数器归零
            s++; //累计出圈人
            a[pos] = a[nextPos];
        }  
    }
   
    delete []a;
   
    if (nextPos != 0)
        return nextPos;
    else
        return n;
}

//很巧妙的方法,Fun_4()的一个改进
int Fun_5(int n, int m)
{
   int *a = new int[n]; //设置一个数组用来存储n个人是否出圈,1表示在圈内,0表示在圈外
   
    for (int i=n-2; i>=0; i--) //存储下一个人的编号
    {
        a[i] = i + 1;
    }
    a[n-1] = 0; //最后一个人的下一位是第一个人
    int pos = n - 1; //当前位置
   
    do
    {
        for (int i=0; i<m-1; i++)
            pos = a[pos]; 
        a[pos] = a[a[pos]];
    } while (pos != a[pos]);
   
    delete []a;
   
    return pos + 1;
}

//很巧妙的方法,直接确定出列的人,并不断改变数组的长度
int Fun_6(int n, int m)
{
    int *a = new int[n]; //设置一个数组用来存储n个人的编号
   
    for (int i=0; i<n; i++) //注意C语言中的数组下标从0开始
    {
        a[i] = i + 1;
    }
   
    int pos = (m - 1) % n;//这是第一个要出列的人的下标
   
    while (n > 1) // 当队列中还有不止一个人的时候,要就进行出列的动作
    {
        for (int i=pos; i<n-1; i++) //这个家伙走了以后,后面的人应该依次顶上来
        {
            a[i] = a[i+1];
        }
        n--;    // 并且整个队列的人少了一个,也就是长度要减掉一
        pos = (pos + m - 1) % n; //这是下一个要出列的人
    }
    pos = a[0];
    delete []a;
    return pos;
}

//采用循环链表结构,来进行删除操作
int Fun_7(int n, int m)
{
    struct node
    {
        int data;
        struct node *next;
    };
    struct node * head, *s, *temp;
    head = new struct node;//存储第一个人的序号信息
    head->data = 1;
    temp = head->next = head;
    for (int i=2; i<=n; i++)//存储所有人的序号信息
    {
        s = new struct node;
        s->data = i;
        s->next = head;
        temp->next = s;
        temp = s;
    }
    s = head;
    while (s->next != s)
    {
        for (int i=1; i<m; i++)//先数m-1个数
        {
            temp = s;
            s = s->next;
        }
        //把数到m的人从链表中删除
        temp->next = s->next;
        delete s;
        s = temp->next;
    }
    int pos = s->data; //最后一个人
    delete s;
    return pos;
}

/*
数学方法:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然
是f[n],因为实际生活中编号总是从1开始,我们输出f[n]+1
递推公式
f[1]=0;
f=(f[i-1]+m)%i;   (i>1)
*/
// 递归算法
int F(int n, int m)
{
    if (n == 1)
      return 0;  
    return (F(n-1, m) + m) % n;
}
int Fun_8(int n, int m)
{    
    return F(n, m) + 1;
}

//非递归算法
int Fun_9(int n, int m)
{
    int r = 0;
    for (int i = 2; i <= n; i++)
        r = (r + m) % i;
    return r + 1;
}

int Fun_10(int n, int m)
{
    int p, i = 1;
    while( i < n )
    {
        p = i * m;
        while (p > n)
            p = p - n + (p - n - 1)/(m - 1);
        i++;
    }
    p = n * m;
    while (p > n)
        p = p - n + (p - n - 1)/(m - 1);
       
    return p;
}


Posted on 2008-12-03 18:22 梦想飞扬 阅读(491) 评论(1)  编辑 收藏 引用

Feedback

# re: 约瑟夫环问题算法集锦[未登录]  回复  更多评论   

2008-12-04 12:19 by 908971
受教了

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理