当前位置: 首页 > news >正文

Golang学习-约瑟夫环问题(Josephus Problem)

约瑟夫环问题(Josephus Problem)

一、问题描述

约瑟夫环是一个经典的数学与计算机科学问题。其情景如下:

N 个人围成一圈,编号 1~N。从编号 1 开始报数,报到 M 的人出列;下一位从 1 重新开始报数,再报到 M 的人出列……如此循环,直到只剩最后一人。求最后幸存者的原始编号。

这个问题的历史可以追溯到公元 1 世纪,犹太历史学家约瑟夫(Flavius Josephus)在罗马围攻中与 40 名同胞藏身洞穴,他们决定宁可自杀也不做俘虏。约瑟夫通过数学计算站到了幸存位置——他自述的这段经历便成了这个问题的名称。

二、三种解法对比

解法时间复杂度空间复杂度特点
数组模拟法O(n × m)O(n)直观易懂,但慢
循环链表模拟法O(n × m)O(n)贴合问题本质,但同样慢
递推公式法O(n)O(1)最优解,纯数学推导

三、解法一:数组模拟法

用一个[]int表示还在圈中的人,1表示在场,0表示已出列。从头到尾循环扫描,遇到未出列的人就计数,数到 m 时标记出列。

3.1 实现思路

  1. 初始化数组,所有位置标记为 1(在场)
  2. 遍历数组,对在场的人报数
  3. 报到 m 时标记出列,计数器归零
  4. 当出列人数 = n-1 时停止,剩下那个就是幸存者

3.2 代码

packagemainimport"fmt"// JosephusArray 数组模拟法funcJosephusArray(n,mint)int{ifn<=0||m<=0{return-1}alive:=make([]bool,n)// true=在场fori:=rangealive{alive[i]=true}count:=0// 已出列人数number:=0// 报数器idx:=0// 当前下标forcount<n-1{ifalive[idx]{number++ifnumber==m{alive[idx]=false// 出列number=0count++}}idx=(idx+1)%n// 环形绕回}// 找到唯一幸存者fori,a:=rangealive{ifa{returni+1// 编号从 1 开始}}return-1}funcmain(){fmt.Println("n=41, m=2:",JosephusArray(41,2))// 19fmt.Println("n=8, m=5:",JosephusArray(8,5))// 3fmt.Println("n=5, m=3:",JosephusArray(5,3))// 4}

运行结果:

n=41, m=2: 19 n=8, m=5: 3 n=5, m=3: 4

缺点:每次循环都要跳过已出列的人,时间复杂度为 O(n × m),当 n 和 m 都很大时效率很低。

四、解法二:循环链表模拟法

用循环链表直接模拟"围成一圈"的场景,每报到 m 就删除当前节点,天然贴合问题模型。

4.1 代码

packagemainimport"fmt"typeCLNodestruct{NointNext*CLNode}// JosephusCircularList 循环链表模拟法funcJosephusCircularList(n,mint)int{ifn<=0||m<=0{return-1}// 构建循环链表:1 -> 2 -> ... -> n -> 1head:=&CLNode{No:1}cur:=headfori:=2;i<=n;i++{cur.Next=&CLNode{No:i}cur=cur.Next}cur.Next=head// 尾指头,形成环// 找到头节点的前驱(辅助删除)prev:=headforprev.Next!=head{prev=prev.Next}// 开始报数,每次报 m 个人就删除forcur.Next!=cur{// 直到只剩一个人// 报数 m-1 次(当前节点算第1个,走 m-1 步到第 m 个)fori:=1;i<m;i++{prev=cur cur=cur.Next}// 删除 cur 节点prev.Next=cur.Next cur=cur.Next}returncur.No}funcmain(){fmt.Println("n=41, m=2:",JosephusCircularList(41,2))// 19fmt.Println("n=8, m=5:",JosephusCircularList(8,5))// 3fmt.Println("n=5, m=3:",JosephusCircularList(5,3))// 4}

运行结果:

n=41, m=2: 19 n=8, m=5: 3 n=5, m=3: 4

优点:模型直观,不需要额外的标记数组
缺点:时间复杂度仍然是 O(n × m),空间复杂度 O(n)

五、解法三:递推公式法(最优解)

这是约瑟夫问题的数学精华。通过递推关系,可以做到 O(n) 时间、O(1) 空间。

5.1 推导过程

f(n, m)表示 n 个人、报数为 m 时最后幸存者的编号(从 0 开始编号)。

基本情况:当 n = 1 时,只有一个人,幸存者就是 0 号。

f(1, m) = 0

递推:当 n 个人中第一个出列的是第 m 个人(编号 m-1)后,剩下 n-1 个人。关键在于,这 n-1 个人的编号体系发生了偏移

原始编号: 0, 1, 2, ..., m-2, m-1, m, m+1, ..., n-1 出列后: m, m+1, ..., n-1, 0, 1, ..., m-2 重新编号: 0, 1, ..., n-m-1, n-m, n-m+1, ..., n-2

从原始编号到新编号的映射关系:

新编号 = (旧编号 - m) % n 旧编号 = (新编号 + m) % n

所以:

f(n, m) = (f(n-1, m) + m) % n

这就是约瑟夫问题的核心递推公式!

5.2 从 0 编号到 1 编号

上述公式假设编号从 0 开始。如果要从 1 开始编号,最终结果加 1 即可:

