约瑟夫环-链表和数组表示实现及分析(Josephus Problem)

约瑟夫环:

约瑟夫环是一个数学的应用问题:

已知totalNum个人(以编号1,2,3...totalNum分别表示)围坐在一张圆桌周围。

从编号为startNum的人开始报数,数到step的那个人出列;

他的下一个人又从1开始报数,数到m的那个人又出列;

依此规律重复下去,直到圆桌周围的人全部出列。

***********************************************************************************************************************

这个问题可以用链表和数组两种数据结构来抽象表示,问题中的“出局”可以抽象为“删除结点”(当然也可以设置一个标记位,代表该结点是否可用,后面会讲到)。

所以对应不同数据结构,其优缺点如下:

1. 链表表示:优点是对数据的删除修改比较简单,但缺点是涉及太多指针操作,非常容易出错;

2. 数组表示:优点是直观,但缺点是如果将问题中的“出局”可以抽象为“删除结点”需要移动大量数据,复杂度大;

    所以可以用 Available[i]=0表示第i位已经被挑选出局 , Available[i]=1表示仍然可以参与游戏 这样,一个数组就有了双重意义,也成功避免了“删除”造成的移动操作


PS: 也看到有可以用数学推导法来找出每次出局序号的规律,但我还没研究明白。等今后慢慢补充吧~


具体如下(以下程序均可以直接运行)


1. 链表表示

#include "stdlib.h"
#include "stdio.h"

/*
* Author lxywl09
* Date   2014/10/08 
* 构建Josephus问题,即环形链表长度n,
* 从第i个开始,数到第m个删除,然后从下一个继续。
*/

//定义链表类型数据结构的结点 
//如果需要输出对应的人名,可以在这里增加一个char name[]成员 
struct CircleNode 
{
  int m_nValue;
  CircleNode* m_pNext;
};

CircleNode* CreateNode(int value)//创建一个结点 
{
  CircleNode *pNode = new CircleNode();
  pNode->m_nValue = value;
  pNode->m_pNext = NULL;
  
  return pNode;
}

CircleNode* CreateCircleList(int numbersOfNodes)//创建多个结点
{
  if(numbersOfNodes <= 0) return NULL;
  
  CircleNode* pHead = CreateNode(1);
  CircleNode* pNode = pHead;
  
  for(int i=2; i<=numbersOfNodes; i++)
  {
    CircleNode* pNewNode = CreateNode(i);
    pNode->m_pNext = pNewNode;
    pNode = pNewNode;
  }
  pNode->m_pNext = pHead;
   
  return pHead;
}

//输出循环链表 
void PrintCircle(CircleNode* pHead, int numbersOfNodes)
{
  printf("Numbers in this Circle are: ");
  CircleNode* pNode = pHead;
  int i=0;
  while(pNode != NULL && i<numbersOfNodes)
  {
    printf("%d ", pNode->m_nValue);
    pNode = pNode->m_pNext;
    i++;
  }
  printf("\n");
}

//Josephus环问题开始 
void PrintJosephus(int numbersOfPeople, int startNumber, int stepNumber)
{
  CircleNode* pHead = CreateCircleList(numbersOfPeople);
  PrintCircle(pHead, numbersOfPeople);
 
  CircleNode* pNode = pHead;
  CircleNode* pToBeDelete = NULL;
  
  for(int i=1; i<startNumber && pNode!=NULL; i++)//先找到起始位置 
    pNode = pNode->m_pNext;
  
  int numbersOfDel = 1;
  while(pNode != NULL && numbersOfDel <= numbersOfPeople)//开始计数 
  {
    for(int i=2; i<stepNumber; i++)
      pNode = pNode->m_pNext;
/*   
    //这个是 间隔step位    
    if (pNode->m_pNext != NULL)
    {
      pToBeDelete = pNode->m_pNext;
      printf("第%d次删除的是:%d\n", numbersOfDel, pToBeDelete->m_nValue);
      
      numbersOfDel++;
    }
*/
    //这个是 数到第step位,与上面相差一个。
    //不同的游戏规则对应不同的情况 
    if (pNode != NULL)
    {
      pToBeDelete = pNode;
      printf("第%d次删除的是:%d\n", numbersOfDel, pToBeDelete->m_nValue);
      
      numbersOfDel++;
    }
    pNode->m_pNext = pToBeDelete->m_pNext;
    pNode = pNode->m_pNext;
  }   
}

int main()
{
  PrintJosephus(9, 1, 5);
  PrintJosephus(9, 2, 5);
  PrintJosephus(10, 2, 5);
  
  system("pause");
  return 0;
}

2. 数组表示

#include <iostream>
using namespace std;

#define MAXNUM 20   //最大成员个数 
/*
* Author lxywl09
* Date   2014/10/09 
* 构建Josephus问题,即环形链表长度totalNum
* 从第startNum个开始,数到第step个删除,然后从下一个继续。
*/

void Josephus(int totalNum, int startNum, int step)
{
  //totalNum是实际的个数 
  //从数组下标为 1 的位开始 到 totalNum 位结束 
  //用 Available[i]=0表示第i位已经被挑选出局 , Available[i]=1表示仍然可以参与游戏 
  int Available[totalNum];
  for(int i=1; i<=totalNum; ++i)
    Available[i] = 1;   //游戏开始,先标记所有成员为1,即available 
  
  int num = totalNum;
  int start = startNum;
  int startIterator = start;
  while(num>=1)
  {
    int count = 0;  // 计数器 
      
    while(count<step)
    {
      if(Available[startIterator] == 1) count++;
      if(count == step) break;
      startIterator++;
      if(startIterator == totalNum+1) startIterator = 1;//如果走到数组尾部,则将其指向数组的第1位,实现“环”的概念
    } 
    cout<<startIterator<<endl;
    Available[startIterator] = 0;
    num--;
  }  
  
   
} 
int main()
{
  Josephus(5, 2, 3);
  
  system("pause");
  return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值