约瑟夫环:
约瑟夫环是一个数学的应用问题:
已知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;
}