PHP 解迷宮之 G + H 最小

jcc123發表於2019-08-05

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];
}

NOT IS BECAUSE I WANT TO WRITE,
BUT I WANT TO INCREASE,
SO I GO TO WRITE~~

相關文章