您的位置:首页 > 编程语言 > PHP开发

关于“约瑟夫环”的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: