c语言:三种不同的方法实现约瑟夫环--循环链表,遍历数组,队列操作。

目录

约瑟夫环

问题引入:约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
在这里插入图片描述
好吧,这张图卵用没有😂
进入正题

1. 利用循环链表实现约瑟夫环

代码:

/*约瑟夫环问题*/
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
	int n;
	struct node* next;
}person;
/*使用循环链表将同学连接起来*/
person* creat(int n)
{
	person* head=(person*)malloc(sizeof(person));//开辟空间
	head->n=1;//后面会链接不需要将next指向空。
	person *p=head;
	int i;
	for(i=2;i<=n;i++)
	{
		person* q=(person*)malloc(sizeof(person));
		q->n=i;
		p->next=q;
		p=p->next; 
	}
	p->next=head;//完成循环结构 。 
	return head;//返回循环链表 。 
}//第一个函数完成对循环链表的建立及对各个节点的赋值。
void find(person* head,int a,int b)//a为开始人数,b为弹出数字
{
	int i;
	person *p=head,*q;
	while(p->n!=a)//找出第一个数字 
	{
		q=p;
		p=p->next;
	}
	while(p->next!=p)//不满足条件说明还有人
	{
		for(i=1;i<b;i++)
		{
			q=p;
			p=p->next;
		}//通过操作弹出这个人
		q->next=p->next;
		printf("the %d out\n",p->n);
		free(p);
		p=q->next;//继续 
	} 
	printf("the %d is end\n",p->n);
	free(p);
} 
int main()
{
	int n,m,b;
	scanf("%d",&n);//n为人数
	person* head=creat(n);
	scanf("%d %d",&m,&b);//输入开始数数的数字
	find(head,m,b); 
	return 0;
}
  1. 首先,循环链表是博主本人比较推荐的偏新手的一种做法,他的思想简洁,代码砍袖很多,对新手却很友好,就是简单的链表操作。一个是循环链表的构造,多了循环两个字,我们只需要把尾部的next指针指向head便可。
  2. 弹出的人,我们把节点删除即可,我们用大的while循环来判断链表是否循环完毕,很明显,如果只剩最后一个人,那么我们链表的next便指向这个链表,所以也很容易判断,这里的思想便不必多说,这是最符合常人思想的算法了,到这个数字,把你弹出去,然后归零继续数。😂下一个。

2.利用队列实现约瑟夫环

代码:

#include<stdio.h>
#include<stdlib.h>
#define error(str) fprintf(stderr,"%s",str),exit(1)//自定义报错信息
typedef struct node
{
	int *a;
	int size;//size表示长度 
	int fornt;
	int rear;
}p; 
/*函数区*/
p* creat();//创建及初始化 
int isempty(p* q);//判断队列是否为空
int isfull(p* q);//判断是否为满 
void init(p* q,int n);//入队
void outit(p* q);//出队
/*主函数*/ 
int main()
{
	int i,k;//假设五个人 
	p* q=creat();//需要的队列 
	for(i=1;i<=5;i++)
		init(q,i);//入队 
	while(!isempty(q))
	{
		for(i=0;i<2;i++)
		{
			k=q->fornt;
			outit(q);
			init(q,q->a[k]);
		}
		outit(q);
		k=(k+1)%5;
		printf("%d ",q->a[k]);
	} 
	return 0;
} 
/*函数实现*/ 
p* creat()
{
	p* q=(p*)malloc(sizeof(p));
	q->a=(int*)malloc(sizeof(int)*5);//开辟五个元素的空间
	q->fornt=0;//整体进行初始化 
	q->rear=0;
	q->size=0; 
}
int isempty(p* q)
{
	return q->size==0;//为0则为空返回1 
}
int isfull(p* q)
{
	return q->size==5;//为5则满,返回1否则返回0。 
}
void init(p* q,int n)
{ 
	if(isfull(q))
		error("is full");
	q->rear%=5;
	q->a[q->rear++]=n;//实现完之后自加 这里的前点不变 
	q->size+=1;//这里入队的情况应该都考虑到了。 
} 
void outit(p* q)
{
	if(isempty(q))
		error("is empty");
	q->fornt=(q->fornt+1)%5;//对fornt进行变化 ,最后用fornt和rear进行遍历。
	q->size-=1; 
}
  1. 这种算法也是博主比较推荐小白用的,因为他的思想很简单。学过队列的小伙伴都知道,队列是一种头出尾入的数据结构。那么我们只需要把从1到n的数字全部排入队列,然后一个一个出队入队,如果到了那个需要的数字,我们就只对它进行出队操作。接着继续出队入队。总体很容易想,但是需要学过队列并且已经会写队列最基本的操作,博主前面博客有讲到,这里便不多讲。
  2. 当然因为涉及到队列的相关操作,代码量也会比较大,事实上,我们会发现,在写算法的时候,很多时候代码量真的与思想深度呈反比,很多小伙伴在学会更更高级的算法后,可能会发现,很多简洁的算法,他们的思想深度都是比较可怕的。而事实上,如果时间复杂度和空间复杂度允许的话,我们可以编一段无限长的代码去解决任何问题。所以很多时候呢,我们学习数据结构,学习算法,思维的深度一定不能落下,要尽量做到代码简短,复杂度小,并且也尽可能去追求极致,当然只是尽可能,毕竟不是人人都是天才。

