在解决约瑟夫问题时,以前我往往使用的是数组,但是数组有一个明显的缺点——对于那些出圈的人你需要更复杂的思维。但是如果使用链表,则可以将出圈这一动作完全地模拟出来,更易于理解,同时避免了不必要的内存占用。
约瑟夫问题
总时间限制: 1000ms 内存限制: 65536kB
描述
约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
输入
每行是用空格分开的两个整数,第一个是 n, 第二个是 m ( 0 < m,n <=300)。最后一行是:
0 0
输出
对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号
样例输入
6 2
12 4
8 3
0 0
样例输出
5
1
7
代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
struct MS
{
int index; //编号
struct MS *previous, *next; //上一个、下一个节点的地址
}*first, *current;
int main()
{
int n, m; //n - (人)数, m - 要报的数
while (~scanf("%d%d", &n, &m) && n && m) //应题目要求输入
{
//建立链表
first = current = (struct MS*)calloc(1, sizeof(struct MS)); //哑节点,便于之后建立链表
for (int i = 0; i < n; i++)
{
current->next = (struct MS*)calloc(1, sizeof(struct MS)); //calloc有初始化的功能,malloc没有
current->next->previous = current; //使建立的新节点的左链域指向当前节点
current = current->next; //移动当前节点至下一节点
current->index = i + 1; //给他编号
}
first->next->previous = current; //在创建完整个链表后,将头节点(即哑节点)指向的节点与尾节点相连
current->next = first->next; //构成一个循环链表,哑节点退化为指针
//报数
current = first; //方便统一操作,所以不写成first->next
int count = 0; //已经出去的人
int number = 0; //报的数
while (count < n)
{
current = current->next;
number++;
if (number == m)
{
count++;
number = 0;
//输出报数的人
//printf("%d ", current->index);
if (count == n) printf("%d\n", current->index);
//删除节点
current->previous->next = current->next; //让上一个节点不指向自己而是指向下一个节点
current->next->previous = current->previous; //让下一个节点不是指向自己而是指向上一个节点
struct MS *p = current; //用来保存即将删除的节点的地址
current = current->previous; //移动当前节点
free(p);//释放,即完成出圈过程
}
}
free(first);//最后一步把哑节点给释放掉
}
return 0;
}