PHP 解迷宫之 G + H 最小

A*搜索算法。保证了走n步时 G + H ,是最小的。会有误差。实验了几次发现,当点比较密集时n在3-5左右,误差较小。点比较分散时,n>=7,误差较小,n越大,进行的运算越慢。 F = G + H,G = 从起点 A 移动到指定方格的移动代价,H = 从指定的方格移动到终点 的估算成本。 还有待优化 , 比如解决绕远路问题, 让它回来~~

体验地址

原理

走几步回头看看哪个短,然后继续走,不断重复,直到到达终点

$stepCount 大于10 走起来就很吃力了,理论上$stepCount越大越精确,但也越吃内存

1

代码可直接运行

php -S localhost:8888

访问 localhost:8888/xxx.php

<?php

$n=20;

$positions=[];
for ($i=0;$i<=$n;$i++){
    for ($j=0;$j<=$n;$j++){
        $positions[] =[$j,$i] ;
    }
}

//开始的点
$startDot=[0,0];
//已经放置的点
$placedDot=[];
//不可放置的点
$notPlacedDot=notPlacedDot($positions,$n);
$placedDot[]=$startDot;

//所有穿过的点
list($lineDot,$finishDot)=line($positions,$startDot,$placedDot,$notPlacedDot,$n);

//平面上的所有迷宫点
$mazePositions=mazePosition($positions,$lineDot);

$line=[];//最终结果
$canReOpen=[];//库存
$startDot=[0,0];//开始的点
$line[]=[0,0];
$count=1;//默认值达到 $stepCount重置
$nextStep=[];//保存改点走$stepCount步有多少种可能
$extraNotPlaceDot=[];//不可放置的点
$stepCount=3;//每次前进几步进行比较
$shortLine=getShortLine($line,$canReOpen,$startDot,$mazePositions,$notPlacedDot,$finishDot,$n,$count,$nextStep,$extraNotPlaceDot,$stepCount);

echo "解迷宫";
echo getHtml($positions,$lineDot,$shortLine,$n);
echo "<br>";
echo "迷宫路线";
echo getAllHtml($positions,$lineDot,$n);
//echo getHtml($positions,$lineDot,$n);

/**
 * 生成迷宫路线
 * @param $positions
 * @param $startDot
 * @param $placedDot
 * @param $notPlacedDot
 * @param $n
 * @return array
 */
function line($positions,$startDot,$placedDot,$notPlacedDot,$n){

    $dotWrapper=GetDotWrappers($startDot);
    $key=rand(0,3);
    $nextDot =  $dotWrapper[$key];

    $i=0;
    $notPlacedDotAll =array_merge($notPlacedDot,[]);
    while (in_array($nextDot,$placedDot)||in_array($nextDot,$notPlacedDotAll)){

        $i++;
       if(badDot($nextDot,$placedDot,$notPlacedDot,$n)){//是否是坏点

           $notPlacedDotAll[]=$nextDot;

          $startDot=$placedDot[count($placedDot)-1];
           $dotWrapper=GetDotWrappers($startDot);
           $key=rand(0,3);
           $nextDot =  $dotWrapper[$key];

          if(badDot($startDot,$placedDot,$notPlacedDotAll,$n)){//本身是否是坏点
              $notPlacedDotAll[]= array_pop($placedDot);

              $startDot=$placedDot[count($placedDot)-1];
              $dotWrapper=GetDotWrappers($startDot);
              $key=rand(0,3);
              $nextDot =  $dotWrapper[$key];

          }

        }else{
            $key=rand(0,3);
            $nextDot =  $dotWrapper[$key];

        }
        if($i>4&&count($placedDot)>1){//循环大于3次 还没找到去除该点
            $i=0;
            $notPlacedDot[]= array_pop($placedDot);
            $startDot=$placedDot[count($placedDot)-1];
            $dotWrapper=GetDotWrappers($startDot);
            $key=rand(0,3);
            $nextDot =  $dotWrapper[$key];

        }

    }

    $placedDot[]=$nextDot;

   if($nextDot[0]>=$n&&$nextDot[1]>0&&$nextDot[1]<=$n){
       return [$placedDot,$nextDot];
   }
   return line($positions,$nextDot,$placedDot,$notPlacedDot,$n);
}

