zoukankan      html  css  js  c++  java
  • 约瑟夫环问题

    最近遇到一个算法题, 题目描述为

    已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列,求出剩下的人的编号。

    按照直观的思路, 这应该是一个循环链表问题。思路大概如下

    1. 初始化链表
    2. 插入节点
    3. 遍历链表(步长为m)
    4. 遇到编号为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;  
    }
  • 相关阅读:
    oracle中rownum和rowid的区别和用法
    jsp中,对window对象的简单总结
    下拉列表框实现二级联动
    window.showModalDialog()的简单用法
    javascript中的正则表达式
    java实现树型结构样式
    几个数据库的驱动、连接
    Java桌面程序中设置一个软件的系统托盘
    【动态规划】求两字符串连续最大公共子串长度
    大整数相乘
  • 原文地址:https://www.cnblogs.com/mingao/p/7527094.html
Copyright © 2011-2022 走看看