Josephus问题分析与实现

Josephus问题:编号为1,2,3,……n的n个人按顺时针方向围坐在一张圆桌周围,每人持有一个密码(正整数)。任选一个正整数作为报数上限值值m,从第一个人开始按顺时针方向自1开始报数,报到m的人出列,并将他的密码作为新的密码,重新从1开始报数,依次出列,直至所有人全部出列为止!

问题分析:

围成一圆桌的人可以认为是一个循环链表,每个链表节点有两个数据成员,一个是成员本身的序号,另一个是这个成员本身的密码。结构体的定义如下:

typedef struct NodeType
{
    int id;                   //成员本身序号
    int password;             //成员持有的密码
    struct NodeType *next;
}NodeType;
创建循环链表的代码如下:
void CreatList(NodeType **phead, int n)
{
    int i = 0;
    int ipassword = 0;
    NodeType *pnew = NULL;
    NodeType *pcur = NULL;
    for(i = 1; i <= n; i++)
    {
        printf("请输入第%d个人的密码:", i);
        scanf("%d", &ipassword);
        pnew = GetNode(i, ipassword);
        if(*phead == NULL)
        {
            *phead = pcur = pnew;
            pcur->next = *phead;
        }
        else{
            pnew->next = pcur->next;
            pcur->next = pnew;
            pcur = pnew;
        }
    }
    printf("单向链表创建完成!\n");
}
/*创建一个节点*/
NodeType *GetNode(int id, int ipassword)
{
    NodeType *pnew = NULL;
    pnew = (NodeType *)malloc(sizeof(NodeType));
    if(!pnew)
    {
        printf("malloc error!\n");
        exit(-1);
    }
    pnew->id = id;
    pnew->password = ipassword;
    pnew->next = NULL;
    return pnew;
}

创建循环链表使用尾差法,CreatList函数中,实参pphead为二级指针,使用二级指针是因为在main函数(后面可以看到)中已经定义好了phead,如果传参数为phead,则在创建完成之后,链表的头指针往后的节点都在实参所申请的内存空间当中,即就是实参会将形参的内容进行复制,但同时也会创建一个新的地址空间,并将后面创建的节点连接在新的地址空间之后,所以当函数返回,虽然循环链表已经生成,但是phead并没有将后面的节点连接在它后面(也就是phead依旧会是空的)。

原始循环链表的打印:

将原始的循环链表按照存放的顺序进行输出,需要注意的是,循环链表的结尾,所以循环链表的输出结束条件应当是当前输出的项不是第一项(可使用do……while循环先将第一项进行输出)。代码如下:

void Print_List(NodeType *phead)
{
    NodeType *pcur = phead;
    if(!IsEmptyList(phead))
    {
        printf("id       password\n");
        do{
            printf("%3d, %7d\n", pcur->id, pcur->password);
            pcur = pcur->next;
        }while(pcur != phead);
    }
}
判空操作:

当每次出列一个人之后,链表就会缩短,使用该函数来判断是否为空链表:

int IsEmptyList(NodeType *phead)
{
    if(!phead)
    {
        printf("the list is empty!\n");
        return 1;
    }
    return 0;
}
约瑟夫环操作:

要实现约瑟夫环,就必定需要删除链表节点,要删除链表节点,就需要有删除节点的前驱节点,对于该循环链表,由于创建时没有表头节点,所以首先需要通过一个循环将prv指针移到链表的末尾,然后prv->next即就是head节点的前驱。之后需要通过一个次数为密码的循环,将prv和pcur同时后移密码数个节点,在这个循环中应当添加一个条件,当prv和pcur相同时说明链表仅剩一个元素,这时可以做一个标志位,退出循环,在删除该节点前,应当先输出该节点,删除时也要注意先将密码进行更新,也就是将要删除的节点保存到循环的次数中。该部分代码实现如下:

void JosephusOperate(NodeType **phead, int ipassword)
{
    int icount = 0;
    int iflag = 1;
    NodeType * prv = NULL;
    NodeType * pcur = NULL;
    NodeType * pdel = NULL;
    prv = pcur = *phead;
    while(prv->next != *phead)
    {
        prv = prv->next;
    }
    while(iflag)
    {
        for(icount = 1; icount < ipassword; icount++)
        {
            prv = pcur;
            pcur = pcur->next;
        }
        if(prv == pcur)
        {
            iflag = 0;
        }
        pdel = pcur;
        prv->next = pcur->next;
        pcur = pcur->next;
        ipassword = pdel->password;
        printf("第%d个人出列(密码:%d)\n", pdel->id, pdel->password);
        free(pdel);
    }
    *phead = NULL;
    getchar();
}

以上是我对约瑟夫环的理解,完整的代码可以参考这里.

上面是一个带密码的约瑟夫环问题,还有一个比较简单的不带密码的,相比来说更加简单,只是每次的密码都固定,也就是循环的次数是固定不变的,在程序运行开始,就预先输入了人数和密码,完整代码可以参考这里.
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值