问题 C-E: Josephus问题(Ⅰ)(Ⅱ)(Ⅲ)
题目描述 - Josephus问题(Ⅰ)
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
要求程序模拟题意来实现
题目描述 - Josephus问题(Ⅱ)
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二"报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
n很大,直接模拟题意不行,请找出规律
题目描述 - Josephus问题(Ⅲ)
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二三…"报数,报到m的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。
本题的数据规模更具有挑战性,尝试更通用且高效的算法。
输入(Ⅰ、Ⅱ)
不超过1000组数据。
每组数据一行,每行一个正整数,代表人数n。 (1 <= n <= 1000)
输出(Ⅰ、Ⅱ)
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。
样例输入(Ⅰ、Ⅱ)
7
2
样例输出(Ⅰ、Ⅱ)
7
1
题目提示
第一组数据出队顺序: 2 4 6 1 5 3
输入(Ⅲ)
超过1000组数据。
每组数据一行,每行两个正整数,代表人数n (1 <= n < 231)和m(1<=m<=100)。
输出(Ⅲ)
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。
样例输入(Ⅲ)
7 2
2 2
样例输出(Ⅲ)
7
1
解题过程
思路 一
首先我们就题论题的来看,Josephus(1)题目的要求就是设计一个程序来简单模拟题意,循环出队,最后一个出队的成员被记录输出。
因为这个思路过于简单,我们就直接分享一下代码,不多做解释了。
#include <stdio.h>
#include <stdlib.h>
#define max 1024
void findout(int n, int m)
{
int buf[max];
int dex = 0,in = 0, i;
for ( i = 1; i <= n; i++)
{
buf[i] = i;
}
while (n > in + 1)
{
for (i = 1; i <= n; i++)
{
if (buf[i] != 0)
{
dex++;
if (dex == m)
{
buf[i] = 0;
in++;
dex = 0;
}
}
}
}
for (i = 1; i <= n; i++)
if (buf[i] != 0)
printf("%d\n", buf[i]);
}
int main()
{
int n, m;
/*
m=2
while (scanf("%d", &n) != EOF)
*/
while (scanf("%d %d", &n, &m) != EOF)
{
if (m == 1)
{
printf("%d\n", n);//当 m = 1 时,也就是每一个人都排队出列,此时最后一个出列的就是第 n 个人
continue;
}
findout(n, m);
}
return 0;
}
代码分析
这样解决的话时间复杂度很高,O(n) = n^3,所以当n很大的时候不适用(其实一般大可能就会时间超限,所以不推荐使用),采用主函数注释中的内容就可以解决小规模的问题(m = 2)。
思路二
代码分析
首先创建结构体数组,每个结构体包括元素的编号、前驱元的索引(previous)及后继元的索引(next)。每次循环删除一个元素,使该元素的前驱元的next指向后继元的previous,后继元的previous指向前驱元的next,并将此元素标号置为-1。
从被删除元素的下一个元素开始继续循环,直到数组中只剩下一个元素(此元素的previous与next相等)
显然该算法时间复杂度为O(m*n)
思路三
#include<cstdio>
#include<algorithm>
using namespace std;
long long solve_Josephus(long long n, long long m)
{
long long p = 0, i = 2;
long long buf;
if (m == 1)
return 0;
while (i <= n)
{
if (p + m < i)
{
buf = (i - p - 1) % (m - 1) ? (i - p - 1) / (m - 1) : (i - p - 1) / (m - 1) - 1;
if (i + buf > n)
{
p += (n + 1 - i) * m;
break;
}
i = buf + i;
p = p + buf * m;
}
else
{
p = (p + m) % i;
i++;
}
}
return (p + 1) % n;
}
int main()
{
long long n, m, p;
while (scanf("%lld %lld", &n, &m) != EOF)
{
p = solve_Josephus(n, m);
printf("%lld\n", p ? p : n);
}
return 0;
}
代码分析
利用数学规律探求Josephus问题的数学解法,只进行数学运算,不利用递归和循环解法直接求解,这样的话就可以极大程度的减少时间复杂度,此方法可解决问题2&3,解决问题2时要选择性的改变m。
显然该算法时间复杂度为O(1)
综合分析
假设当前游戏玩家总数为t,报数最大值为m,第一个出席的玩家编号为f(t)。那么很显然,当t=1时,f(1)=1。
当t>1时,很显然,f(t)=m。如下图:
剩下的(t-1)个成员,重新制订编号,假设原编号为i,新编号为j,
则i和j满足关系:i = (j + m - 1) % t + 1
所以得到:f(n) = (f(n-1) + m - 1)%n + 1
此时我们得到了一个递归关系
其中x为[t/m](或写作“t mod m”)。之前的递归是每次离席1人就递归1次,但上图这种情况,可以从m离席“快进”到xm离席,节省了中间的迭代过程,即f(n) = (f(n-x) + xm - 1)%n + 1。
这种算法最终的时间复杂度是O(1)
public int GetResultByFastRecursion()
{
int result = 1;
int delta;
for (int t = 2; t <= Total; t++)
{
if (result + MaxFlag < t)
{
delta = (t - result) / MaxFlag;
if (delta > Total - t)
{
delta = Total - t;
}
t = t + delta;
result = result + MaxFlag * delta;
}
result = (result + MaxFlag - 1) % t + 1;
}
return result;
}
【为了避免大家复制粘贴我换了一种语言】
附录
著名的Josephus问题
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
百度提供的解决代码(搬运,侵权请联系我)
#include<stdio.h>
#include<math.h>
#define FALSE 0
#define TRUE 1
typedef int DataType; /* 定义元素类型为整型,也可定义为其他类型 */
struct Node; /* 单链表结点类型 */
typedef struct Node* PNode; /* 结点指针类型 */
struct Node /* 单链表结点结构 */
{
DataType info;
PNode link;
};
struct LinkList /* 单链表类型定义 */
{
PNode head; /* 指向单链表中的第一个结点 */
};
typedef struct LinkList* PLinkList; /* 单链表类型的指针类型 */
int insert_clink(PLinkList pclist, DataType x, PNode p)
/* 在pclist所指的循环单链表中最后一个结点p的后面插入元素x */
{
PNode q;
q = (PNode)malloc(sizeof(struct Node));
if (q == NULL)
{
printf("Out of space!!! \n");
return (FALSE);
}
else
{
q->info = x;
q->link = pclist->head->link;
p->link = q;
return (TRUE);
}
}
PLinkList createNullList_clink(void)
/* 创建带头结点的空循环链表*/
{
PLinkList pclist;
PNode p;
pclist = (PLinkList)malloc(sizeof(struct LinkList));
if (pclist != NULL)
{
p = (PNode)malloc(sizeof(struct Node)); /* 申请头结点 */
if (p != NULL)
{
pclist->head = p;
p->link = NULL;
}
else
pclist->head = NULL;
}
else
printf("Out of space!!\n");
return pclist;
}
PNode next_clink(PNode p)
{
return p->link;
}
PNode find_clink(PLinkList pclist, int i)
/* 在带有头结点的循环单链表clist中求第i(i>0)个结点的位置 */
/* 当表为空循环单链表时,返回值为NULL */
{
PNode p;
int j;
p = pclist->head->link;
if (i < 1)
{
printf("The value of i=%d is not reasonable.\n", i);
return NULL;
}
if (p == NULL)
return NULL;
for (j = 1; j
p = p->link;
return p;
}
void josephus_clink(PLinkList pclist, int s, int m)
{
PNode p, pre, tp;
int i;
p = find_clink(pclist, s); /* 找第s个结点 */
if (p == NULL) /* 无第s个结点 */
{
printf(" s = %d not exit.\n ", s);
exit(1);
}
while (pclist->head->link != NULL)
{
for (i = 1; i
{
pre = p;
p = p->link;
}
printf(" out element: %i \n", p->info); /* 输出该结点 */
if (pre != p) /* 当表中元素个数大于1时,删除该结点 */
{
pre->link = p->link;
tp = p;
p = p->link;
free(tp);
}
else /* 当表中元素个数等于1时,将头结点指针置空 */
{
free(pre);
pclist->head->link = NULL;
}
}
free(pclist->head);
free(pclist);
}
int main()
{
PLinkList jos_clist;
PNode p;
int i, n, s, m, k;
printf("\n please input the values of n = ");
scanf("%d", &n);
printf(" please input the values of s = ");
scanf("%d", &s);
printf(" please input the values of m = ");
scanf("%d", &m);
jos_clist = createNullList_clink(); /* 创建空循环链表 */
if (jos_clist == NULL || jos_clist->head == NULL) return (FALSE);
p = jos_clist->head;
for (i = 1; i <= n; i++) /* 创建循环链表 */
{
k = insert_clink(jos_clist, i, p);
if (k == FALSE) return(FALSE);
p = next_clink(p);
}
josephus_clink(jos_clist, s, m);
return (TRUE);
}