PHP在2D迷宫式数组中找到最短路径

时间:2018-04-18 09:52:25

标签: php

我正在制作一个2D字符串,最后用\ n \ n新行。

迷宫是1000 x 1000,但为了便于阅读,我将其缩小为10 x 5。

0表示空格。

S意味着起点。

T表示目标点。

  

我创建了一个名为cal_path的函数来计算S和T之间的路径   结果不正确。我已经对估计答案进行了硬编码。任何建议/帮助将不胜感激。

<?php

$maze='S000000000
0000000000
0000000000
00000000T0
0000000000';

$maze_y=explode(PHP_EOL,$maze);//convert line to array

function show_maze($maze_y){
for ($y=0;$y<count($maze_y);$y++){
    for ($x=0;$x<=strlen($maze_y[$y]);$x++){
        echo $maze_y[$y][$x];
    }
    echo "\n";
}
}

function cal_path($maze_y){
    // I hardcoded it right now, I am stuck here
    return array(
array(1,1),
array(1,2),
array(1,3),
array(2,4),
array(2,5),
array(2,6),
array(3,7),
array(3,8),
);
}

show_maze($maze_y); //original maze

$path=cal_path($maze_y);



foreach ($path as $point){
    $maze_y[$point[0]][$point[1]]='P';
}

show_maze($maze_y);

输出:

S000000000
0000000000
0000000000 00000000T0
0000000000

估算输出:

S000000000
0PPP000000
0000PPP000
0000000PP0
0000000000

这是我的尝试,但未能找到最短路径。

<?php

$maze = '0000000000
0000S00000
0000000000
00000000T0
0000000000';

$maze_y = explode(PHP_EOL, $maze); //convert line to array

function show_maze($maze_y)
{
    for ($y = 0; $y < count($maze_y); $y++) {
        for ($x = 0; $x < strlen($maze_y[$y]); $x++) {
            echo $maze_y[$y][$x];
        }
        echo "\n";
    }
}

function cal_path($maze_y)
{

    $found_start = -1;
    $found_end   = -1;
    $row         = 0;
    foreach ($maze_y as $current) {
        for ($x = 0; $x <= strlen($current); $x++) {
            if ($found_start == -1) {
                $found_start = (strpos($current, 'S') === false) ? '-1' : strpos($current, 'S');
                if ($found_start != -1) {
                    $found_start_y = $row;
                }
            }
            if ($found_end == -1) {
                $found_end = (strpos($current, 'T') === false) ? '-1' : strpos($current, 'T');
                if ($found_end != -1) {
                    $found_end_y = $row;
                }
            }
        }
        $row++;
    }

    echo 'start' . $found_start . ' - ' . $found_start_y;
    echo "\n";
    echo 'end' . $found_end . ' - ' . $found_end_y;
    echo "\n";

    $step_size_y = $found_end_y - $found_start_y;
    $step_size_x = $found_end - $found_start;
    echo "step size X $step_size_x Y $step_size_y";
    echo "\n";

    $start_pointer = array($found_start, $found_start_y);
    $maxtry        = 100;
    $cal_result    = array();
    while ($maxtry > 0 && ($start_pointer[0] != $found_end || $start_pointer[1] != $found_end_y)) {
        $maxtry--;
        if ($step_size_x > 1 && $start_pointer[0] != $found_end) {
            $start_pointer[0]++;
        } else if ($step_size_x < 1 && $start_pointer[0] != $found_end) {
            $start_pointer[0]--;
        }

        if ($step_size_y > 1 && $start_pointer[1] != $found_end_y) {
            $start_pointer[1]++;
        } else if ($step_size_y < 1 && $start_pointer[1] != $found_end_y) {
            $start_pointer[1]--;
        }
        array_push($cal_result, array($start_pointer[1] , $start_pointer[0] ));
        echo 'Path: ' . $start_pointer[0] . ' - ' . $start_pointer[1] . "\n";

    }

    return $cal_result;

}

show_maze($maze_y); //original maze

$path = cal_path($maze_y);