3.覆盖数组实现

这个算法是重点,一个是它达到了代码精简的地步,emm,目前我在网上还没有找到比该算法更为简洁的c语言源码,希望小伙伴们在看这段代码时,一定保证思维跟的上,因为…它几乎只有一步emm😂
代码:

#include<stdio.h>
#include<stdlib.h>
josephus(int *a,int n,int k)
{
	
	int f=0,r=n,i;
	while(f-r)
	{
		for(i=0;i<k-1;i++)//循环n-1次,保证弹出 
		{
			f=(f+1)%n;
			r=(r+1)%n;
			a[r]=a[f]; 
		}
		f=(f+1)%n;
		printf("%d  ",a[f]);
	}
}
int main()
{
	int *a,n,k,i;
	scanf("%d %d",&n,&k);
	a=(int*)malloc(sizeof(int)*(n+1));//开辟空间 
	a[0]=n;
	for(i=1;i<=n;i++)
		a[i]=i;
	josephus(a,n,k);
	return 0;	
} 

我们需要关注的是算法核心:我命它为覆盖数组😂

josephus(int *a,int n,int k)
{
	
	int f=0,r=n,i;
	while(f-r)
	{
		for(i=0;i<k-1;i++)//循环n-1次,保证弹出 
		{
			f=(f+1)%n;
			r=(r+1)%n;
			a[r]=a[f]; 
		}
		f=(f+1)%n;
		printf("%d  ",a[f]);
	}
}

这里的n为总人数,k为数到k弹出,这个算法浪费了我大概从歌单的第一首歌到第三十五首歌的时间,不过还好结果不错,他从下标的变化,模拟了一个循环的数组。
在这里插入图片描述
这个算法的核心思想其实我并没有特别研究清楚,但是大概的思路有,怎么说呢,这里数组的应用就很神奇😓,我们得通过不断的替换,来弹出自己需要的数字,我在本子上画了大概前三个数的过程之后我就直接用了,我承认我有赌的成分,但是它竟然真的可以写出来。我们这里弹出数字要用另一种操作代替,那就是覆盖,而且这里并不是对后面的数组都进行覆盖,是进行一个以k-1(k为该数到的弹出的数字)为次数的循环的覆盖,这里有些迷乱
假如你数到3弹出,那么第一次,应该是数到三之后将a[4]赋给a[3]然后将a[5]赋给a[4]然后再弹出a[6]接着继续覆盖,将a[7]赋给a[6],a[8]赋给a[7],大概就是这样的一直循环下去,同时我们知道,刚开始的时候f与n会同步为1之后每一次弹出都会拉开一个一个数字差,但是弹出n次的时候又相同了,因此,我们将while的控制条件定为两者相等时,停止循环我这里用f-n表示,两者相等时变为0.这段代码博主大概还得研究研究,不过大致思路目前大家可以在本子上画一下。

总结

很显然第一和第二段代码对算法还不太精通的小伙伴更为友好,但是最后一段代码确实值得小伙本们研究研究,博主还是希望小伙伴们多多自己思考。
over 晚安。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值