约瑟夫环(猴子选大王问题)
前言
本文是基于懒猫老师的数据结构课程所编写,我在这里直接给上地址:课程链接
1.循环链表实现
具体算法思想的文字图片描述后面补:…
可以去看懒猫老师课程·或者我下面代码中的笔记去理解
#include <stdio.h>
#include <stdlib.h>
/*约瑟夫环可以联想成猴子选大王的问题,
* 约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),
* 从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。
* 就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
*
*/
//解决约瑟夫环主要有四种方式
//循环链表
struct NODE {
int data;
struct NODE* next;
};
int main() {
int m, n;//猴子的个数n个 报数到m退出
int i;
int answer[100];//保存每一次的答案进行统一输出
int count = 0;//用来控制题目答案的下标
struct NODE *head, *tail, *p, *q;
//分别定义一个首 尾节点,p指针指向当前处理的节点,q指针指向下一个节点
//创建并初始化头结点,方便debug跟踪
head = (struct NODE *) malloc(sizeof(struct NODE));
head->data = -1;
head->next = NULL;
while (1) {
scanf("%d%d", &n, &m);
if (n == 0 || m == 0)//对输入进判断
{
free(head);
break;
} else {
//尾插法创建循环链表
tail = head;
for (i = 0; i < n; i++) {
p = (struct NODE *) malloc(sizeof(struct NODE));
p->data = i + 1;//从0开始循环所以加1代表第一个猴子的报数
tail->next = p;//插到尾部
p->next = head->next;//保持首尾相接
tail = p;//tail向后移动到表尾
}
if(n>0)
{
tail->next = head->next;
}
//到这里环形就构建完毕了,开始解决问题
//把p重新移动到首元节点处,将q放在表尾 准备进行删除操作
p = head->next;
q = tail;
i = 1;
// 即保持pq一前一后的关系不变,p指向当前要处理的节点,q指向其前面的一个结点
}
while (p != q)//当表中只有一个元素的时候q p指针就会重合,此时,正好指向的就是我们要的猴王
{//删除结点
if (i == m) {
q->next = p->next;
free(p);
p = q->next;//将p移动到下一个节点
i = 1;//每次有人出去的时候 下一个人都需要重新报数
} else {
i++;
//p q分别向后移动 而且始终保持q在p之前
q = p;
p = p->next;
}
}//跳出while循环说明此时p=q 猴王就是p->data
//注意:当链表中只有两个元素时,删除的是首元节点,如果需要保持链表整性的话
// 那么需要将头结点接到现在留下来的唯一节点上 即head->next=q
answer[count] = p->data;
count++;
free(p);
head->next = NULL;//
}
for (int j = 0; j < count; j++) {
printf("%d\n", answer[j]);
}
// free(head);可以不要 因为在最初时就释放了
return 0;
}
2.数组标志位实现
其实后面两种方法也是借助了链表的思想,进行模拟,只不过虽然本质上是数组,但是却用了取余的方式巧妙的转换成了类似一个循环链表,类似的操作在顺序栈和队列中也有用到,如果不理解的朋友可以去看王卓老师的数据结构视频,十分详细
#include <stdio.h>
//方法二
//数组标志位实现
/**
* 主函数
* 本函数通过模拟猴子报数的过程,确定最后剩下的猴子的编号
* 输入: n - 猴子的总数
* m - 报数的上限
* 输出: 最后剩下的猴子的编号
*/
int main() {
int n,m;
int number;//记录剩余猴子个数
int count=1;//代表当前报数
int i,pos;
// 循环读取输入的猴子总数n和报数上限m
while (~scanf("%d %d",&n,&m))
// 如果输入的n或m为0,则结束程序
if(n==0 || m==0)
{
return 0;
}
number=n;
// 创建一个monkey数组存储猴子的编号和状态
// 存储-1就代表猴子已经出局了,1到n+1代表还没退出的猴子
int monkey[300]={0};
// 初始化monkey数组,设置每个猴子的初始编号
for(i=1;i<n;i++)
{
monkey[i]=i+1;
}
// 主循环,模拟报数过程,直到只剩下一只猴子
while(number>1)
{
// 如果当前猴子还在游戏中(未出局)
if(monkey[pos]>0)
{
// 如果当前报数还未达到m,则继续报数
if(count!=m) {
count++;//报数加1
pos = (pos + 1) % n;//就像一个圈圈
// 一般是往右移动一位 当圈圈到头了 就从0开始
}else{
// 如果当前报数达到m,则当前猴子出局
monkey[pos]=0;
number--;
count=1;
pos=(pos+1)%n;
}
} else
{
// 如果当前猴子已经出局,则移动到下一个猴子
pos=(pos+1)%n;
}
}
// 遍历monkey数组,找到并打印最后剩下的猴子的编号
for (i = 0; i <n; i++)
{
if(monkey[i]>0)
{
printf("%d\n",monkey[i]);
}
}
return 0;
}
数组链接方式实现
也是借助链表的思想,有点类似于双指针,后面会补上 未完待续…