数据结构与算法分析(1)——约瑟夫问题

0 问题描述

  据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。[1]

  简单地说就是n个人围成一个圈,假设编号为0~n-1,报数范围为1~m。从第0个人开始报数,每报到m,这个人就出局。接着下一个人从1开始重新报数...直到剩下最后一个人。

1 问题解法

  该问题常用解法有链表法和数组法,其他的比如公式法和递归法以后补充。个人觉得链表法最容易理解,可能是先入为主的心理,嘿嘿嘿,下面就简单说一下。

1.1 链表法

  首先建立长度为n的循环链表,然后头结点开始报数,每第m个数删除一个结点,直到只剩下一个结点。

#include <iostream>

using namespace std;

struct ListNode
{
    int val;
    struct ListNode* next;
};

/******创建循环链表******/
ListNode* creatCirListNode(int n)
{
    //创建头结点
    ListNode *head,*node,*newnode;
    node = new ListNode;
    node->val = 0;
    node->next = NULL;
    head = node;
    //创建链表
    for (int i = 1; i < n; i++)
    {
        newnode = new ListNode;
        newnode->val = i;
        node->next = newnode;
        node = newnode;
    }
    //将最后一个结点指向头结点,形成循环链表
    node->next = head;
    return head;
}

/******约瑟夫问题求解******/
int josephSolution(ListNode* head,int m)
{
    if (NULL == head)
    {
        return -1;
    }
    ListNode* pNode;                        //用来遍历结点
    pNode = head;        
    while (pNode->next != pNode)            //循环终止条件是只剩下一个结点
    {
        for (int i = 0; i < m-1; i++)        //报数
        {
            pNode = pNode->next;
        }
        cout << pNode->next->val << "->";
        pNode->next = pNode->next->next;    //删除链表结点
    }
    return pNode->val;
}

int main()
{
    int n = 41;
    int m = 3;
    int result;
    ListNode* pHead;
    pHead = creatCirListNode(n);
    result = josephSolution(pHead, m);
    cout << result << endl;
    system("pause");
    return 0;
}

  运行结果:

 

 

1.2 数组法

  数组法的关键是使用求余的方法循环遍历数组i=(i+1)%n,其中i是数组下标即每个人的标号,n是总人数。当i的值等于n-1时,即遍历至数组最后一个元素,通过求余可使i=0。

  借牛客网上的一个题,直接上代码:

int LastRemaining_Solution(int n, int m)
    {
       if(n==0)
       {
           return -1;
       }
       vector<int> childnum(n);    //初始化n个元素,每个元素值为0
       int count=0;                //计出局人数
       int step = 0;

    /****循环遍历数组****/
for(int i=0;;i=(i+1)%n) { if(childnum[i]==-1)   //判断是否出局,是则continue,跳过这个元素 continue; step++;          //否则报数 if(step == m)       //判断是否报数到m,是则将其置-1,否则继续报数 { childnum[i]=-1;    step = 0;       //将计数器置0 count++; //记录离开了多少小朋友 } if(count == n)      //出局n个人,则游戏结束,返回最后一个的坐标 { return i; break; } } return -1; }

 2 结语

  没想到人生第一篇博客先给了约瑟夫问题~hahahahahaha~

  写博客的目的主要是方便以后查看,希望能一直坚持下去吧,为自己还没开始的程序员生涯留下一点痕迹~

posted @ 2019-05-14 17:01  benniaozj  阅读(1133)  评论(0编辑  收藏  举报