问题来源:
据说是新浪的一个面试题,具体题目如下:
一群猴子排成一圈,按1,2,...,n依次编号。
然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数, 再数到第m只,在把它踢出去...,
如此不停的进行下去, 直到最后只剩下一只猴子为止,那只猴子就叫做大王。
要求编程模拟此过程,输入m、n, 输出最后那个大王的编号。
其实这是一个典型的约瑟夫环的问题,下面给出几种解法:
解法一:普通循环法
<?php
/**
* 猴王问题普通循环算法
* 将所有猴子编号放入一个数组
* 开始循环数组
* 判断是否只剩一个猴子,如果是,则猴王诞生,结束循环
* 猴王未诞生,继续往下执行
* 判断是否到第m个猴子了,如果到,踢出猴子,同时计数器归0,如果不到,不做任何操作,继续循环
* 数组循环结束了,应该重置数组,继续下一轮的循环
*
*/
function kickMonkey($n, $m) {
$monkey = range(1, $n); // 给猴子编号,生成数组
$i = 0;
while(list($key, $val) = each($monkey)) { // each - 返回数组中当前的键/值对并将数组指针向前移动一步
if (count($monkey) == 1) { // 剩最后一个猴子,你就是猴王了
echo $val . '成为猴王了<br />';
exit;
}
if (++$i == $m) {
echo $monkey[$key] . '出局<br />';
unset($monkey[$key]);
$i = 0;
}
if (!current($monkey)) { // 循环到头了,重置数组
reset($monkey);
}
}
}
kickMonkey(9,5);
?>
得到结果
5出局
1出局
7出局
4出局
3出局
6出局
9出局
2出局
8成为猴王了
解法二:递归法
<?php
/**
* 猴王问题递归算法
* 递归方法的关键点是需要引入一个计数器,计数器为当前开始数的第一个猴子的索引号(注意,非猴子自身的号码)
* 老样子,先将猴子编号塞入数组待用
* 判断是否只剩一个猴子,如果是,则猴王诞生,递归结束
* 获取要出局的猴子的索引号
* 将猴子踢出数组,重置猴子索引,开始递归
*/
function kickMonkey($monkey, $m, $index = 0) {
$count = count($monkey);
if ($count == 1) {
echo current($monkey) . '成为猴王了<br />';
exit;
}
/*
* 获得踢出的猴子索引号
* 每次踢出下一个猴子的编号为当前踢出猴子编号+$m-1,如果数组到头,则重头开始数
* 即第一次踢出索引为(0+5-1)% 9 == 4的猴子
* 第二次踢出 (4+5-1)%8 == 0 的猴子
* 第三次踢出(0+5-1)%7 == 4的猴子
*/
$index = ($index + $m - 1) % $count;
echo $monkey[$index] . "出局<br />";
unset($monkey[$index]);
kickMonkey(array_values($monkey), $m , $index);
}
$monkey = range(1, 9);
kickMonkey($monkey, 5);
?>
5出局
1出局
7出局
4出局
3出局
6出局
9出局
2出局
8成为猴王了
解法三:数学模拟型
<?php
function kickMonkey($n, $m) {
$index = 0;
for ($i = 2; $i<=$n; ++$i) {
$index = ($index + $m) % $i;
}
echo ($index + 1) . '成为猴王了!';
}
kickMonkey(9,5);
?>
得到结果
8成为猴王了