什么是约瑟夫问题
约瑟夫问题又被称为约瑟夫环,他起源于1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。 我们这个问题略有变化,从围成标有记号1到n的圆圈的n个人开始,每隔1一个删去一个人,知道只有一个人幸存
暴力模拟
最简单的方法是用暴力跑模拟,尽管会耗费很多时间甚至空间,但它显然是有效的。
第一种模拟方法:用一个vis数组(一维)标记这个人是否还活着,把2当作是起点(如果n等于0输出0,如果n等于1输出1,如果n小于0输出-1代表不可能)用一个变量cur指向它,先把当前指向的人杀死然后cur再指向下一个要被杀死的人,一个cnt变量代表存活人数,初始化为n,当存活人数等于1时结束该过程,输出当前指向的编号
算法性能分析:这个方法实现起来比较简单,但无论是时间还是空间复杂度都很高,每次寻找都需要花费O(N)的时间去查找下一个将要被杀死的人在哪,而且要有一个大小为n的数组,这个数组不会随着算法的推进而减小,查找的时间却会随算法的推进而增加,所以这个程序的效率很低,但如果n的范围很小,出于容易编写的目的这个算法也是很合适的
C/C++参考代码
#include <stdio.h>
#include <string.h>
#define N 1100
int n, cur, cnt;
int vis[N];
int main() {
while (scanf("%d", &n) != EOF) {
if (n < 0) puts("-1");
else if (n == 0) puts("0");
else if (n == 1) puts("1");
else {
cur = 1; cnt = n; //下标从0开始,故下标比位序小1
memset(vis, 0, sizeof(int) * (n + 10));
while (cnt != 1) {
vis[cur] = 1;
cnt--;
while (vis[cur]) cur = (cur + 1) % n; //下一个活着的人
cur = (cur + 1) % n;
while (vis[cur]) cur = (cur + 1) % n;
}
printf("%d\n", cur + 1);
}
}
return 0;
}
链表模拟法:链表模拟法原理与第一种模拟方法差不多,只不过上面用数组这里用链表
算法性能分析:就复杂度来说链表模拟法与上面的方法是一样的,但实际上由于链表是变长的所以它的效率要比第一种要高一些,但对于一些数据规模比较大的问题仍然无法解决
C/C++参考代码
#include <stdio.h>
#include <stdlib.h>
//循环链表的结构体定义
typedef struct LNode {
int id;
struct LNode *next;
}LNode, *LinkList;
LinkList create(int n) { //创建一个长度为n的循环链表
LinkList list = (LinkList)malloc(sizeof(LNode));
list->id = 1; list->next = list;
LinkList p = list;
for (int i = 2; i <= n; ++i) {
LinkList q = (LinkList)malloc(sizeof(LNode));
q->id = i;
q->next = p->next;
p->next = q;
p = p->next;
}
return list;
}
int main() {
int n;
while (scanf("%d", &n) != EOF) {
LinkList p = create(n);
while (p->next != p) { //即只剩下一个元素
LinkList q = p->next; //删除被杀死的人
p->next = q->next;
free(q);
p = p->next;
}
printf("%d\n", p->id);
}
return 0;
}
递归求解
分析:首先我们用J(n)来表示有n个人时幸存者的号码,假设一开始有2 n个人,则经过第一轮后剩下的是1, 3,5,···, 2n - 3, 2n - 1这时还有n的人,与1,2, 3,···,n比较可以发现他们之间存在映射关系2 * i - 1,后面的序列通过这个映射关系可以得到前面的序列,所以我们可以进一步推出J(2 n) = 2 * J(n) - 1。
同理可得到n为奇数时,j(2 * n + 1) = 2 * J(n) + 1
再将J(1) = 1带入可以得到以下递归式:
J(1) = 1
J(2 * n) = 2 J(n) - 1, n >= 1
J(2 * n + 1) = 2 J (n) + 1, n >= 1
时间复杂度:O(logN)
C/C++参考代码
#include <stdio.h>
typedef long long ll;
ll cal(ll n) {
if (n == 1) return 1;
if (n & 1) return 2 * cal(n / 2) + 1;
else return 2 * (cal(n / 2)) - 1;
}
int main() {
ll n;
while (scanf("%lld", &n) != EOF) {
printf("%lld\n", cal
(n));
}
return 0;
}