前言
如此经典的约瑟夫环岂能不做一做?!
一、循环链表
可以分成三个步骤:链表创建、结点输入、遍历计数
首先是链表的创建:在结构体里面循环链表和单链表一样都只设置两个域:data 和 next,但是在结点输入的时候需要让最后一个结点指向第一个结点,这样才能形成回路,其次需要注意的是,head作为头结点是没有猴子的,结点都是接在头结点后面的。
然后是结点的输入:为了避免结点编号和链表下标错位(避免猴子从1开始编号,但是数组和链表都是从0开始的),我们需要做这一步操作:cur->data = i + 1;
最后是遍历计数的环节(选大王):这里可以有两种思路哈,一种是设置remained变量保存剩余猴子数量,可别初始化成0了哈!另一种是cur指针(指向当前)和next指针(指向cur的后面一个)相等就结束,很显然嘛,当只剩下一个结点的时候,两个指针肯定就指向同一个地方了(但是我喜欢用第一种)。
还有两个需要注意的点,由于是循环链表,所以最后一个结点就是第一个结点的前驱,大伙儿画个图看看就能理解的,因为最后一个结点要连接上第一个结点才能形成回路嘛;那个计数的变量i,当i数到要退出的那个数字时就删除的操作,i一定是要一直更新的,所以i在循环里面,并且是在判断语句内重置,不然程序会卡住!!(下面有注释)
上代码捏!
//循环链表实现
#include<stdio.h>
#include<stdlib.h>
struct LinkList//链表
{
int data;
struct LinkList* next;
};
int main()
{
struct LinkList Node;
int monkey, number,remained;//monkey是猴子个数,number是报数退出界限,remained是剩余猴子个数
int count = 0;//报数的计数器
struct LinkList* head, * tail, * cur, * next;//头结点,尾结点,cur指向当前接电脑,next指向当前结点的下一个结点
head = (struct LinkList*)malloc(sizeof(struct LinkList));
head->next = NULL;//后面的猴子结点全部接在head后面,head是没有猴子在的
printf("请输入猴子个数以及出局的报数个数:\n");
scanf("%d%d", &monkey, &number);
remained = monkey;//刚开始时没有猴子出局
if (monkey == 0 || number == 0)
{
//这两种情况无解,释放内存后退出即可
free(head);
exit(-1);
}
else
{
tail = head;
for (int i = 0; i < monkey; i++)//尾插法构建循环链表
{
cur = (struct LinkList*)malloc(sizeof(struct LinkList));//因为是构建链表,cur肯定是不断开辟空间的
cur->data = i + 1;//避免猴子编号和链表编号错位
tail->next = cur;//因为这是循环链表,那么最后一个结点是第一个结点的前驱
cur->next = head->next;//循环链表构建,与单链表不同
tail = cur;
}
cur = head->next;//回去咯,准备遍历报数
next = tail;//遍历都需要两个指针的
int i = 1;//控制循环次数,出局一个必须重置
while (remained!=1)
{
if (i == number)//删除cur这个结点
{
next->next = next->next->next;//把cur结点空出来
free(cur);
cur = next->next;
remained--;
i = 1;//重置一定要在if语句里面捏
}
else
{
//各自向后移动一个结点
next = next->next;
cur = cur->next;
i++;
}
}
printf("大王是:%d\n", cur->data);
free(cur);
}
return 0;
}
二、数组
其实我不是很喜欢用数组做这道题,因为代码比较复杂,当然这个因人而异,并且数组也不止这一种方法捏
下面说说需要注意的点哈:
一个是别把monkey和number搞混了,尤其是在for循环的时候!别问我为什么这样说
还有一个是在最后是通过遍历找出最后的猴子,而链表不用。原因很简单,因为那个编号是存在数组里面的,而且你还得遍历那个猴子在哪个位置(也就是数组元素>0那个),这个地方我最讨厌了!!
最后上代码:
#include<stdio.h>
int main()
{
int monkey, number,remained;//monkey是猴子个数,number是出局的报数,remained是剩余猴子个数
int cur = 0;//处理数组下标
int count = 1;//计数器
printf("请输入猴子个数和出局的报数:\n");
scanf("%d%d", &monkey, &number);
if (monkey == 0 || number == 0) return 0;
else
{
remained = monkey;
int information[20];//用来记录猴子的信息,这里为了避免错位同样+1
for (int i = 0; i < monkey; i++)
{
information[i] = i + 1;//0代表以及出局的猴子
}
while (remained >1)
{
if (information[cur] > 0)//遇到的猴子没有出局,正常操作
{
if (count != number)//没到报数
{
count++;//报数加一
cur = (cur + 1) % monkey;//当小于monkey的%monkey是原数,大于等于monkey的数字%monkey会重置
}
else//到了报数,出局处理
{
information[cur] = 0;
count = 1;//这里是重置为1,不是0哦
remained--;
cur = (cur + 1) % monkey;
}
}
else//遇到已经出局的猴子跳过去
{
cur = (cur + 1) % monkey;
}
}
for (int i = 0; i < monkey; i++)
{
if(information[i]>0)
printf("大王是:%d\n", information[i]);
}
}
return 0;
}
总结
我第一次看到这道题是在C基础一百题,那时还没学习数据结构,所以用的是数组,因为现在数据结构学的差不多了,趁这个机会拿出来复习复习。其实有一种更更更简便的方法是用数学直接递归的,但是我是蒟蒻想不出来,各位有兴趣去看看众多神犇的解答吧!!!