约瑟夫环问题
历史
这个问题是以弗拉维奥.约瑟夫斯命名的,它是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另一个人是最后两个留下的人。约瑟夫斯说服那个人,他们向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个
约瑟夫环问题
N个人围成一圈顺序编号,从1号开始按1、2、3......顺序报数,报p者退出圈外,其余的人再从1、2、3开始报数,报p的人再退出圈外,以此类推。 请按退出顺序输出每个退出人的原序号
算法思想
这里介绍3种算法,两种直观,一种运用数学
循环链表
建立一个有N个元素的循环链表,然后从链表头开始遍历并计数,如果基数i == m,则踢出该元素,继续循环,直到当前元素与下一个元素相同时退出循环
#include <stdio.h> #include <stdlib.h> #include <string.h> struct lnode { int pos; struct lnode *next; }; /** * Description:构建循环链表 */ struct lnode* create_ring(int n) { int i; struct lnode *pre, *pcur, *phead; phead = (struct lnode *)malloc(sizeof(struct lnode)); phead->pos = 1; phead->next = NULL; pre = phead; for (i = 2; i <= n; i ++) { pcur = (struct lnode *)malloc(sizeof(struct lnode)); pcur->pos = i; pre->next = pcur; pre = pcur; } pre->next = phead; return phead; } /** * Description:约瑟夫环 */ void kickoff_ring(struct lnode *head, int p, int n) { struct lnode *pre, *pcur; pre = pcur = head; int i = 1; while (pcur != NULL) { if (i == p) { printf("%d ", pcur->pos); pre->next = pcur->next; free(pcur); pcur = pre->next; i = 1; } pre = pcur; pcur = pre->next; if (pre == pcur) { printf("%d\n", pre->pos); free(pcur); break; } i ++; } } int main() { int p, n; struct lnode *head; while (scanf("%d %d", &n, &p) != EOF) { head = create_ring(n); kickoff_ring(head, p, n); } return 0; }
数组模拟
思想跟循环链表类似,少了构建循环链表的过程
#include <stdio.h> #include <stdlib.h> int main() { int i, index, p, n, remain, delete[3001], flag[3001] = {0}; while (scanf("%d %d", &n, &p) != EOF) { remain = n; index = 0; while (remain >= 1) { for (i = 0; i < n; i ++) { if (flag[i] == 0) { // 报数 index ++; // 报p者退出圈外 if (index == p) { // 退出圈外 flag[i] = 1; // 重新报数 index = 0; delete[remain - 1] = i + 1; remain --; } } } } // 输出每个退出人的序号 for (i = n - 1; i >= 0; i --) { if (i == 0) { printf("%d\n", delete[i]); } else { printf("%d ", delete[i]); } } } return 0; }
缺陷:
用链表和数组实现约瑟夫环,有一个特点:要模拟整个游戏过程,不仅程序写起来比较复杂,而且时间复杂度为O(np),当n和p非常大的时候,几乎没有办法在短时间内得出结果,我在九度用循环链表和数组做约瑟夫环的时候,就超时了
数学推导
暂时不会
链表优化
在runtime无数次之后,我将两个模块合并就ac了,看来我对c里面指针的掌握还是差很多,直接上ac代码吧,原理一样的!
#include <stdio.h> #include <stdlib.h> #include <string.h> struct lnode { int pos; struct lnode *next; }; /** * Description:构建循环链表&&循环遍历 */ void create_ring(int n, int p) { int i; struct lnode *pre, *pcur, *phead; phead = (struct lnode *)malloc(sizeof(struct lnode)); phead->pos = 1; phead->next = NULL; pre = phead; for (i = 2; i <= n; i ++) { pcur = (struct lnode *)malloc(sizeof(struct lnode)); pcur->pos = i; pre->next = pcur; pre = pcur; } pre->next = phead; pcur = phead; while (pcur->next != pcur) { for (i = 1; i < p; i ++) { pre = pcur; pcur = pcur->next; } printf("%d ", pcur->pos); pre->next = pcur->next; free(pcur); pcur = pre->next; } printf("%d\n", pcur->pos); free(pcur); } int main() { int p, n; while (scanf("%d %d", &n, &p) != EOF) { create_ring(n, p); } return 0; } /************************************************************** Problem: 1188 User: wangzhengyi Language: C Result: Accepted Time:40 ms Memory:912 kb ****************************************************************/