/**
 * 所有迷宫的点
 * @param $positions
 * @param $lineDot
 * @return array
 */
function mazePosition($positions,$lineDot){

    $mazePositions=[];
    foreach ($positions as $position){

            if(in_array($position,$lineDot)){
                $mazePositions[]=$position;
            }
    }
    return $mazePositions;
}

/**
 *  计算最短路线
 *
 * 迷宫平面坐标系
 * @param $line
 * 待用的点
 * @param $canReOpen
 * 开始的点
 * @param $startDot
 * 所有迷宫的可走的点
 * @param $mazePositions
 * 不能放置的点比如平面坐标系外的点
 * @param $notPlacedDot
 * 迷宫终点
 * @param $endDot
 * 没用到坐标系的宽
 * @param $n
 * 初始值默认1 最终会达到 $stepCount后会重置
 * @param $count
 * 存放一个点的走 $stepCount 步所有可能性类似于这样的数组 json_decode({"0":[1,0],"10":[[1,1]],"11":[[1,2],[2,1]],"12":[[2,2],[0,2]],"22":[[3,2],[3,2]],"02":[[0,3]],"21":[[2,2],[3,1]],"31":[[3,2],[3,0]]}',true)
 * @param $nextStep
 * 走的过程中不能放置的点
 * @param $extraNotPlaceDot
 * @param $stepCount
 * @return array
 */
