约瑟夫环问题
2017-09-15 16:31
302 查看
最近遇到一个算法题, 题目描述为
已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列,求出剩下的人的编号。
按照直观的思路, 这应该是一个循环链表问题。思路大概如下
初始化链表
插入节点
遍历链表(步长为m)
遇到编号为m的节点则删除
定义节点
class Node {
public $elem; // 节点数据
public $next; // 下一个节点指针
循环链表实现
有上面的代码实现, 解决这个问题就简单了,代码如下
上面的代码过程都体现出来了, 但复杂度为O(mn).如果只需要得到最后的编号, 不需要这么复杂, 第一次报数, m出列, 则当前顺序为m+1, m+2, ...n, n-1, n-2, ...,m-1, 设为f'(n-1, m).对此重新编号则为1,2,...,n-1, 设为f(n-1, m).
则原问题f(n,m)转化为f'(n-1, m)的解, 我们只需要得出f'(n-1, m)与f(n-1, m)的关系即可。
第一个出列的人编号为m%n, 则他前面的人(m-1)%n, 后面的人为(m+1)%n, f与f'的对应关系为f'(n-1, m) = (f(n-1, m) + m)%n, 得到递推关系, 则此问题就简单了。
已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列,求出剩下的人的编号。
按照直观的思路, 这应该是一个循环链表问题。思路大概如下
初始化链表
插入节点
遍历链表(步长为m)
遇到编号为m的节点则删除
定义节点
class Node {
public $elem; // 节点数据
public $next; // 下一个节点指针
public function __construct($elem) { $this->elem = $elem; $this->next = null; } }
循环链表实现
class CircularList { public $currentNode; public $head; public function __construct(Node $node) { $this->head = $node; $this->head->next = $this->head; $this->currentNode = $this->head; } /** 在节点之后插入某节点 */ public function insert($elem, $item) { $curr = $this->find($item); $node = new Node($elem); $node->next = $curr->next; $curr->next = $node; } /** 找到某节点 */ public function find($elem) { $currentNode = $this->head; while ($currentNode->elem != $elem) { $currentNode = $currentNode->next; } return $currentNode; } /** 查找当前节点的前一节点 */ public function findPrev ($elem) { $currentNode = $this->head; while ($currentNode->next->elem != $elem) { $currentNode = $currentNode->next; } return $currentNode; } public function delete ($elem) { $node = $this->findPrev($elem); if (isset($node->next->next)) { $node->next = $node->next->next; } } /** 前进n步 */ public function advance($n) { while ($n > 0) { if ($this->currentNode->next->elem == 'head') { $this->currentNode = $this->currentNode->next->next; }else{ $this->currentNode = $this->currentNode->next; } $n--; } } /* * 列表长度 */ public function count() { $currentNode = $this->head; $count = 0; while (isset($currentNode->next) && $currentNode->next->elem != 'head') { $currentNode = $currentNode->next; $count++; } return $count; } // 遍历打印链表 public function display() { $curr = $this->head; while (isset($curr->next) && $curr->next->elem != 'head') { echo $curr->next->elem . " "; $curr = $curr->next; } } }
有上面的代码实现, 解决这个问题就简单了,代码如下
$list = new CircularList(new Node('head')); for ($i = 1; $i <= $n ; $i++) { $list->insert($i, $i == 1 ? 'head' : $i - 1); } $total = $list->count(); while ($total > $m -1 ) { $list->advance($m); $list->delete($list->currentNode->elem); $total--; $list->display(); echo "<br/><br/>"; }
上面的代码过程都体现出来了, 但复杂度为O(mn).如果只需要得到最后的编号, 不需要这么复杂, 第一次报数, m出列, 则当前顺序为m+1, m+2, ...n, n-1, n-2, ...,m-1, 设为f'(n-1, m).对此重新编号则为1,2,...,n-1, 设为f(n-1, m).
则原问题f(n,m)转化为f'(n-1, m)的解, 我们只需要得出f'(n-1, m)与f(n-1, m)的关系即可。
第一个出列的人编号为m%n, 则他前面的人(m-1)%n, 后面的人为(m+1)%n, f与f'的对应关系为f'(n-1, m) = (f(n-1, m) + m)%n, 得到递推关系, 则此问题就简单了。
function Joseph($n,$m) { $r=0; for($i=2; $i<=$n; $i++) { $r = ($r + $m) % $i; } return $r+1; }
相关文章推荐
- 约瑟夫环问题(循环链表)
- 线性表---单循环链表(约瑟夫环问题)
- 数据结构学习笔记(3)---循环链表(约瑟夫环问题)
- 约瑟夫环问题
- 约瑟夫环问题
- 约瑟夫环问题:有n个人围成一圈,顺序编号。从第1个人开始报数(从1-m报数),凡报到m的人退出圈子,问最后留下的是原来第几号的那位?
- 约瑟夫环问题 The Game about KILL
- 约瑟夫环问题递归解法的一点理解
- 约瑟夫环问题
- 剑指offer 约瑟夫环问题
- 用数组实现约瑟夫环问题
- 解题笔记(10)——约瑟夫环问题
- C++ 约瑟夫环问题
- 约瑟夫环问题
- 约瑟夫环问题
- R - Joseph经典约瑟夫环问题
- 链表解决约瑟夫环类似的猴子问题
- 使用指针链表解决约瑟夫环问题
- pku3750小孩报数问题(约瑟夫环问题)
- POJ 2886 Who Gets the Most Candies? (线段树 约瑟夫环问题变种)