result = f(n, m) + 1

5.3 代码实现

packagemainimport"fmt"// JosephusRecursive 递归版本(可能栈溢出,n 大时不推荐)funcJosephusRecursive(n,mint)int{ifn==1{return0}return(JosephusRecursive(n-1,m)+m)%n}// JosephusIterative 迭代版本(推荐,O(n) 时间 O(1) 空间)funcJosephusIterative(n,mint)int{ifn<=0||m<=0{return-1}survivor:=0// f(1, m) = 0fori:=2;i<=n;i++{survivor=(survivor+m)%i}returnsurvivor+1// 转为 1~n 编号}funcmain(){fmt.Println("n=41, m=2:",JosephusIterative(41,2))// 19fmt.Println("n=8, m=5:",JosephusIterative(8,5))// 3fmt.Println("n=5, m=3:",JosephusIterative(5,3))// 4fmt.Println("n=1000000, m=3:",JosephusIterative(1000000,3))// 大规模也能秒出}

运行结果:

n=41, m=2: 19 n=8, m=5: 3 n=5, m=3: 4 n=1000000, m=3: 374433

5.4 手动验证递推过程

以 n=5, m=3 为例(编号 0~4):

isurvivor计算
10f(1)=0
2(0+3)%2 = 1f(2)
3(1+3)%3 = 1f(3)
4(1+3)%4 = 0f(4)
5(0+3)%5 = 3f(5)

最终 survivor = 3,转为 1 编号 =4。验证正确!

六、扩展:出列顺序

如果不仅要求最后幸存者,还要知道每个人的出列顺序,递推公式就不够了,需要模拟。循环链表法可以轻松记录出列顺序:

funcJosephusOrder(n,mint)[]int{ifn<=0||m<=0{returnnil}// 构建循环链表head:=&CLNode{No:1}cur:=headfori:=2;i<=n;i++{cur.Next=&CLNode{No:i}cur=cur.Next}cur.Next=head prev:=headforprev.Next!=head{prev=prev.Next}varorder[]intforcur.Next!=cur{fori:=1;i<m;i++{prev=cur cur=cur.Next}order=append(order,cur.No)prev.Next=cur.Next cur=cur.Next}order=append(order,cur.No)// 最后幸存者returnorder}
http://www.jsqmd.com/news/1266308/

相关文章:

  • windows网络适配器驱动开发-开发 WiFiCx 客户端驱动程序(八)
  • 2026 进藏避坑全攻略|7 位备案本地持证导游大盘点 - 纯玩旅游推荐官
  • 认知曲率Ω模型:量化AI与人类认知偏差风险
  • 2026 沈阳名包回收,二手奢侈品包包免费在线估价 - 讯息早知道
  • 2026年重庆推荐公考培训机构综合口碑榜单,避坑精选实力测评 - 工业品牌热点
  • 深入解析Android AVB镜像:手动验证哈希与FEC纠错实战
  • 基于YOLOv8与SAM的自动标注系统开发实践
  • AI赋能个人效率革命:7天掌握Prompt工程+自动化工作流,下周就用上!
  • PPT高级交互设计:用触发器与VBA实现Windows 7系统模拟器
  • 一文看懂哈尔滨黄金回收规则,正规门店接受市场监督,杜绝恶意压价,交易流程公开可查 - 每日生活报
  • 函数依赖(Functional Dependency, FD)是数据库理论中关系模式设计与规范化的核心概念
  • 2026年广东自建房肤感板OEM怎么选?这份优选指南助你避坑 - geo交流
  • 数组作为函数参数为什么要多传一个长度_Day10
  • 收藏保存!2026济南腕表回收全攻略,全城合规门店全覆盖 - 讯息早知道
  • 基于YOLOv5的实时抽烟检测系统设计与部署
  • 基于RAG的私有文档问答机器人开发指南
  • C++ STL容器resize()函数深度解析:内存管理与性能优化实战
  • 上海吸塑加工价格透明实力测评,2026年十大口碑榜单避坑指南 - 工业品牌热点
  • 成都二手包包出手渠道推荐,避开回收常见套路 - 生活时报
  • 想在哈尔滨回收黄金?牢记市场监管提醒,甄别正规渠道,守护自身合法权益不受损害 - 每日生活报
  • 【2024高精度库存预警白皮书】:基于千万级SKU实测数据,3类算法选型决策树首次公开
  • “数据库”(Database)是指长期存储在计算机内、有组织的、可共享的数据集合
  • 2026广州黄埔民办校排名+择校指南,避坑必看 - 服务品牌热点
  • 机器学习实战:朴素贝叶斯分类与K-Means聚类算法详解(附情感分析与客户分群案例)
  • 江阴长江沿岸房屋渗漏原因分析与2026本地防水维修要点 - 雨婺虹房屋维修
  • 发改136号文新能源入市全流程拆解|全网独家复现交易收益仿真模型 机制竞价/市场注册/现货结算全链路落地、风光储协同优化收益稳定性
  • 梦笔记20260726
  • 本地大模型不是“买卡就跑”!20年SRE亲授:如何用cgroups+Prometheus+自研成本看板,实现每token推理成本实时下钻监控
  • 国产AI算力资源池架构解析与优化实践
  • OpenClaw离线构建与部署实战:金融级自动化运维方案