function getShortLine($line,$canReOpen,$startDot,$mazePositions,$notPlacedDot,$endDot,$n,$count,$nextStep,$extraNotPlaceDot,$stepCount){

    $dotWrapper=GetDotWrappers($startDot);  //一个点的上下左右点
    $allOpen = array_reduce($canReOpen, 'array_merge', array()); //待用点
    $arr=[];//周围符合条件的点
    $notPlacedDotAll =array_merge($notPlacedDot,$extraNotPlaceDot);
    foreach ($dotWrapper as $dot){
        if(in_array($dot,$mazePositions)&&!in_array($dot,$line)&&!in_array($dot,$allOpen)&&!in_array($dot,$notPlacedDotAll)){
            $arr[distance($dot,$endDot)][]=$dot;
        }
    }

    if($count==1){//首次进入父节点

        if($startDot==$endDot){
            return $line;
        }
        if(empty($arr)){//死点
            array_pop($line);
            $extraNotPlaceDot[]=$startDot;
            $nextStartDot = $line[count($line)-1];

        }else{

            ksort($arr);//按距离大小升序排序
            $arr = array_reduce($arr, 'array_merge', array());
            $canReOpen=[];
            if(!empty($arr)){
                $canReOpen[] = array_values($arr);
            }
            $nextSteps=[];//父节点走$stepCount步,周围所有可能走的点,是一个二维数组 类似于这样json_decode([{"0":[1,0],"10":[[1,1]],"11":[[1,2],[2,1]],"12":[[2,2],[0,2]],"22":[[3,2],[3,2]],"02":[[0,3]],"21":[[2,2],[3,1]],"31":[[3,2],[3,0]]}]',true)
            $count++;
            do{//求周围点的可能
                $nextStep=[];
                $nextStartDot=array_shift($arr);//依次取出最小距离的作为开始点
                if($nextStartDot==$endDot){
                    array_push($line,$nextStartDot);
                    return $line;
                }
                $nextStep[0]=$nextStartDot;//父顶点
                //调用 getShortLine 进入到if($count==1){}else{} 的else 代码块。求走$stepCount的所有可能的点
                $nextSteps[] = getShortLine($line,$canReOpen,$nextStartDot,$mazePositions,$notPlacedDot,$endDot,$n,$count,$nextStep,$extraNotPlaceDot,$stepCount);

            }while(!empty($arr));

            $result=[];
            foreach ($nextSteps as $step){
                if(isset($step[0])){
                    $ar=[];
                    $item=$step[0];
                    unset($step[0]);
                    $parent=[];
                    $result=linkTable($result,$step,$item,$item,$ar,$parent);//将结果转化为链表的结构
                }
            }
            $nextSteps=$result;
            if(empty($nextSteps)){
                array_pop($line);
                $extraNotPlaceDot[]=$startDot;
                $nextStartDot = $line[count($line)-1];
            }else{
                $full=[];
                $notFull=[];
                foreach ($nextSteps as $k1=>$temp){
                    if(count($temp)==$stepCount){//走完$stepCount的点
                        $full[$k1]=$temp;
                    }else{//没走完$stepCount的点
                        $notFull[$k1]=$temp;
                    }
                }
                if(!empty($full)){//优先使用走完$stepCount步的点
                    $temps=[];
                    foreach ($full as $key=>$tmp){
                        $end=$tmp[count($tmp)-1];
                        $distance=distance($end,$endDot);
                        $temps[$distance]=$tmp;
                    }
                }else{
                    $temps=[];
                    foreach ($notFull as $key=>$tmp){
                        $end=$tmp[count($tmp)-1];
                        $distance=distance($end,$endDot);
                        $temps[$distance]=$tmp;
                    }
                }

                ksort($temps);//按距离大小升序排序
                $steps=array_shift($temps);//取出最近的一个组点
                $nextStartDot=$steps[count($steps)-1];//最后一个点作为开始点
                $line=array_merge($line,$steps);
                if($nextStartDot==$endDot){
                    return $line;
                }
            }

        }
        //继续下一个点重复以上步骤
        $count=1;
        return getShortLine($line,$canReOpen,$nextStartDot,$mazePositions,$notPlacedDot,$endDot,$n,$count,$nextStep,$extraNotPlaceDot,$stepCount);
    }else{//求每一个父顶点下的所有可能点

        if($startDot==$endDot){
            return $nextStep;
        }
        if($count<=$stepCount){
            if(empty($arr)){//死点
                $extraNotPlaceDot[]=$startDot;//这个点不能放
                foreach ($nextStep as $key=>&$next){//把这个点移出去, $nextStep 属于结构类似与这样 json_decode({"0":[1,0],"10":[[1,1]],"11":[[1,2],[2,1]],"12":[[2,2],[0,2]],"22":[[3,2],[3,2]],"02":[[0,3]],"21":[[2,2],[3,1]],"31":[[3,2],[3,0]]}',true)
                    if($startDot==$next){
                        unset($nextStep[$key]);
                        continue;
                    }
                    if(in_array($startDot,$next)){
                        unset($next[array_search($startDot,$next)]);

                        if(empty($next)){
                            unset($nextStep[$key]);
                        }else{
                            $next=array_values($next);
                        }
                    }
                }
                return $nextStep;
            }else{
                ksort($arr);//按距离大小升序排序
                $arr = array_reduce($arr, 'array_merge', array());

                $canReOpen[] = array_values($arr);

                do{//继续改点的分裂直到达到$stepCount 一个点可能分裂为1个两个或三个
                    $count_tmp=$count;
                    $nextStartDot=array_shift($arr);
                    $nextStep[$startDot[0].$startDot[1]][]=$nextStartDot;

                    $count_tmp++;
                    $nextStep=getShortLine($line,$canReOpen,$nextStartDot,$mazePositions,$notPlacedDot,$endDot,$n,$count_tmp,$nextStep,$extraNotPlaceDot,$stepCount);

                }while(!empty($arr));

                return $nextStep=array_map(function ($val){
                    if(isset($val[0])&&!is_array($val[0])){//父节点第一个点
                        return $val;
                    }
                    return array_values(array_unique($val,SORT_REGULAR));
                },$nextStep);

            }
        }else{

            if(empty($arr)){//死点
                $extraNotPlaceDot[]=$startDot;//这个点不能放
            }
            return $nextStep;
        }
    }
}

