类似约瑟夫的问题又称为约瑟夫环。又称“丢手绢问题”。
这个问题来自于这样的一个关于著名犹太历史学家 Josephus传说: 在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
约瑟夫问题是个有名的问题,比如6个人围一个圈,从第一个人开始123123的报数,最后一个报完了循环到第一个继续报数,报到数字3的人就退出游戏。这样退出的顺序就是: 3 6 4 2 5,最后剩下1一个人。
解决问题:
就像题中描述的一样直接翻译成代码是(拯救约瑟夫的代码):
1 #include<iostream> 2 using namespace std; 3 const int maxn = 1e6 + 10; 4 main() 5 { 6 bool a[maxn] = {0}; 7 int n, m, i, f = 0, t = 0, s = 0; 8 cin >> n >> m; 9 do 10 { 11 ++t;//逐个枚举圈中的所有位置 12 if(t > n) 13 t = 1;//数组模拟环状,最后一个与第一个相连 14 if(!a[t]) 15 s++;//第t个位置上有人则报数 16 if(s == m)//当前报的数是m 17 { 18 s = 0;//计数器清零 19 cout << t << " ";//输出被杀人编号 20 a[t] = 1;//此处人已死,设置为空 21 f++;//死亡人数+1 22 } 23 } 24 while(f != n - m + 1);//直到死亡得剩下他们两个人为止 25 cout << " 死亡人数:" << f << " " << endl; 26 //最后就剩下16和31没有被杀 27 }
考虑到时间复杂度的问题,我们有下面一种改良的做法(约瑟夫递推算法):
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 //一圈一圈的循环的来回的转,最后剩一个人 5 int main() { 6 int n, m, f = 0; 7 cin >> n >> m; 8 for (int i = 1; i <= n; i++) f = (f + m) % i; 9 cout << f + 1 << endl; 10 //输出最后活着的一个人. 11 return 0; 12 }
猴子选王
题意:一堆猴子都有编号,编号是1,2,3 ...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第1开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。
解题思路<肯定比百度上面丰富>:
①、单向循环链表实现:
1 #include <stdio.h> 2 #include <malloc.h> 3 #define LEN sizeof(struct monkey)//定义structmonkey这个类型的长度 4 struct monkey 5 { 6 int num; 7 struct monkey *next; 8 }; 9 struct monkey *create(int m) 10 { 11 struct monkey *head, *p1, *p2; 12 int i; 13 p1 = p2 = (struct monkey*)malloc(LEN); 14 head = p1; 15 head->num = 1; 16 for(i=1, p1->num = 1; i < m; i++) 17 { 18 p1 = (struct monkey*)malloc(LEN); 19 p1->num = i + 1; 20 p2->next = p1; 21 p2 = p1; 22 } 23 p2->next = head; 24 return head; 25 } 26 struct monkey *findout(struct monkey *start, int n) 27 { 28 int i; 29 struct monkey *p; 30 i = n; 31 p = start; 32 for(i = 1; i < n - 1; i++) 33 p = p->next; 34 return p; 35 } 36 struct monkey *letout(struct monkey *last) 37 { 38 struct monkey *out, *next; 39 out = last->next; 40 last->next = out->next; 41 next = out->next; 42 free(out); 43 return next; 44 } 45 int main() 46 { 47 int m, n, i, king; 48 struct monkey *p1, *p2; 49 printf("请输入猴子的个数m: "); 50 scanf("%d", &m); 51 printf("每次数猴子的个数n: "); 52 scanf("%d", &n); 53 if(n == 1) 54 { 55 king=m; 56 } 57 else 58 { 59 p1 = p2 = create(m); 60 for(i = 1;i < m; i++) 61 { 62 p2 = findout(p1, n); 63 p1 = p2; 64 p2 = letout(p1); 65 p1 = p2; 66 } 67 king = p2->num; 68 free(p2); 69 } 70 printf("猴王的编号是:%d ", king); 71 return 0; 72 }
单向链表的尾节点的下一个练到头节点上,这样实现循环,然后就判断删除即可。
②、数组模仿链表--有点并查集的思想:
1 #include<stdio.h> 2 #include<malloc.h> 3 int main() 4 { 5 int *monkey, i, node, n, m; 6 scanf("%d%d",&n, &m); 7 monkey = (int*)malloc(sizeof(int)*(n+1)); 8 for(i=1;i<=n;i++)//初始化圈 9 { 10 monkey[i] = i + 1;//i表示编号为i的猴, monkey[i]的值表示编号为i的猴的下一个猴的编号 11 } 12 monkey[n] = 1;//编号为n的下一个猴的编号是1 13 node = 1; 14 printf ("The murdered monkey are: "); 15 while(node != monkey[node])//当这个圈中只剩下一个猴的时候跳出循环. 16 { 17 for(i = 1; i < m - 1; i++) //找出念m的那只猴 18 { 19 node = monkey[node]; 20 } 21 printf("%d ", monkey[node]);//输出退出的猴编号 22 monkey[node] = monkey[monkey[node]];//修改退出的前一个猴的monkey[node]为退出的后一个猴的编号 23 node = monkey[node];//这句话中的node是退出的猴的后一个猴 24 } 25 printf(" The king is: %d ", node);//输出最终大王的的编号 26 return 0; 27 }
创建数组的时候下标从1开始填数,1上面填2,2上面填3,...,最后n上面填1,这样达到循环的效果,然后以monkey[i] = i;为条件进行猴子的出队操作。
③、数组实现:
1 //猴子选大王问题(约瑟夫环问题) 2 #include <stdio.h> 3 #include <string.h> 4 #include <stdlib.h> 5 /* 6 * 7 * 屏蔽的代码部分为测试使用. 8 * 为使删除的过程更加明确. 9 * 10 */ 11 int fre (char mok[],int k) 12 { 13 int i; 14 15 /*printf(" 猴子编号: "); 16 for(i = 0; mok[i] != ' '; i++) 17 printf("%d ", mok[i]);//输出为踢出之前的编号,测试用 18 */ 19 20 21 for(i = k; mok[i] != '