[DS]链表之约瑟夫环(Josephus)问题



原创文章,欢迎转载。转载请注明:转载自 祥的博客

原文链接:https://blog.csdn.net/humanking7/article/details/80786920



算法问题

约瑟夫环(Josephus)问题:
有N个人做成一圈,编号为1至N。从编号为1的人开始传递马铃薯。
M次传递后,持有马铃薯的人退出游戏,圈缩小,然后游戏从退出人后面的人开始,继续进行。
最终留下来的人获胜。

eg:

  • 如果 M = 0 且 N = 5,那么参加游戏的人依次退出,5号获胜。
  • 如果 M = 1 且 N = 5, 那么退出顺序就是 2、4、1、5

用链表实现,依次运用了3种算法,算法效率依次变高。

本例程主要是针对链表的运用,其题目来源于《数据结构与算法分析:C++描述》中的

题3.6,针对于约瑟夫环问题,应该有更好的简单解法,针对于链表的操作,在下认为alg3()应该是最佳选择了,期待算法大神指导。

代码

//算法1
#include <iostream>
#include <list>
using namespace std;

int alg1(int n, int m);
int alg2(int n, int m);
int alg3(int n, int m);

int main()
{
    //Initialization
    int n, m;
    char str[] = "Josephus问题:\n有N个人做成一圈,编号为1至N。从编号为1的人开始传递马铃薯。\nM次传递后,持有马铃薯的人退出游戏,圈缩小,然后游戏从退出人后面的人开始,继续进行。最终留下来的人获胜。\nEg:\n 如果 M = 0 且 N = 5,那么参加游戏的人依次退出,5号获胜。\n如果 M = 1 且 N = 5, 那么退出顺序就是 2、4、1、5";
    cout << str << endl;
    cout << "enter N (# of people) & M (# of passes before elimination):";
    cin >> n >> m;

    int cnt1, cnt2, cnt3;
    cnt1 = alg1(n, m);
    cnt2 = alg2(n, m);
    cnt3 = alg3(n, m);

    cout << "算法1 cnt: " << cnt1 << endl;
    cout << "算法2 cnt: " << cnt2 << endl;
    cout << "算法3 cnt: " << cnt3 << endl;

    return 0;
}

//-----------------------------------
//--------      alg1         --------
//-----------------------------------
int alg1(int n, int m)
{
    int i, j,run_cnt;   
    list <int> L;
    list<int>::iterator iter;
    run_cnt = 0;
    cout << "算法1:" << endl;
    //获得人数圈
    for (i = 1; i <= n; i++)
    {
        L.push_back(i);
    }
    iter = L.begin();


    //传递马铃薯
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < m; j++)
        {
            iter++;
            run_cnt++;//迭代器移动
            if (iter == L.end())
                iter = L.begin();
        }

        cout << "\tdel: " << *iter << endl;
        iter = L.erase(iter);
        if (iter == L.end())
            iter = L.begin();
    }
    cout << endl;
    cout << "\t迭代器移动次数: " << run_cnt << endl << endl;

    return run_cnt;
}

//-----------------------------------
//--------      alg2         --------
//-----------------------------------
int alg2(int n, int m)
{//只运用了单向链表
    int i, j, run_cnt, numLeft,mPrime;
    list <int> L;
    list<int>::iterator iter;
    numLeft = n;//剩余人数
    run_cnt = 0;
    cout << "算法2:" << endl;
    //获得人数圈
    for (i = 1; i <= n; i++)
    {
        L.push_back(i);
    }
    iter = L.begin();


    //传递马铃薯
    for (i = 0; i < n; i++)
    {
        mPrime = m % numLeft;//传递次数是人员人数的余数,这样迭代器可以少移动
        for (j = 0; j < mPrime; j++)
        {
            iter++;
            run_cnt++;//迭代器移动
            if (iter == L.end())
                iter = L.begin();
        }

        cout << "\tdel: " << *iter << endl;
        iter = L.erase(iter);
        numLeft = L.size();//由于删除人员,获取现在人数
        if (iter == L.end())
            iter = L.begin();
    }
    cout << endl;
    cout << "\t迭代器移动次数: " << run_cnt << endl << endl;

    return run_cnt;
}

//-----------------------------------
//--------      alg3         --------
//-----------------------------------
int alg3(int n, int m)
{//体现出双向链表的强大之处
    int i, j, run_cnt, numLeft, mPrime;
    list <int> L;
    list<int>::iterator iter;
    numLeft = n;//剩余人数
    run_cnt = 0;
    cout << "算法3:" << endl;
    //获得人数圈
    for (i = 1; i <= n; i++)
    {
        L.push_back(i);
    }
    iter = L.begin();


    //传递马铃薯
    for (i = 0; i < n; i++)
    {
        mPrime = m % numLeft;//传递次数是人员人数的余数,这样迭代器可以少移动


        if (mPrime <= numLeft / 2) //正向移动迭代器
        {
            cout << "forward";
            for (j = 0; j < mPrime; j++)
            {
                iter++;
                run_cnt++;//迭代器移动
                if (iter == L.end())
                    iter = L.begin();
            }
        }
        else//反向移动迭代器
        {
            cout << "backward";
            for (j = 0; j < (numLeft - mPrime); j++)
            {
                run_cnt++;//迭代器移动
                if (iter == L.begin())
                    iter = --L.end();
                else
                    iter--;
            }
        }

        cout << "\tdel: " << *iter << endl;
        iter = L.erase(iter);
        numLeft = L.size();//由于删除人员,获取现在人数
        if (iter == L.end())
            iter = L.begin();
    }
    cout << endl;
    cout << "\t迭代器移动次数: " << run_cnt << endl << endl;

    return run_cnt;

}

测试结果

人数N传递次数M离场顺序算法1CNT算法2CNT算法3CNT
501,2,3,4,5000
512,4,1,5,3544
523,1,5,2,41065
534,3,5,2,11574
545,1,3,4,22052
551,3,2,5,42543
562,5,1,3,43033
573,2,5,4,13575
584,5,3,1,24053
595,2,3,1,44563

分析

  • 算法1:中规中矩,最差的实现,随着传递次数m的增加,迭代器移动次数增加,算法时间复杂度O(mn)
  • 算法2:节省的部分在于多余的传递部分圈数(当人数numLeft少于传递次数时m时,每次踢人会多传递nLoop = m/numLeft圈,也就是多传递[nLoop*numLeft]次,实际只需要传递mPrime = m%numLeft次),这样会节省相当多的时间尤其是m比较大的时候,算法时间复杂度在下不会求--!,大概是…..
  • 算法3:相较于算法2,最大的优势在于运用了双向链表这一优势,当要迭代器移动的次数( mPrime )大于当前人数的一半时,就反向移动(numLeft - mPrime)次,算法时间复杂度大概是…..

对这些算法进行了简单测试,测试结果如下,希望能从里面推断出算法时间复杂度的一丝端倪,不过还是没有搞明白,希望有大神解答一下。

人数N传递次数M算法1CNT算法2CNT算法3CNT
70001700069996999
7000350024,500,00014,422,2827,519,652
7000550038,500,00013,616,6004,565,344
7000699948,993,0008,705,0265,569,974
7000900063,000,00012,370,4137,211,057

赞赏码New

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值