PHP递归

递归

递归是一种函数调用自身的机制。

递归必须要有边界条件,也就是递归出口(退出递归)

递归前进段和递归返回段,也就是最后得到的值

当边界条件不满足时,递归前进;当边界条件(递归出口)满足时,递归返回。

PHP的递归非常消耗性能,故而在PHP中尽量避免使用递归


例1:斐波那契数列1,1,2,3,5,8,13,21......

从上述数列中我们得出结论f(n)=f(n-1)+f(n-2)

function fbnq($n){
      if($n==1 || $n==2){
           return 1;
      }else{
           return fbnq($n-1)+fbnq($n-2);
      }
}

例2:目录的递归遍历

function loop_dir($path){
      $dh = opendir($path);
      while($file=readdir($dh) !== false){
            $current_file = $path.DIRECTORY_SEPARATOR.$file;
            if($file == '.' || $file == '..'){
                 continue;
            }elseif(is_dir($current_file)){
                  echo 'Directory '.$current_file.':<br>';
                  loop_dir($current_file);
            }else{
                  echo 'File in Directory '.$path.': '.$file.'<br>';
            }
      }
      closedir();//不加参数,关闭的是打开的最近打开的句柄,这样就保证函数结束时,关闭所有的句柄
}


例3:无限分类

function limitless_class($classes,$pid){
      foreach($classes as $class){
            if($class['pid'] == $pid){
                 $class['children'] = limitless_class($classes,$class['id']);//递归获取子集
                  if($class['children'] == null){
                       unset($class['children']);
                  }
            }
            $tree[] = $class;
      }
      return $tree;
}
非递归无限分类,请查阅 循环无限分类


例4:约瑟夫环

       罗马人攻占了桥塔帕特,41个人藏在一个山洞中躲过了这场浩劫。这41个人中,包括历史学家Josephus(约瑟夫)和他的一个朋友。剩余的39个人为了表示不向罗马人屈服,决定集体自杀。大家制定了一个自杀方案,所有这41个人围成一个圆圈,由第一个人开始顺时针报数,每报数为3的人就立刻自杀,然后再由下一个人重新开始报数,仍然是每报数为3的人就立刻自杀……,直到所有的人都自杀身亡为止。 
  约瑟夫和他的朋友并不想自杀,于是约瑟夫想到了一个计策,他们两个同样参与到自杀方案中,但是最后却躲过了自杀。请问,他们是怎么做到的? 

循环

第一步 抽象成一个队列添加元素到另一个队列中 
假设队列A中有n个元素,队A不断进行出队操作,每次元素出队后都判断元素是否符合添加到队B的条件(结合约瑟夫环问题就是:如果是3的倍数,就不符合入队的条件) 
第二步 改进第一步 
可以认为是一个队S,这个队S在不断进行出队和入队的操作(就是出队的元素再次添加到队尾),如果出队元素为不符合再次入队的条件就踢出.

function ysf($n,$m)
    {
        $i=0;
        $arr = range(1,$n);//创建:含有100个值的数组
        while(count($arr)>=$m) {
            ++$i;
            //不断出队(即删除数组的第一个元素)
            $survice = array_shift($arr);
            if($i%$m<>0){
                //结合约瑟夫环问题
                //报到3就自杀(从队列中踢出),否则等待下一次的审判(再次添加到队尾)
                array_push($arr, $survice);
            } elseif($i>count($survice)){$i=0;}#这句代码加不加都无所谓,便于理解
        }
        return $arr;
    }
    //是不是很好奇 约瑟夫将他和它的朋友安排在哪个位置,逃过一死的呢?
    //调用 输出 看看
    print_r( ysf(41,3) );

若只保留一个人,则上面函数存在缺陷

//n只猴子,每隔m剔除一只猴子
function getMonkeyKing($n,$m)
{
	$monkey = range(1,$n);//生成初始数组
	$c = 1;
	while(count($monkey) >1)//若剩余的猴子大于1,则需要继续筛选
	{
		$current = array_shift($monkey);//每次都把数组中第一个取出来
		if( $c != $m )//如果累计基数不等于$m
		{
			array_push($monkey,$current);//把取出来的第一个数排到数组尾部
			$c++;//数组累计数累加
		}else{//如果累计计数等于$m
			$c=1;//取出的第一个数不会被放到数组尾部,即被从数组中删除,且累计数重置为1
		}
	}
	echo $monkey[0].' is the king';//最后数组中剩下的猴子即猴王
}
getMonkeyKing(7,3);

递归算法:

function kill_monkey($monkeys,$m,$current=0{
      $number = count($monkeys);
      $num = 1;
      if($number == 1){
           echo $monkey[0].' is the King';
           return ;
      }else{
           while($num++<$m){
                $current++;
                $current = $current%$number;
           }
           echo $monkey[$current].' is out';
           array_splice($monkeys,$current,1);
           kill_monkey($monkeys,$m,$current);
      }
}
$monkeys = range(1,10);//10只猴子
$m=3;
kill_monkey($monkeys,$m);

指针

function getKingMonkey($n, $m)
{
    $a = array();//声明内部数组
    for($i = 1; $i <= $n; $i ++)
    {
        $a[$i] = $i;//这一步是对号入座
    }
    reset($a);//为了严谨,我们来一个 reset() 函数,其实也可以省去
    while(count($a) > 1)//主循环开始,这里使用的判别条件是数组元素的个数等于 1 的时候停止循环
    {
        for($counter = 1; $counter <= $m; $counter++)//嵌套的 for 循环,用来“踢出”数到 m 的猴子
        {
            if(next($a)){//如果存在 next 元素
                if($counter == $m)
                {
                    unset($a[array_search(prev($a), $a)]);//当数到 m 时,使用 unset() 删除数组元素
                }
            }
            else//如果不存在 next 元素
            {
                reset($a);//则数组的第一个元素充当 next 元素
                if($counter == $m)
                {
                    unset($a[array_search(end($a), $a)]);//当数到 m 时,使用 unset() 删除数组元素,注意这里是 end()
                    reset($a);//记得让数组内部指针“归位”
                }
            }
        }
    }
    return current($a);
}
线性表应用:

每个猴子出列后,剩下的猴子又组成了另一个子问题。只是他们的编号变化了。第一个出列的猴子肯定是a[1]=m(mod)n(m/n的余数),他除去后剩下的猴子是a[1]+1,a[1]+2,…,n,1,2,…a[1]-2,a[1]-1,对应的新编号是1,2,3…n-1。设此时某个猴子的新编号是i,他原来的编号就是(i+a[1])%n。于是,这便形成了一个递归问题。假如知道了这个子问题(n-1个猴子)的解是x,那么原问题(n个猴子)的解便是:(x+m%n)%n=(x+m)%n。问题的起始条件:如果n=1,那么结果就是1。

function yuesefu($n,$m) {  
    $r=0;  
    for($i=2; $i<=$n; $i++) {
        $r=($r+$m)%$i;  
    }
    return $r+1;  
}  

例5:二分查找

1,二分查找需要数组是一个有序的数组

2,取数组中间的值,即下标为$mid =floor(($start+$end)/2)与所需查找的值比较

3,若比中间值小,则说明所需查找的值在前半部分,此时需要再次二分,则此时的结束位置为$m-1(已经与位置为$m的值比较)

4,若比中间值大,则说明所需查找的值在后半部分,此时需要再次二分,则此时的开始位置为$m+1(已经与位置为$m的值比较)

5,一直二分,直至找到或者找不到

循环实现:

function binary_search($target,$arr){
      $start = 0;
      $end = count($arr)-1;
      while($start<$end){
            $mid = floor(($start+$end)/2);
            if($arr[$mid] == $target){
                 return $mid;
            }elseif($arr[$mid] > $target){
                 $end = $mid-1;
            }else{
                 $start = $mid+1;
            }
      }
      return false;
}
递归实现:

function binary_search_recursive($target,$arr,$start,$end){      
      if($start<=$end){
           $mid = floor(($start+$end)/2);
           if($arr[$mid] == $target){
                return $mid;
           }elseif($arr[$mid] > $target){
                $end = $mid-1;
                return binary_search_recursive($target,$arr,$start,$end);//此处必须要有return,否则函数会往下运行,递归函数仅仅得到一个返回值,必须要用return 结束函数
           }else{
                $start = $mid+1;
                return binary_search_recursive($target,$arr,$start,$end);
           }
      }
      return false;
}

例6:猴子吃桃:每天吃一半,有多吃一个,第10天时只剩一个桃子。

由题意我们我们知道第n天的苹果数量为f(n)=f(n-1)-(f(n-1)/2+1)即f(n)=2*(f(n+1)+1);

function total_apple($n){
      if($n == 10){
             return 1; 
      }else{
             return 2*(total_apple($n+1)+1);
      }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值