关于“约瑟夫环”的php的几种写法
2015-12-16 14:04
645 查看
关于约瑟夫环 的php实现
数学和逻辑这种东西,对我而言有着深深的吸引力,原因是什么?因为我这方面比较弱。我数学和逻辑很弱,但是当初上学的时候成绩却是不错,即使这样,也不能证明我数学和逻辑学得好。正因为学得不好,所以才会着迷。。。。。。废话不多说,言归正传。
约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。通常解决这类问题时我们把编号从0~n-1,最后[1] 结果+1即为原问题的解。
关于约瑟夫环的实现,百度百科也给出了不同语言的各种算法。下面我们只针对php语言。列出三-四种算法。如果大家有更好的算法或更好的想法,一定与笔者联系哇。
约瑟夫环运作如下:
1、一群人围在一起坐成 环状(如:N)
2、从某个编号开始报数(如:K)
3、数到某个数(如:M)的时候,此人出列,下一个人重新报数
4、一直循环,直到所有人出列 ,约瑟夫环结束
第一种方法,分解此问题,按步骤实现
function getJoseph($n, $m) { $arr = range(1,$n);//给每个人设置编号 //每次遇到编号为$m的人,踢出 //$j 计数 $count = $n; $k=1; //踢人计数,用于计量从1到$m $j =1; //编号计数。用于计量从1到$n $unset = array();//已经踢出的编号 while($count>1){ //每次遇到已踢出的编号,则编号计数继续,踢人计数保持不动 while (in_array($j, $unset)){ $j++; if($j>$n)$j=1; continue 2; } //如果踢人计数与所需踢出的第$m人相同,踢 if($k==$m){ unset($arr[$j]); $unset[] = $j; $count = count($arr); $k=1; }else{ $k++; } $j++; if($j>$n)$j=1; } echo current($arr); } getJoseph(100,3);
第二种方法,同样分解此问题,按步骤实现
function getJoseph($n, $m) { $arr = range(1,$n);//给每个人设置编号 while(count($arr)>1) { for($i = 1; $i <= $m; $i++) { if(next($arr)){ //如果存在 next 元素 if($i == $m) { $k = array_search(prev($arr), $arr);//查找当前数组的key unset($arr[$k]);//当数到 m 时,使用 unset() 删除数组元素 unset($k); } } else{ reset($arr);//则数组的第一个元素充当 next 元素,组成一个环 if($i == $m) { $k =array_search(end($arr), $arr); unset($arr[$k]);//当数到 m 时,使用 unset() 删除数组元素,注意这里是 end() reset($arr);//指针复位 unset($k); } } } } return current($arr); } echo getJoseph(100, 3);
以上两个方法,第一个方法,是以所有人为基准进行循环
第二个方法,是以被踢人数为基准进行循环
两个切入点。第二个方法用到了php自带的函数current() next() prev() reset() end() array_search()等,推荐大家。
下面的方法,用到了一些数学优化的思维,在此简述一下
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是(m-1)%n) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始): k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2 并且从k开始报0。现在我们把他们的编号做一下转换: k –> 0 k+1 –> 1 k+2 –> 2 n-1 –> n-1-k 0–> n-k … … k-3 –> n-3 k-2 –> n-2 序列1: 1, 2, 3, 4, …, n-2, n-1, n 序列2: 1, 2, 3, 4, … k-1, k+1, …, n-2, n-1, n 序列3: k+1, k+2, k+3, …, n-2, n-1, n, 1, 2, 3,…, k-2, k-1 序列4:1, 2, 3, 4, …, 5, 6, 7, 8, …, n-2, n-1 变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来: ∵ k=m%n; ∴ x’ = x+k = x+ m%n ; 而 x+ m%n 可能大于n ∴x’= (x+ m%n)%n = (x+m)%n 得到 x‘=(x+m)%n 如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 —- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式: 令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f . 递推公式: f[1]=0; f[i]=(f[i-1]+m)%i; (i>1)
递归算法实现
function getJoseph($n , $m , $current = 0){ $number = count($n); $num = 1; if(count($n) == 1){ echo $n[0]; return; } else{ while($num++ < $m){ $current++ ; $current = $current%$number; } array_splice($n , $current , 1); getJoseph($n , $m , $current); } } $n = range(1,100); $m = 3; getJoseph($n , $m);
通过算法实现
define('N', 100); //总数 define('P', 1); //开始报数的位置 define('M', 3); //报数的间距 function getJoseph() { $data = range(1, N); if(empty($data)) return false; //第一个报数的位置 $start_p = (P-1); while(count($data) > 1) { //报到数出列的位置 $del_p = ($start_p + M - 1) % count($data); if(isset($data[$del_p])) { unset($data[$del_p]); } else { break; } //数组从新排序 $data = array_values($data); $new_count = count($data); //计算出在新的$data中,开始报数的位置 $start_p = ($del_p >= $new_count) ? ($del_p % $new_count) : $del_p; } echo "<br> successful num : " . $data[0] . "<br><br>"; } getJoseph();
以上四种方法实现了从接地气到高逼格的递进。给笔者本人的启示。一定要学好数学。
本文参考:
http://9iphp.com/web/php/1112.html
http://baike.baidu.com/link?url=XO4N3fCgrB8m9QlK0b7Sy0FKHdSDbYOYZ7iE_iAqld57pCPBlP69T5gVapDDJQpdzWJGfl-1hU63O3Eg2-kUda#1
http://www.acmerblog.com/joseph-problem-3394.html
相关文章推荐
- PHP接口的介绍与实现
- php自己创建TPL模板引擎之初学习
- php(2)
- PHP面向对象之重写与重载
- 欢迎使用CSDN-markdown编辑器
- getPixels
- 借助PHP的mysql_query()函数来创建MySQL数据库的教程
- 这里既是结束,也是开始
- Magento在php7下速度测试
- PHP中将数据库中的数据显示在网页
- php函数初步
- NavicatPremium4MAC
- thinkphp中的验证码的实现
- 数据模型
- php中return die exit用法梳理
- yii框架 widgets 内部传递参数使用方案
- php 实物回滚
- 通过PHP简单实例介绍文件上传
- 安装php过程中的错误和解决方式
- 前进道路上的收获历程