目录↓
约瑟夫环
问题引入:约瑟夫环是一个数学的应用问题:已知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;
}
- 首先,循环链表是博主本人比较推荐的偏新手的一种做法,他的思想简洁,代码砍袖很多,对新手却很友好,就是简单的链表操作。一个是循环链表的构造,多了循环两个字,我们只需要把尾部的next指针指向head便可。
- 弹出的人,我们把节点删除即可,我们用大的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到n的数字全部排入队列,然后一个一个出队入队,如果到了那个需要的数字,我们就只对它进行出队操作。接着继续出队入队。总体很容易想,但是需要学过队列并且已经会写队列最基本的操作,博主前面博客有讲到,这里便不多讲。
- 当然因为涉及到队列的相关操作,代码量也会比较大,事实上,我们会发现,在写算法的时候,很多时候代码量真的与思想深度呈反比,很多小伙伴在学会更更高级的算法后,可能会发现,很多简洁的算法,他们的思想深度都是比较可怕的。而事实上,如果时间复杂度和空间复杂度允许的话,我们可以编一段无限长的代码去解决任何问题。所以很多时候呢,我们学习数据结构,学习算法,思维的深度一定不能落下,要尽量做到代码简短,复杂度小,并且也尽可能去追求极致,当然只是尽可能,毕竟不是人人都是天才。
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 晚安。
670

被折叠的 条评论
为什么被折叠?