function getAllHtml($positions,$lineDot,$n){
    $str="<table border='1'>";

    foreach (array_chunk($positions,$n+1) as $row){
        $str.="<tr>";
        foreach ($row as $tr){
            if(in_array($tr,$lineDot)){

                $str.="<td style='color: white;background-color: red'>点{$tr[0]},{$tr[1]}</td>";

            }else{
                $str.="<td>{$tr[0]},{$tr[1]}</td>";
            }
        }
        $str.="<tr>";

    };
    $str.="</table>";
    return $str;
}
function getHtml($positions,$lineDot,$shortLine,$n){
    $str="<table border='1'>";

    foreach (array_chunk($positions,$n+1) as $row){
        $str.="<tr>";
        foreach ($row as $tr){
            if(in_array($tr,$lineDot)){
                if(in_array($tr,$shortLine)){
                    $str.="<td style='color: white;background-color: blue'>点{$tr[0]},{$tr[1]}</td>";
                }else{
                    $str.="<td style='color: white;background-color: red'>点{$tr[0]},{$tr[1]}</td>";
                }

            }else{
                $str.="<td>{$tr[0]},{$tr[1]}</td>";
            }
        }
        $str.="<tr>";

    };
    $str.="</table>";
    return $str;
}

function linkTable($result,$steps,$item,$start,$ar,$parent){

    if(isset($steps[$item[0].$item[1]])&&!empty($steps[$item[0].$item[1]])){
        $items=$steps[$item[0].$item[1]];
        $length=count($items);
        for ($i=0;$i<$length;$i++){
            if(!in_array($items[$i],$ar)){//避免形成一个环
                $parent[]=$items[$i];
                $ar[]=$items[$i];
                $item=$items[$i];
                $result=linkTable($result,$steps,$item,$start,$ar,$parent);
                array_pop($parent);
                $ar=$parent;
            }
        }
        return array_values(array_unique($result,SORT_REGULAR ));
    }else{
        array_unshift($ar,$start);
        $result[]=$ar;
        return $result;
    }
}
function distance($start,$end){
    return abs($end[0]-$start[0])+abs($end[1]-$start[1]);
}

function notPlacedDot($positions,$n)
{

    $notPlacedDot=[];
    foreach ($positions as $position){
        $dotWrapper=GetDotWrappers($position);

        foreach ($dotWrapper as $dot){
            if($dot[0]<0||$dot[1]<0||$dot[0]>$n||$dot[1]>$n){
                $notPlacedDot[]=$dot;
            }
        }
    }
    return $notPlacedDot;
}

/**
 * 是否是坏点
 * @param $dotWrapper
 * @param $placedDot
 * @param $notPlacedDot
 * @return bool
 */
function badDot($position,$placedDot,$notPlacedDot,$n){

    $dotWrapper=GetDotWrappers($position);
    $i=0;
    foreach ($dotWrapper as $dot){
        if($dot[0]<0||$dot[1]<0||$dot[1]>$n||in_array($dot,$placedDot)||in_array($dot,$notPlacedDot)){
            $i++;
        }
    }
    if($i==4){
        return true;
    }
    return false;

}

function GetDotWrappers($placementEd){
    $top = calculateXY($placementEd[0],$placementEd[1],'top');
    $bottom = calculateXY($placementEd[0],$placementEd[1],'bottom');
    $left = calculateXY($placementEd[0],$placementEd[1],'left');
    $right = calculateXY($placementEd[0],$placementEd[1],'right');
    return [$top,$bottom,$left,$right];
}

function calculateXY($x,$y,$default='top')
{
    switch ($default){
        case 'top':
            $y=$y+1;
            break;
        case 'bottom':
            $y=$y-1;
            break;
        case 'left':
            $x=$x-1;
            break;
        case 'right':
            $x=($x+1);
            break;
        case 'left_top':
            $x=($x-1);
            $y=($y+1);
            break;
        case 'right_top':
            $x=($x+1);
            $y=($y+1);
            break;
        case 'right_bottom':
            $x=($x+1);
            $y=($y-1);
            break;
        case 'left_bottom':
            $x=($x-1);
            $y=($y-1);
            break;
    }

    return [$x,$y];
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
Make everything simple instead of making difficulties as simple as possible
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

博客挺高产的 :+1:

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!