约瑟夫问题

1)设编号为 1,2,3 ... n 的 n 个人围坐一圈。

2)约定编号为 k (1 <= k <= n)的人从 1 开始报数,数到 m 的那个人出列。

3)它的下一位又从 1 开始报数,数到 m 的那个人又出列,依此类推,直到所有人出列为止。

4)由此产生一个出列编号的序列。

解题思路

1)构建一个结构体 Boy

No 为 Boy 的编号。

Next 指针指向下一个 Boy。

代码如下:

// 小孩的结构体
type Boy struct {No   int  // 编号Next *Boy // 指向下一个小孩的指针
}

2)根据编号,创建一个单向环形队列。

假设 n = 5 ,即一共圈内有5个小孩,创建完成的结构体如图所示:

first 指针指向队首。

curBoy 指针指向当前要添加的新节点。

当只有一个小孩时,构成一个自循环,Next 指针指向自身,如图所示:

当不止一个小孩,新节点加入的流程如图所示:

1、构建编号为 n 的新节点 boy

boy := &Boy{No: i,
}

2、循环加入编号为 n 的新节点:

让 curBoy 的 Next 指针指向新建立的节点 boy

让 curBoy 指针指向新建立的节点 boy

让 curBoy 的 Next 指针指向头节点 first

依此类推,循环加入新节点

如图所示:

循环构建单向环形链表的代码:

// 小孩构成单向环形链表
// num:小孩的个数
// *Boy:返回单向环形链表的头指针
func AddBoy(num int) *Boy {// 第一个小孩,空节点first := &Boy{}// 因为 first 指针固定指向第一个小孩// 因此 first 指针不能移动,需要一个辅助指针// 辅助指针 curBoy ,指向当前的小孩curBoy := &Boy{}if num < 1 {fmt.Println("num 的值有误")return first}// 循环地构成单向环形链表for i := 1; i <= num; i++ {boy := &Boy{No: i,}// 第一个小孩if i == 1 {first = boycurBoy = boy// 形成一个自循环curBoy.Next = first} else {curBoy.Next = boycurBoy = boy// 最后一个节点指向头节点// 形成一个单向环形链表curBoy.Next = first}}return first}

3)根据编号构建好单向环形队列后,编写一个出列算法

1、让头指针 first 指向单向环形队列的头部。

2、让尾指针 tail 指向单向环形队列的尾部。

如图所示:

3、当 first 指针移动时,tail 指针也跟着移动相应的次数。

设置 tail 指针的目的是作为辅助指针,用来删除指定的节点

    假设:n = 5, k = 2 , m = 3 (即5个小孩围成一圈,由第2个小孩开始报数,数到3的小孩出列)。

未报数前:

由于是第 2 个小孩开始报数,因此 first 指针移动到节点 2 。

因为 tail 指针移动的次数和 first 指针一致,因此 tail 指针移动到节点 1。

如下图所示:

报数后:

由第2个小孩由1开始报数,数到 3 的小孩出列。

因此,报数后,first 指针移动到节点4。(节点2:报1,节点3:报2,节点4:报3)

tail 指针移动的次数和 first 指针一致,因此 tail 指针移动到节点 3。

如下图所示:

那么,节点4 出列。因此需要在单向环形链表上删除节点4。

因此,需要执行如下流程:

1.把 first 指针后移。

2.把 tail 指针的 Next 指向 first。

如下图所示:

由于 节点4 指向 节点5 ,但是没有任何节点指向 节点4 , 因此 节点4 会自动被系统的垃圾回收机制回收。

因此新构成的单向环形链表如图所示:

依此类推,循环出列节点,直到单向环形队列中只剩下一个节点。

循环出列结束的标志为 first == tail (因为当 first == tail 的时候,说明队列中只剩下了一个节点)

如图所示:

出列的算法如下:

