一、问题叙述
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而.Josephus和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第K个人。接着,再越过k-1个人,并杀掉第K个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
二、分析
设编号分别为: 1,2,.,n的n个人围坐一圈。约定序号为k (1≤k≤n)的人从1开始计数,数到m的那个人出列,他的下一位又从1开始计数,数到m的那个人又出列,依次类推,直到所有人出列为止或者最后剩下一个人。
例如:有7个人,数到3出列,直到只剩一个人 那么出列的顺序为 3 6 2 7 5 1 最后剩下4.
三、代码实现
功能函数如下
//创建循环链表
linklist *createLinklist();
//求循环链表的结点个数
int linklist_length(linklist *head);
//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data);
//约瑟夫环的实现
void linklist_huan(linklist *p, int m);
函数实现
//创建链表
linklist *createLinklist()
{
linklist *head = (linklist *)malloc(sizeof(linklist));
if(NULL == head)
{
perror("malloc");
return NULL;
}
head->next = head;//循环,自己指向自己
return head;
}
//求链表的结点个数
int linklist_length(linklist *head)
{
linklist *p = head->next; //第一个有效结点
int num = 0;
//遍历链表
while(p != head)
{
num++;
p = p->next;
}
return num;
}
//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data)
{
int len = linklist_length(head);
if(pos < 0)
return -1;
pos=pos%(len+1);//循环,一圈,插入位置等于当前位置取余(总长度+1);例如,一个循环长度为6,当前位置为2,则在2后面插入的位置=6%3=3;
linklist *new = (linklist *)malloc(sizeof(linklist));
new->data = data;
new->next = NULL;
//找到pos的前一个位置
linklist *p = head;
while(pos--)
{
p=p->next;
}
//先连后断
new->next = p->next;
p->next = new;
return 0;
}
//环
void linklist_huan(linklist *p, int m)
{
int j = 1;//记录人数
linklist *q = NULL;
while(p->next != p)
{
int i;
for(i=1; i<m-1; i++)//p移动到周期数的前一个位置;假如周期m= 3,则循环执行一次,p从第一个有效结点,往后移动一次,指向第二个结点。
{
p=p->next;
}
printf("第%d个人是:%d\n", j,p->next->data);//打印当前指向的下一个结点的值。
j++;
q = p->next;//把p指向的下一个结点地址给q,为了释放结点
p->next = q->next;//把q指向的地址,给 p->next;
free(q);//释放q
p = p->next;//此时p->next 保存的是q指向的地址,先赋值,再释放,再把p->next保存的地址给p;
}
printf("最后剩下的是:%d\n", p->data);//退出循环就说明只剩一个人。
}
四、完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int data_t;
typedef struct node{
data_t data;
struct node *next;
}linklist;
//创建链表
linklist *createLinklist()
{
linklist *head = (linklist *)malloc(sizeof(linklist));
if(NULL == head)
{
perror("malloc");
return NULL;
}
head->next = head;//循环,自己指向自己
return head;
}
//求链表的结点个数
int linklist_length(linklist *head)
{
linklist *p = head->next; //第一个有效结点
int num = 0;
//遍历链表
while(p != head)
{
num++;
p = p->next;
}
return num;
}
//按位置添加结点
int linklistInsertByPos(linklist *head, int pos, data_t data)
{
int len = linklist_length(head);
if(pos < 0)
return -1;
pos=pos%(len+1);//循环,一圈,插入位置等于当前位置取余(总长度+1);例如,一个循环长度为6,当前位置为2,则在2后面插入的位置=6%3=3;
linklist *new = (linklist *)malloc(sizeof(linklist));
new->data = data;
new->next = NULL;
//找到pos的前一个位置
linklist *p = head;
while(pos--)
{
p=p->next;
}
//先连后断
new->next = p->next;
p->next = new;
return 0;
}
//环
void linklist_huan(linklist *p, int m)
{
int j = 1;//记录人数
linklist *q = NULL;
while(p->next != p)
{
int i;
for(i=1; i<m-1; i++)//p移动到周期数的前一个位置;假如周期m= 3,则循环执行一次,p从第一个有效结点,往后移动一次,指向第二个结点。
{
p=p->next;
}
printf("第%d个人是:%d\n", j,p->next->data);//打印当前指向的下一个结点的值。
j++;
q = p->next;//把p指向的下一个结点地址给q,为了释放结点
p->next = q->next;//把q指向的地址,给 p->next;
free(q);//释放q
p = p->next;//此时p->next 保存的是q指向的地址,先赋值,再释放,再把p->next保存的地址给p;
}
printf("最后剩下的是:%d\n", p->data);//退出循环就说明只剩一个人。
}
int main(int argc, const char *argv[])
{
linklist *head = createLinklist();
if(NULL == head)
{
printf("createLinklist failed\n");
return -1;
}
int i = 0;
while(i<41)//插入41个元素,相当于41个人,可以随意改变人数
{
linklistInsertByPos(head, i, i+1);
i++;
}
//去掉头结点
linklist *p = head->next;
linklist *q = NULL;
while(p->next != head)
{
p=p->next;
}
p->next = head->next;
free(head);
head = NULL;
q = p->next; //q就是第一个有效结点
int m; //周期
puts("请输入一个周期: ");
scanf("%d", &m);
linklist_huan(q, m);
}