foreach ($path as $point) {
    $maze_y[$point[0]][$point[1]] = 'P';

}

show_maze($maze_y);

1 个答案:

答案 0 :(得分:2)

您可以使用以下内容作为起点。

这是一个简单的伪代码实现:https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Using_a_priority_queue

<?php
require_once 'src/MinQueue.php';
require_once 'src/Dijkstra.php';
require_once 'src/Maze.php';

$maze = Maze::fromString(file_get_contents('maze.txt')); // < a simple text file

$start = $maze->find('S');
$goal = $maze->find('T');

$helper = new Dijkstra(
    // return neighbors
    function ($a) use ($maze) {
        return $maze->getNeighbors($a, ['W']);
    },
    // calculate the distance
    function ($a, $b) use ($maze) {
        return $maze->getDistance($a, $b);
    }
);

$tStart = microtime(true);
$path = $helper->findPath($start, $goal);
$tEnd = microtime(true);

// export the maze with the path marked with '.'
$mazeStrWithPath = $maze->toString(function ($tile) use ($path) {
    return in_array($tile, $path, true) && !in_array($tile->value, ['S', 'T'])
        ? '.'
        : $tile->value
    ;
});

printf("%s\nin: %.5fs\n\n", $mazeStrWithPath, $tEnd - $tStart);

示例输出:

_____________________________________________________W_________________
_____________________________________________________W_________________
_____________________________________________________W_________________
_________________W___________________________________W_________________
_________________W___________________________________W_____________T___
_________________W___________________________________W____________.____
_________________W___________________________________W___________._____
_________________W___________________________________W__________.______
_________________W___________________________________W_________._______
_________________W____________________.........______W________.________
_________________W___________________.WWWWWWWWW._____W_______._________
_________________W__________________._W_____...______W______.__________
_________________W_________________.__W____.WWWWWWWWWW_____.___________
_________________W________________.___W_____...............____________
_________________W_____________...____W________________________________
_________________W____________.WWWWWWWW________________________________
_________________W_____________.______W________________________________
_________W_______W______________._____W________________________________
_________W_______W______________._____W________________________________
_________W_______WWWWWWWWWWWWWWW._____W________________________________
_________W_____________________.______W________________________________
_________W____________________._______W________________________________
_________W___________________.________W________________________________
__S...___W__________________._________W________________________________
______.__W_________________.__________W________________________________
_______._W________________.___________W________________________________
________.W_______________.____________W________________________________
_________................_____________W________________________________

使用以下类:

MinQueue

<?php
declare(strict_types=1);

class MinQueue implements \Countable
{
    /**
     * @var \SplPriorityQueue
     */
    private $queue;

    /**
     * @var \SplObjectStorage
     */
    private $register;

    /**
     * MinQueue constructor.
     */
    public function __construct()
    {
        $this->queue = new class extends \SplPriorityQueue
        {
            /** @inheritdoc */
            public function compare($p, $q)
            {
                return $q <=> $p;
            }
        };

        $this->register = new \SplObjectStorage();
    }

    /**
     * @param object $value
     * @param mixed  $priority
     */
    public function insert($value, $priority)
    {
        $this->queue->insert($value, $priority);
        $this->register->attach($value);
    }

    /**
     * @return object
     */
    public function extract()
    {
        $value = $this->queue->extract();
        $this->register->detach($value);

        return $value;
    }

    /**
     * @inheritdoc
     */
    public function contains($value)
    {
        return $this->register->contains($value);
    }

    /**
     * @inheritdoc
     */
    public function count()
    {
        return count($this->queue);
    }
}

迪杰斯特拉

<?php
declare(strict_types=1);

class Dijkstra
{
    /**
     * @var callable
     */
    private $neighbors;

    /**
     * @var callable
     */
    private $length;

    /**
     * Dijkstra constructor.
     *
     * @param callable $neighbors
     * @param callable $length
     */
    public function __construct(callable $neighbors, callable $length)
    {
        $this->neighbors = $neighbors;
        $this->length = $length;
    }

