问题描述:皇帝决定找出全国中最幸运的一个人,于是从全国选拔出 n 个很幸运的人,让这 n 个人围着圆桌进餐,可是怎么选择出其中最幸运的一个人呢?皇帝决定:从其中一个人从 1 开始报数,按顺序数到第 k 个数的人自动出局,然后下一个人从 1 开始报数,数到 k 的人出局……。如此直到最后只剩下约瑟夫一人,然后他就成为全国最幸运的人。请问约瑟夫最初的位置?(注:原问题略显暴力,故自创此趣味题目)

分析:把第一个开始报 1 的人标定为 1,然后按报数顺序依次标定其余的人为:2,3,……,n - 1,n。按规则进行淘汰,直到最后剩一个数字,这个数字就是约瑟夫的位置。

解决方案:

1. 模拟法(simulation)

数组模拟,时间复杂度最高为 O(n3) (当k≈n时 ) ,空间复杂度O(n)

 /********** 用数组模拟 *************/
void findNext(bool *out, int n, int &curPosition){
if(!out[curPosition]) {
int pNext = (curPosition + ) % n;
if(!out[pNext])
curPosition = pNext;
else{
curPosition = pNext;
while(out[curPosition])
curPosition = (curPosition + ) % n;
}
}else
{
while(out[curPosition])
curPosition = (curPosition + ) % n;
}
}
int josephus(int n, int k)
{
if(n < || k < ) return -;
if(n == ) return n;
bool *out = new bool[n]; /********* 记录是否出局 *********/
for(int i = ; i < n; ++i)
out[i] = false;
int current = ;
int n2 = n;
while(n2 != )
{
int cnt = k;
while(--cnt)
findNext(out, n, current);
out[current] = true;
findNext(out, n, current);
--n2;
}
delete[] out;
out = NULL;
return (current + );
}

Code

循环链表模拟:时间复杂度O(n),空间复杂度O(n)

/********** 循环链表 *************/
struct ListNode{
int val;
ListNode * next;
ListNode(int x):val(x), next(NULL) {}
}; int josephus(int n, int k)
{
if(n < 1 || k < 1)
return -1;
if(n == 1) return n;
ListNode *head = new ListNode(1);
ListNode *prior = head;
for(int i = 2; i <= n; ++i)
{
ListNode *tem= new ListNode(i);
prior->next = tem;
prior = prior->next;
}
prior->next = head;
while(head->next != head)
{
int cnt = k;
while(--cnt)
{
head = head->next;
prior = prior->next;
}
prior->next = prior->next->next;
ListNode *current = head;
head = head->next;
delete current; /*** 只释放堆内存空间,局部指针自动回收 ***/
}
return head->val;
}

 2.建模法(modeling)

使用队列建模。

 /********** 用队列(注:使用STL可简单化) *************/
bool ERROR = false;
typedef int ELEM;
struct Node{
ELEM val;
Node *next;
Node(ELEM e):val(e), next(NULL){}
};
struct queue{
queue():front(NULL), tail(NULL) {}
ELEM pop();
void push(ELEM val);
bool empty();
private:
Node *front;
Node *tail; };
ELEM queue::pop(){
if(front == NULL){
ERROR = true;
return -;
}else{
ELEM v = front->val;
front = front->next;
return v;
}
}
void queue::push(ELEM val){
Node *p = new Node(val);
if(front == NULL)
front = tail = p;
else
{
tail->next = p;
tail = tail->next;
}
}
bool queue::empty(){
if(front == NULL)
return true;
else
return false;
} int josephus(int n, int k)
{
if(n < || k < ) return -;
if(n == ) return ;
queue qu;
for(int i = ; i <= n; ++i)
qu.push(i);
int result = ;
while(!qu.empty())
{
for(int i = ; i <= k-; ++i)
qu.push(qu.pop());
result = qu.pop();
}
return result;
}

Code

 

3. 数学推理 && 动态规划

初始:0 1 ... (k-2) (k-1)  k ... (N-1)

K 出局:                    新的顺序:                         

k                              0                                                                      

...            p               ...                                                           

N-1         映              N - k - 1                                                P(x) = (x - k + N) mod N                             

0             射              N - k                                              令:y = P(x) = (x - k + N) mod N                             

1                              N - k + 1                                        则,x = (y + k - N)             mod N  

...                             ...                                                           = (y + k)                   mod N

k-2                           N - 2                                           P-1(x) = (x + k) mod N 

设 f(N,k) 为最后所得的数字,则:

f(N,k) = P-1( f(N-1,k) ) = (f(N-1,k) + k) mod N

所以有如下递推公式:

int josephus(int n, int k)
{
if(n < 1 || k < 1) return -1;
if(n == 1) return 1;
int result = 0;
for(int i = 2; i <= n; ++i)
result = (result + k) % i;
return result+1;
}

另外,简洁的递归:(不推荐,递归栈太小,容易溢出)

int josephus(int n, int k)
{
if(n < 1 || k < 1) return -1;
if(n == 1) return 1;
else
return ((josephus(n - 1, k) + k - 1) % n + 1);
}

最后,当 k = 2 时,如下公式可直接求出:

代码为:

int n = 1000;
cout<< 2*(n - pow(2.0, int(log((float)n) / log((float)2))))+1 <<endl;

约瑟夫(环)问题(Josephus problem)的更多相关文章

  1. 约瑟夫环问题(Josephus)

    约瑟夫环:用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至最后一个元素并输出该元素的值. 一.循环链表:建立一个有N个元素的循环链表,然后从链表头开始遍历并记数,如果计数值为M,则 ...

  2. 组合数学--约瑟夫环问题 Josephus

    约瑟夫斯问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题.在计算机编程的算法中,类似问题又称为约瑟夫环. 有n个囚犯站成一个圆圈,准备处决.首先从一个人开始,越过k-2个人(因为第 ...

  3. 约瑟夫问题(Josephus Problem)的两种快速递归算法

    博文链接:http://haoyuanliu.github.io/2016/04/18/Josephus/ 对,我是来骗访问量的!O(∩_∩)O~~ 约瑟夫问题(Josephus Problem)也称 ...

  4. LightOJ - 1179 Josephus Problem(约瑟夫环)

    题目链接:https://vjudge.net/contest/28079#problem/G 题目大意:约瑟夫环问题,给你n和k(分别代表总人数和每次要数到k),求最后一个人的位置. 解题思路:因为 ...

  5. 谁能笑到最后,约瑟夫环-Josephus问题求解

     一. 简述Josephus问题 N个人站成一环,从1号开始,用刀将环中后面一个人“消灭“”掉,之后再将刀递给下一个人,这样依次处理,最后留下一个幸存者. 二. 求解方法  1.  约瑟夫问题如果使用 ...

  6. 算法Sedgewick第四版-第1章基础-017一约瑟夫问题(Josephus Problem)

    /************************************************************************* * * Josephus problem * * ...

  7. Josephus环的四种解法(约瑟夫环)

    约瑟夫环 约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个 ...

  8. Josephus problem(约瑟夫问题,丢手绢问题)

    约瑟夫问题 约瑟夫环问题是一个数学应用题:已知n个人(以编号1,2,3.....,n)围坐在一张圆桌的周围.从编号为k的人开始报数,数到m的那个人出列:他的下一个人又从1开始报数,数到m的那个人又出列 ...

  9. 单向环形链表解决约瑟夫环(Josephus)问题

    一.约瑟夫环问题 Josephu 问题为:设编号为1,2,- n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那 ...

  10. Roman Roulette(约瑟夫环模拟)

    Roman Roulette Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)To ...

随机推荐

  1. 通过WebViewJavascriptBridge实现OC与JS交互

      在.m方法当中,申明一个WebViewJavascriptBridge属性: @interface ExampleAppViewController () @property WebViewJav ...

  2. WCF服务开发与调用的完整示例

    WCF服务开发与调用的完整示例 开发工具:VS2008 开发语言:C# 开发内容:简单的权限管理系统 第一步.建立WCF服务库 点击确定,将建立一个WCF 服务库示例程序,自动生成一个包括IServi ...

  3. Fragment开发计划

    Fragment是什么 Fragment正如字面意思所言是碎片,所以这是一个管理碎片时间的应用程序.目前考虑的是先在Android上实现,如果IOS的合作伙伴靠谱可以交给他做,如果不靠谱就等Andro ...

  4. 【Selenium2+Python】定位

    定位Frame driver.switch_to_frame("frameID") 多窗口切换 #获得当前窗口 nowhandle = driver.current_window_ ...

  5. ACE - Reactor源码总结整理

    ACE源码约10万行,是c++中非常大的一个网络编程代码库,包含了网络编程的边边角角. ACE代码可以分三个层次:OS层.OO层和框架层: OS层主要是为了兼容各个平台,将网络底层API统一化,这一层 ...

  6. 浅谈C#中的接口和抽象类

    C#中接口为"其他方面互不相干"的类型提供公共的服务和特征:C#中class只支持但继承,使用接口却支持多继承,例如:C#中System.String是从System空间中的4个i ...

  7. 自动机理论、语言和计算导论 by John E. Hopcroft

    计算理论是计算机应用的基础,理论和应用缺一而不可. ---- 目录 ---- C01 自动机 C02 有穷自动机 C03 正则表达式与正则语言 C04 正则语言的性质 C05 上下文无关文法及上下文无 ...

  8. sublime 的使用

    常见问题: ctrl+`快捷见弹不出console怎么办! 解决方法: 估计适合电脑其他软件的快捷键冲突了,在sublime里面重新设置下快捷键 点击菜单中Preferences下面的Key Bind ...

  9. 帝国CMS灵动标签e:loop

    头条调用方法 1 [e:loop={'selfinfo',5,13,0,'firsttitle=2'}]<a href="<?=$bqsr[titleurl]?>" ...

  10. 在线聊天室的实现(1)--websocket协议和javascript版的api

    前言: 大家刚学socket编程的时候, 往往以聊天室作为学习DEMO, 实现简单且上手容易. 该Demo被不同语言实现和演绎, 网上相关资料亦不胜枚举. 以至于很多技术书籍在讲解网络相关的编程时, ...