// 解决约瑟夫问题的算法
// k : 约定编号为 k 的人从 1 开始报数
// m : 从1开始报数,数到 m 的人出列
func Josephu(first *Boy, k int, m int) {fmt.Println("\n小孩出圈的顺序如下:")// 单独处理空链表if first.Next == nil {fmt.Println("empty link list")return}if k > CountBoy(first) {fmt.Printf("输入的参数 %d 有误\n", k)return}// 定义一个辅助指针,帮助删除小孩tail := first// 让 tail 指向环形链表的最后一个节点(小孩)// 后面 tail 和 first 都往后挪动的时候// 才能保证 tail 一直在 fist 后面for {// 说明已经指向了最后一个节点// 退出循环if tail.Next == first {break}tail = tail.Next}// 让 first 移动到 k (编号为 k 的人开始报数)// 移动到编号 k ,只需要移动 k - 1 次for i := 0; i < k-1; i++ {first = first.Nexttail = tail.Next}// 开始数 m 下(数到 m 的人出列)// 然后就删除 first 指向的小孩for {for i := 0; i < m-1; i++ {first = first.Nexttail = tail.Next}fmt.Printf("编号为 %d 的小孩出圈\n", first.No)// 删除 first 指向的小孩first = first.Nexttail.Next = first// 如果圈中只有一个小孩if first == tail {fmt.Printf("编号为 %d 的小孩出圈\n", first.No)break}}}

完整代码

/*数据结构约瑟夫问题(Josephus problem):1)设编号为 1,2,3 ... n 的 n 个人围坐一圈2)约定编号为 k (1 <= k <= n)的人从 1 开始报数数到 m 的那个人出列3)它的下一位又从 1 开始报数,数到 m 的那个人又出列依此类推,直到所有人出列为止4)由此产生一个出列编号的序列
*/
package mainimport "fmt"// 小孩的结构体
type Boy struct {No   int  // 编号Next *Boy // 指向下一个小孩的指针
}// 小孩构成单向环形链表
// num:小孩的个数
// *Boy:返回单向环形链表的头指针
func AddBoy(num int) *Boy {// 第一个小孩,空节点first := &Boy{}// 因为 first 指针固定指向第一个小孩// 因此 first 指针不能移动,需要一个辅助指针// 辅助指针 curBoy ,指向当前的小孩curBoy := &Boy{}if num < 1 {fmt.Println("num 的值有误")return first}// 循环地构成单向环形链表for i := 1; i <= num; i++ {boy := &Boy{No: i,}// 第一个小孩if i == 1 {first = boycurBoy = boy// 形成一个自循环curBoy.Next = first} else {curBoy.Next = boycurBoy = boy// 最后一个节点指向头节点// 形成一个单向环形链表curBoy.Next = first}}return first}// 显示单向的环形链表
func ShowBoy(first *Boy) {// 单独处理空链表if first.Next == nil {fmt.Println("empty link list")return}// 创建一个辅助指针,帮助遍历curBoy := firstfor {fmt.Printf("小孩编号 = %d -> ", curBoy.No)// 退出循环的条件// 单向环形链表的最后一个节点指向头指针if curBoy.Next == first {break}curBoy = curBoy.Next}}// 统计小孩的数量
func CountBoy(first *Boy) int {// 单独处理空链表if first.Next == nil {return 0}// 创建一个辅助指针,帮助遍历curBoy := first// 计数count := 0for {count++// 退出循环的条件// 单向环形链表的最后一个节点指向头指针if curBoy.Next == first {break}curBoy = curBoy.Next}return count}// 解决约瑟夫问题的算法
// k : 约定编号为 k 的人从 1 开始报数
// m : 从1开始报数,数到 m 的人出列
func Josephu(first *Boy, k int, m int) {fmt.Println("\n小孩出圈的顺序如下:")// 单独处理空链表if first.Next == nil {fmt.Println("empty link list")return}if k > CountBoy(first) {fmt.Printf("输入的参数 %d 有误\n", k)return}// 定义一个辅助指针,帮助删除小孩tail := first// 让 tail 指向环形链表的最后一个节点(小孩)// 后面 tail 和 first 都往后挪动的时候// 才能保证 tail 一直在 fist 后面for {// 说明已经指向了最后一个节点// 退出循环if tail.Next == first {break}tail = tail.Next}// 让 first 移动到 k (编号为 k 的人开始报数)// 移动到编号 k ,只需要移动 k - 1 次for i := 0; i < k-1; i++ {first = first.Nexttail = tail.Next}// 开始数 m 下(数到 m 的人出列)// 然后就删除 first 指向的小孩for {for i := 0; i < m-1; i++ {first = first.Nexttail = tail.Next}fmt.Printf("编号为 %d 的小孩出圈\n", first.No)// 删除 first 指向的小孩first = first.Nexttail.Next = first// 如果圈中只有一个小孩if first == tail {fmt.Printf("编号为 %d 的小孩出圈\n", first.No)break}}}func main() {first := AddBoy(5)ShowBoy(first)Josephu(first, 2, 3)
}

执行结果

小孩编号 = 1 -> 小孩编号 = 2 -> 小孩编号 = 3 -> 小孩编号 = 4 -> 小孩编号 = 5 ->
小孩出圈的顺序如下:
编号为 4 的小孩出圈
编号为 2 的小孩出圈
编号为 1 的小孩出圈
编号为 3 的小孩出圈
编号为 5 的小孩出圈

约瑟夫问题(Josephus problem)详解相关推荐

  1. HDU-2993--MAX Average Problem详解

    此题是关于DP的优化问题,具体解题思路贴在后面 此题大意: 读入一列正数N,a1, a2, -, aN,以及一个数F.定义ave(i,j)=ai到aj的平均值,j-i+1>=k, 求一个最大的a ...

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

    参考:http://haoyuanliu.github.io/2016/04/18/Josephus/ 转载于:https://www.cnblogs.com/xiaoshayu520ly/p/102 ...

  3. halting problem 详解

    在讲解halting problem 问题之前,我们先来看一张图和一个小故事 图片: 图片来自网络,如有侵权请联系删除 故事: 理发师悖论:   在一个村子里只有有一个理发师,他说他只给不给自己理发的 ...

  4. 约瑟夫环问题的两种解法(详解)

    约瑟夫环问题的两种解法(详解) 题目: Josephus有过的故事:39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓.于是决定了自杀方式,41个人排成一个圆 ...

  5. Joseph Problem(解约瑟夫问题)

    今天在一个OJ上做了一个Joseph Problem(解约瑟夫问题)的题,题目不难,直接用循环链表模拟实际操作即可完成,但是用此种方法的时间太长,超时,所以我就用了一个大家对这类问题比较常用的解法-- ...

  6. C语言解决约瑟夫问题详解的代码

    C语言解决约瑟夫问题详解的代码 参考文章: (1)C语言解决约瑟夫问题详解的代码 (2)https://www.cnblogs.com/odsxe/p/10791049.html (3)https:/ ...

  7. 约瑟夫环——递推公式详解(leetcode 1823. 找出游戏的获胜者)

    约瑟夫环--递推公式详解(leetcode 1823. 找出游戏的获胜者) 约瑟夫环问题 约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知 n 个人(以编号1,2,3-n分别表示)围坐在一张圆桌周围. ...

  8. Josephus Problem的详细算法及其Python, Java语言的实现

      笔者昨天看电视,偶尔看到一集讲述古罗马人与犹太人的战争--马萨达战争,深为震撼,有兴趣的同学可以移步:http://finance.ifeng.com/a/20170627/15491157_0. ...

  9. 位运算详解+竞赛常见用法总结

    目录 一.位运算详解 二.位运算应用 1.快速幂 2.给定一个数组A, 长度为n,求下面这段程序的值 3.数数字 4.数数字 2 5.nim博弈问题: 6.树状数组 7.判断一个数x是不是2的某次方 ...

  10. Java源码详解四:String源码分析--openjdk java 11源码

    文章目录 注释 类的继承 数据的存储 构造函数 charAt函数 equals函数 hashCode函数 indexOf函数 intern函数 本系列是Java详解,专栏地址:Java源码分析 Str ...

最新文章

  1. 工信部通告:任何组织和机构不得继续实施“计算机信息系统集成企业资质认定”...
  2. Cobbler无人值守安装系统史上最细实践文档
  3. 【原】webpack--plugins,主要解释plugins干了啥
  4. Servlet读取xml文件的配置参数
  5. Sklearn fit , transform ,fit_transform
  6. 数值分析(11)-数值积分
  7. 计算机能否代替老师英语作文,雅思大作文范文:电脑不可取代老师
  8. angular directive详解
  9. Iterative混沌映射
  10. PHP 图片木马隐写方法及靶机演示
  11. 服务更新发布方式------“金丝雀、滚动更新、蓝绿部署”
  12. linux被病毒感染 CPU使用很高
  13. 马斯克坚信的“矩阵模拟”,是一种怎样“烧脑”的存在?
  14. rand函数用法整理
  15. 5000万存银行,一年利息够不够花呢?
  16. 利用加速度求解位置的算法——三轴传感器
  17. CentOS 7之ifconfig
  18. Linux之gstreamer视频编解码测试指令
  19. VS 2015 社区版(Community) 试用期(30天)之后,账户登录
  20. [书]x86汇编语言:从实模式到保护模式 -- 第16章 分页机制、平坦模型

热门文章

  1. linux查看各端口号,端口如何查看linux的端口号
  2. 调音台docker教程_超详细Docker实战教程,万字详解!
  3. 计算机毕业设计ssm毕业论文管理系统b909r系统+程序+源码+lw+远程部署
  4. 程序员进阶必备--写文档
  5. 15-Mixly模拟输出 | Mixly技巧系列
  6. 电脑右键新建没有文本文档解决办法
  7. 人脸识别系统_设计说明书
  8. meanshift算法通俗讲解
  9. PackageManager(管理应用程序包)解析
  10. request.getParameterValues()用法