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();
}
以上是我对约瑟夫环的理解,完整的代码可以参考这里.