约瑟夫问题C语言解决(包括顺序表和链表两种方案)

        设有n个人围坐在圆桌周围,现从第s个人开始报数,数到第m个人出列,然后从出列的下一个人重新开始报数,数到第m个人又出列...如此重复,直到所有人出列为止,出列结构即为约瑟夫问题结果。

        约瑟夫问题是一个非常经典的题目,解决问题的方法有很多,我在这里分享一下我想出的两种方法。

        首先是顺序表,我们可以将所有人用数组元素的下标来代表,元素的值为1表示这个人还在继续游戏,为0则表示已经出局。这样用C语言来描述游戏的情况就很轻松。当我们每一个要数m个人时,只需判断一下数过的每个元素的值,如果是1不用额外处理,如果是0就表示这个人已经出局,咱少数了,就再多数一次。

具体思路见代码:

void joseph(int n, int s, int m)
{
	/*1. 
	创建一个大小为n的数组,以元素的下标来表示参与游戏的人,如people[0]表示第一个人的情况
	其中,1表示这个人还在游戏中,0表示已经出列 */
	int people[n] = {1};
	for(int i=0; i<n; i++)
		people[i] = 1;
	int temp = 0; //计数,表示已出局的人数 

	/*2.
	找到开始的那个人:第s个人*/
	int p = s-1; //p用来表示当前某个人对应的下标
	
	/*3.
	开始一个大循环,直到所有人全部出局
	从第s个开始数,数到第m个人,这表示我们实际上要数m-1次 */
	while(temp < n)
	{
		for(int i=0; i<m-1; i++)
		{
			p = (p + 1) % n; 
            //每数一个人,下标右移一次,同时由于人群围成一圈,故到达末尾时需从0开始,即取余 
			if(people[p] == 0) 
            // 对于那些已经出局的人我们在计数时会数到,但实际上应该将他们去除 
				i--;
		}
		printf("%d\t", p+1); //输出结果

		people[p] = 0; //将找到的那个人记为0,并将计数加1 
		temp++;
		
		p = (p + 1) % n; //从出局的那个人后边的人继续 
	}
}

        现在我们再来考虑一下用链表怎么做。

        如果我们采用最简单的链表来做,那么实际上与顺序表没有什么区别,只是换了一种载体,甚至由于链表的特殊性,反而还更加麻烦了,因此我们采用单循环链表来做。单循环链表与普通的链表区别不大,唯一的区别就是,单循环链表最后一个节点的指针不是指向NULL,而是指向它的头部。

        现在我们来看一下单循环链表的创建:

#include<stdlib.h>

//首先定义一个最简单的结构体,它由两个数据域data和指针next组成,
//其中data我们一会儿用来表示人的序号
typedef struct node
{
	int data;
	struct node *next;
}Lnode

Lnode *creat(int len)//尾插法创建单循环链表 
{
	Lnode *h, *p, *t;
	int data;
	h = (Lnode *)malloc(sizeof(Lnode));
	h->next = h;  //这里就是关键,next不再指向空,反而指向第一个节点
	h->data = 1;
	t = h;

	for(int i=1; i<len; i++)
	{
		p = (Lnode *)malloc(sizeof(Lnode));
		p->data = i+1;
		p->next = h; 
        // 创建一个新的节点并赋值,由于我们是尾插法,该节点是最后一个,
        //所以它的next指向第一个节点,如果右同学选择使用头插法,需要特别注意一下这一点
        
		t->next = p;
		t = p;
	}
	return h;
}

        之后的步骤就很简单了,我们找到目标节点,将它的数据读出来,然后删除这个节点,之后重复上述动作就可以了。

        此外还有一点,一个单独的链表节点是没办法删除的,因此我们还是需要一个计数的变量来判断是否结束游戏,不能直接以链表为空来作为结束的判断标志。

void joseph(int n, int s, int m)
{
	Lnode *h = creat(n); //创建一个有n个值的无头结点的单循环链表 
	printf("已创建\n");
	int temp = 0; //用于计数
	Lnode *p;
	p = h;
	
	for(int i=0; i<s-1; i++) //使p指向第s个人 
	{
		p = p->next;
	} 

	while(true) //死循环走起 
	{
		for(int i=0; i<m-2; i++) //使p指向第m个人的前一个 
		{
			p = p->next;
		}
		
		printf("%d\n", p->next->data); //输出
		 
		p->next = p->next->next; //删除节点,右移指针 
		p = p->next;	
		
		temp++;  //计数加一 
		if(temp == n)  //判断游戏是否结束 
			break;
	} 
}

        大体就是这样了,希望对大家有所帮助。

  • 4
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值