约瑟夫问题
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了人数总和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
个人认为,约瑟夫真的是个心机婊。呵呵。。。
抛开政治,讨论算法。
这个是个典型的循环链表,所以先定义结构。
typedef struct node
{
int data;
struct node* next;
}node;
node* ghead = NULL;
然后生成41个有序的循环链表。
void create_41(int num)
{
node* ltail = NULL, * lnew = NULL;
int i ;
/*ghead = (node*)malloc(sizeof(node));
ltail = ghead;*/
for (i = 0; i < num; i++)
{
lnew = (node*)malloc(sizeof(node));
lnew->data = i + 1;
if (i == 0)
{
ghead = lnew;
lnew->next = lnew;
}
else
{
lnew->next = ltail->next;
ltail->next = lnew;
}
ltail = lnew;
}
}
看看创建的情况,所以再写个打印循环链表。
void printlist(node * ll)
{
node* llh=NULL;
llh = ll;
int i = 0;
if (!ll)
{
printf("链表为空");
return;
}
while (ll->next != llh)
{
printf("%d:\t%d\n", i++, ll->data);
ll = ll->next;
}
printf("%d:\t%d\n", i++, ll->data);
}
下边开始进入重点了,如何让约瑟夫逃过此劫,就要发挥想象了。
我想了两种解决方法,一种是第一天写的(没思路,常规套路),一种是第二天写的(顿悟中。。。。)。
第一种方法:
比较传统,找个变量依次记录步数(就三步),然后输出节点信息,修改头指针,重置标志等等。没有新意,效率比较低。
int GetListLength(node* ll)
{
node* llh = NULL;
llh = ll;
int i = 1;
while (ll->next != llh)
{
i++;
ll = ll->next;
}
return i;
}
//约瑟夫问题
void Joseph(node* ll)
{
/*
41个人围成圆圈,从第一个人开始报数,数到3的人自杀。
再从下一个人开始,数到3,自杀。
最后剩2个人。
*/
node* llpos=NULL;
node* llprev=NULL;
llpos = ll;
int i = 1;
while (GetListLength(ll) > 2)
{
//到达第三人
if (i == 3)
{
ll = llpos->next;
printf("%d \t号自杀了。\n", llpos->data);
llprev->next = llpos->next;
free(llpos);
i = 1;
llpos = ll;
}
else if (i == 2) //得到前驱节点
{
llprev = llpos;
i++;
llpos = llpos->next;
}
else
{
i++;
llpos = llpos->next;
}
}
//更新头指针所指向位置
ghead = ll;
}
第二种方法:
利用循环这个要点,步子迈大点,但是不要扯着淡,ll->next->next,如果剩余两个的话,刚刚回到原点,这种方式只要循环39次就行,但是第一种方法循环的方式为39*3次,对于计算机而言,这都不是事,但是追求代码短、效率高的极致,就要不断提升。
void Joseph2(node* ll)
{
node* llpos = NULL;
node* llprev = NULL;
while (ll->next->next != ll)
{
//得到目标节点
llpos = ll->next->next ;
//获得前驱节点
llprev = ll->next;
//修改头节点指针
ll = llpos->next;
//输出
printf("%d \t号自杀了。\n", llpos->data);
//删除节点
llprev->next = llpos->next;
//释放内存
free(llpos);
}
//更新头指针所指向位置
ghead = ll;
}
运行结果
怎么样?是不是很ok。
测试代码
int main()
{
create_41(41);
//printlist(ghead);
//printf("链表长度:%d\n",GetListLength(ghead));
//printf("-------------------\n");
//Joseph(ghead);
Joseph2(ghead);
printf("-------------------\n");
printf("逃脱的人员:\n");
printlist(ghead);
system("pause");
}