C语言编程解约瑟夫问题,详解约瑟夫环问题及其相关的C语言算法实现

约瑟夫环问题

N个人围成一圈顺序编号,从1号开始按1、2、3......顺序报数,报p者退出圈外,其余的人再从1、2、3开始报数,报p的人再退出圈外,以此类推。

请按退出顺序输出每个退出人的原序号

算法思想用数学归纳法递推。

无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),若nm非常大,无法在短时间内计算出结果。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。

为了讨论方便,先把问题稍微改变一下,并不影响原意:

问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):

k k+1 k+2 ... n-2,n-1,1,2,... k-2并且从k开始报0。

现在我们把他们的编号做一下转换:

k     --> 0

k+1   --> 1

k+2   --> 2

...

...

k-2   --> n-2

k-1   --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n

如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况――这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]

递推公式

f[1]=0;

f[i]=(f[i-1]+m)%i; (i>1)

一、循环链表建立一个有N个元素的循环链表,然后从链表头开始遍历并计数,如果基数i == m,则踢出该元素,继续循环,直到当前元素与下一个元素相同时退出循环

#include

#include

#include

typedef struct lnode

{

int pos;

struct lnode *next;

} lnode;

/**

* 构建循环链表&&循环遍历

*/

void create_ring(lnode **root,int loc,int n)

{

lnode *pre,*current,*new;

current = *root;

pre = NULL;

while (current != NULL) {

pre = current;

current = current->next;

}

new = (lnode *)malloc(sizeof(lnode));

new->pos = loc;

new->next = current;

if (pre == NULL) {

*root = new;

} else {

pre->next = new;

}

// 循环链表

if (loc == n) {

new->next = *root;

}

}

/**

* 约瑟夫环

*/

void kickoff_ring(lnode *head,int p)

{

int i;

lnode *pre,*pcur;

pre = pcur = head;

while (pcur->next != pcur) {

for (i = 1; i < p; i ++) {

pre = pcur;

pcur = pcur->next;

}

printf("%d ",pcur->pos);

pre->next = pcur->next;

free(pcur);

pcur = pre->next;

}

printf("%d\n",pcur->pos);

free(pcur);

}

void print_ring(lnode *head)

{

lnode *cur;

cur = head;

while (cur->next != head) {

printf("%d ",cur->pos);

cur = cur->next;

}

printf("%d\n",cur->pos);

}

int main()

{

int i,p,n;

lnode *head;

while (scanf("%d %d",&n,&p) != EOF) {

// 构建循环链表

for (i = 1,head = NULL; i <= n; i ++)

create_ring(&head,i,n);

// 约瑟夫环

if (p != 1)

kickoff_ring(head,p);

else

print_ring(head);

}

return 0;

}

/**************************************************************

Problem: 1188

User: wangzhengyi

Language: C

Result: Accepted

Time:110 ms

Memory:912 kb

****************************************************************/

二、数组模拟思想跟循环链表类似,少了构建循环链表的过程

#include

#include

int main()

{

int i,index,n,remain,delete[3001],flag[3001] = {0};

while (scanf("%d %d",&p) != EOF) {

remain = n;

index = 0;

while (remain >= 1) {

for (i = 0; i < n; i ++) {

if (flag[i] == 0) {

// 报数

index ++;

// 报p者退出圈外

if (index == p) {

// 退出圈外

flag[i] = 1;

// 重新报数

index = 0;

delete[remain - 1] = i + 1;

remain --;

}

}

}

}

// 输出每个退出人的序号

for (i = n - 1; i >= 0; i --) {

if (i == 0) {

printf("%d\n",delete[i]);

} else {

printf("%d ",delete[i]);

}

}

}

return 0;

}

三、数学推导

#include

int main(void)

{

int i,m,last;

while (scanf("%d",&n) != EOF && n != 0) {

// 接收报数

scanf("%d",&m);

// 约瑟夫环问题

for (i = 2,last = 0; i <= n; i ++) {

last = (last + m) % i;

}

printf("%d\n",last + 1);

}

return 0;

}

总结

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

小编个人微信号 jb51ccc

喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值