    /**
     * see: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Using_a_priority_queue
     *
     * @param object $src
     * @param object $dst
     *
     * @return array
     */
    public function findPath($src, $dst): array
    {
        // setup
        $queue = new MinQueue();
        $distance = new \SplObjectStorage();
        $path = new \SplObjectStorage();

        // init
        $queue->insert($src, 0);
        $distance[$src] = 0;

        while (count($queue) > 0) {
            $u = $queue->extract();

            if ($u === $dst) {
                return $this->buildPath($dst, $path);
            }

            foreach (call_user_func($this->neighbors, $u) as $v) {
                $alt = $distance[$u] + call_user_func($this->length, $u, $v);
                $best = isset($distance[$v]) ? $distance[$v] : INF;

                if ($alt < $best) {
                    $distance[$v] = $alt;
                    $path[$v] = $u;

                    if (!$queue->contains($v)) {
                        $queue->insert($v, $alt);
                    }
                }
            }
        }

        throw new \LogicException('No path found.');
    }

    /**
     * @param object            $dst
     * @param \SplObjectStorage $path
     *
     * @return array
     */
    private function buildPath($dst, \SplObjectStorage $path): array
    {
        $result = [$dst];

        while (isset($path[$dst]) && null !== $path[$dst]) {
            $src = $path[$dst];
            $result[] = $src;
            $dst = $src;
        }

        return array_reverse($result);
    }
}

迷宫

<?php
declare(strict_types=1);

class Maze
{
    /**
     * @var array
     */
    private $tiles = [];

    /**
     * Maze constructor.
     *
     * @param array $tiles
     */
    private function __construct(array $tiles = [])
    {
        $this->tiles = $tiles;
    }

    /**
     * @param string $maze
     * @param string $rowDelimiter
     *
     * @return Maze
     */
    public static function fromString(string $maze, string $rowDelimiter = "\n"): Maze
    {
        $tiles = [];

        foreach (explode($rowDelimiter, $maze) as $r => $row) {
            $rowTiles = [];
            foreach (str_split(trim($row)) as $c => $value) {
                $rowTiles[] = (object)[
                    'row' => $r,
                    'col' => $c,
                    'value' => $value
                ];
            }

            $tiles[] = $rowTiles;
        }

        return new self($tiles);
    }

    /**
     * @param callable $renderer
     * @param string   $rowDelimiter
     *
     * @return string
     */
    public function toString(callable $renderer = null, string $rowDelimiter = "\n"): string
    {
        $renderer = $renderer ?: function ($tile) { return $tile->value; };

        $result = [];
        foreach ($this->tiles as $r => $row) {
            if (!isset($result[$r])) {
                $result[$r] = [];
            }

            foreach ($row as $c => $tile) {
                $result[$r][$c] = $renderer($tile);
            }
        }

        return implode($rowDelimiter, array_map('implode', $result));
    }

    /**
     * @param string $value
     *
     * @return object
     */
    public function find(string $value)
    {
        foreach ($this->tiles as $row) {
            foreach ($row as $tile) {
                if ($tile->value === $value) {
                    return $tile;
                }
            }
        }

        return null;
    }

    /**
     * @param object $tile
     * @param array  $filter
     *
     * @return array
     */
    public function getNeighbors($tile, array $filter = []): array
    {
        $neighbors = [];
        foreach ([
            [-1, -1], [-1, 0], [-1, 1],
            [ 0, -1],          [ 0, 1],
            [ 1, -1], [ 1, 0], [ 1, 1],
        ] as $transformation) {
            $r = $tile->row + $transformation[0];
            $c = $tile->col + $transformation[1];

            if (isset($this->tiles[$r][$c]) && !in_array($this->tiles[$r][$c]->value, $filter, true)) {
                $neighbors[] = $this->tiles[$r][$c];
            }
        }

        return $neighbors;
    }

    /**
     * @param object $a
     * @param object $b
     *
     * @return float
     */
    public function getDistance($a, $b): float
    {
        $p = $b->row - $a->row;
        $q = $b->col - $a->col;

        return sqrt($p * $p + $q * $q);
    }
}

下载https://github.com/Yoshix/so-49896590