Час пик - Решаем игру - PullRequest
33 голосов
/ 21 мая 2010

час пик
если вы не знакомы с ней, игра состоит из набора автомобилей различных размеров, установленных по горизонтали или по вертикали, на сетке NxM с одним выходом.
Каждый автомобиль может двигаться вперед / назад в направлении, в котором он установлен, если другой автомобиль не блокирует его. Вы можете никогда изменить направление движения автомобиля.
Есть одна специальная машина, обычно красная. Он расположен в том же ряду, в котором находится выход, и цель игры состоит в том, чтобы найти серию ходов (движение - движение автомобиля на N шагов назад или вперед), которые позволят красной машине выехать из лабиринта ,

Я пытался подумать, как решить эту проблему в вычислительном отношении, и я действительно не могу придумать ни одного хорошего решения.
Я придумал несколько:

  1. Откат. Это довольно просто - рекурсия и еще несколько рекурсий, пока вы не найдете ответ. Тем не менее, каждая машина может быть перемещена несколькими различными способами, и в каждом игровом состоянии можно перемещать несколько автомобилей, и итоговое дерево игры будет ОГРОМНЫМ.
  2. Какой-то алгоритм ограничения, который будет учитывать то, что нужно переместить, и каким-то образом работать рекурсивно. Это очень грубая идея, но это идея.
  3. Графы? Смоделируйте игровые состояния в виде графика и примените какое-либо изменение алгоритма раскраски, чтобы устранить зависимости? Опять же, это очень грубая идея.
  4. Друг предложил генетические алгоритмы. Это вроде возможно, но не легко. Я не могу придумать хороший способ сделать функцию оценки, и без этого у нас ничего нет.

Итак, вопрос в том, как создать программу, которая будет использовать сетку и схему транспортного средства и выводить серию шагов, необходимых для того, чтобы вывести красную машину?

Подвопросы:

  1. Нахождение какого-то решения.
  2. Нахождение оптимального решения (минимальное количество ходов)
  3. Оценка того, насколько хорошим является текущее состояние

Пример: как вы можете перемещать автомобили в этом режиме, чтобы красная машина могла «выйти» из лабиринта через выход справа?
http://scienceblogs.com/ethicsandscience/upload/2006/12/RushHour.jpg

Ответы [ 7 ]

29 голосов
/ 21 мая 2010

Для классического часа пик эту проблему очень легко решить с помощью простого поиска в ширину. Заявленная самая сложная из известных начальных конфигураций требует 93 ходов, всего 24132 достижимых конфигураций. Даже наивно реализованный алгоритм поиска в ширину может исследовать все пространство поиска менее чем за 1 секунду даже на скромной машине.

Ссылки


Решатель Java

Вот полный исходный код исчерпывающего решения для поиска в ширину, написанный в стиле C.

import java.util.*;

public class RushHour {
    // classic Rush Hour parameters
    static final int N = 6;
    static final int M = 6;
    static final int GOAL_R = 2;
    static final int GOAL_C = 5;

    // the transcription of the 93 moves, total 24132 configurations problem
    // from http://cs.ulb.ac.be/~fservais/rushhour/index.php?window_size=20&offset=0
    static final String INITIAL =   "333BCC" +
                                    "B22BCC" +
                                    "B.XXCC" +
                                    "22B..." +
                                    ".BB.22" +
                                    ".B2222";

    static final String HORZS = "23X";  // horizontal-sliding cars
    static final String VERTS = "BC";   // vertical-sliding cars
    static final String LONGS = "3C";   // length 3 cars
    static final String SHORTS = "2BX"; // length 2 cars
    static final char GOAL_CAR = 'X';
    static final char EMPTY = '.';      // empty space, movable into
    static final char VOID = '@';       // represents everything out of bound

    // breaks a string into lines of length N using regex
    static String prettify(String state) {
        String EVERY_NTH = "(?<=\\G.{N})".replace("N", String.valueOf(N));
        return state.replaceAll(EVERY_NTH, "\n");
    }

    // conventional row major 2D-1D index transformation
    static int rc2i(int r, int c) {
        return r * N + c;
    }

    // checks if an entity is of a given type
    static boolean isType(char entity, String type) {
        return type.indexOf(entity) != -1;
    }

    // finds the length of a car
    static int length(char car) {
        return
            isType(car, LONGS) ? 3 :
            isType(car, SHORTS) ? 2 :
            0/0; // a nasty shortcut for throwing IllegalArgumentException
    }

    // in given state, returns the entity at a given coordinate, possibly out of bound
    static char at(String state, int r, int c) {
        return (inBound(r, M) && inBound(c, N)) ? state.charAt(rc2i(r, c)) : VOID;
    }
    static boolean inBound(int v, int max) {
        return (v >= 0) && (v < max);
    }

    // checks if a given state is a goal state
    static boolean isGoal(String state) {
        return at(state, GOAL_R, GOAL_C) == GOAL_CAR;
    }

    // in a given state, starting from given coordinate, toward the given direction,
    // counts how many empty spaces there are (origin inclusive)
    static int countSpaces(String state, int r, int c, int dr, int dc) {
        int k = 0;
        while (at(state, r + k * dr, c + k * dc) == EMPTY) {
            k++;
        }
        return k;
    }

    // the predecessor map, maps currentState => previousState
    static Map<String,String> pred = new HashMap<String,String>();
    // the breadth first search queue
    static Queue<String> queue = new LinkedList<String>();
    // the breadth first search proposal method: if we haven't reached it yet,
    // (i.e. it has no predecessor), we map the given state and add to queue
    static void propose(String next, String prev) {
        if (!pred.containsKey(next)) {
            pred.put(next, prev);
            queue.add(next);
        }
    }

    // the predecessor tracing method, implemented using recursion for brevity;
    // guaranteed no infinite recursion, but may throw StackOverflowError on
    // really long shortest-path trace (which is infeasible in standard Rush Hour)
    static int trace(String current) {
        String prev = pred.get(current);
        int step = (prev == null) ? 0 : trace(prev) + 1;
        System.out.println(step);
        System.out.println(prettify(current));
        return step;
    }

    // in a given state, from a given origin coordinate, attempts to find a car of a given type
    // at a given distance in a given direction; if found, slide it in the opposite direction
    // one spot at a time, exactly n times, proposing those states to the breadth first search
    //
    // e.g.
    //    direction = -->
    //    __n__
    //   /     \
    //   ..o....c
    //      \___/
    //      distance
    //
    static void slide(String current, int r, int c, String type, int distance, int dr, int dc, int n) {
        r += distance * dr;
        c += distance * dc;
        char car = at(current, r, c);
        if (!isType(car, type)) return;
        final int L = length(car);
        StringBuilder sb = new StringBuilder(current);
        for (int i = 0; i < n; i++) {
            r -= dr;
            c -= dc;
            sb.setCharAt(rc2i(r, c), car);
            sb.setCharAt(rc2i(r + L * dr, c + L * dc), EMPTY);
            propose(sb.toString(), current);
            current = sb.toString(); // comment to combo as one step
        }
    }

    // explores a given state; searches for next level states in the breadth first search
    //
    // Let (r,c) be the intersection point of this cross:
    //
    //     @       nU = 3     '@' is not a car, 'B' and 'X' are of the wrong type;
    //     .       nD = 1     only '2' can slide to the right up to 5 spaces
    //   2.....B   nL = 2
    //     X       nR = 4
    //
    // The n? counts how many spaces are there in a given direction, origin inclusive.
    // Cars matching the type will then slide on these "alleys".
    //
    static void explore(String current) {
        for (int r = 0; r < M; r++) {
            for (int c = 0; c < N; c++) {
                if (at(current, r, c) != EMPTY) continue;
                int nU = countSpaces(current, r, c, -1, 0);
                int nD = countSpaces(current, r, c, +1, 0);
                int nL = countSpaces(current, r, c, 0, -1);
                int nR = countSpaces(current, r, c, 0, +1);
                slide(current, r, c, VERTS, nU, -1, 0, nU + nD - 1);
                slide(current, r, c, VERTS, nD, +1, 0, nU + nD - 1);
                slide(current, r, c, HORZS, nL, 0, -1, nL + nR - 1);
                slide(current, r, c, HORZS, nR, 0, +1, nL + nR - 1);
            }
        }
    }
    public static void main(String[] args) {
        // typical queue-based breadth first search implementation
        propose(INITIAL, null);
        boolean solved = false;
        while (!queue.isEmpty()) {
            String current = queue.remove();
            if (isGoal(current) && !solved) {
                solved = true;
                trace(current);
                //break; // comment to continue exploring entire space
            }
            explore(current);
        }
        System.out.println(pred.size() + " explored");
    }
}

В исходном коде есть две строки, достойные внимания:

  • break; когда решение найдено
    • Теперь это прокомментировано, так что поиск в ширину исследует все пространство поиска, чтобы подтвердить числа, указанные на связанном веб-сайте выше
  • current = sb.toString(); в slide
    • По сути, каждое движение любой машины считается одним движением. Если автомобиль перемещается на 3 пробела влево, это 3 хода. Чтобы объединить это как один ход (так как это вовлекает ту же самую машину, двигающуюся в том же самом направлении), просто прокомментируйте эту строку. Связанный веб-сайт не подтверждает комбо, поэтому эта строка теперь не комментируется, чтобы соответствовать минимальному количеству данных ходов. С подсчетом комбо, задача с 93 ходами требует только 49 комбо ходов. То есть, если на участке есть парковщик, который перемещает эти машины, ему нужно всего лишь 49 раз входить и выходить из машины.

Обзор алгоритма

Алгоритм - это, по сути, поиск в ширину, реализованный с использованием очереди, как это обычно бывает. Карта предшественников поддерживается так, что любое состояние может быть прослежено до исходного состояния. Ключ никогда не будет переназначен, и поскольку записи вставляются в порядке поиска в ширину, гарантируется кратчайший путь.

Состояние представляется как NxM-длина String. Каждый char представляет сущность на доске, сохраненную в мажорном порядке.

Соседние состояния обнаруживаются путем сканирования всех четырех направлений из пустого пространства, поиска подходящего типа автомобиля и перемещения его по мере размещения комнаты.

Здесь есть много лишней работы (например, длинные "аллеи" сканируются несколько раз), но, как уже упоминалось ранее, хотя обобщенная версия является PSPACE-полной, классический вариант Rush Hour очень легко поддается грубой обработке.

ссылки на Википедию

7 голосов
/ 18 мая 2011

Вот мой ответ. Он решает загадку гроссмейстера всего за 6 секунд.

Используется поиск в ширину (BFS). Хитрость заключается в том, чтобы найти макет доски, который вы видели ранее в предыдущих поисках, и прервать эту последовательность. Из-за BFS, если вы уже видели этот макет раньше, чем уже получили более короткий путь, так что пусть эта последовательность будет пытаться ее решить, а не эта более длинная.

#!perl

# Program by Rodos rodos at haywood dot org

use Storable qw(dclone);
use Data::Dumper;

print "Lets play Rush Hour! \n";


# Lets define our current game state as a grid where each car is a different letter.
# Our special car is a marked with the specific letter T
# The boarder is a * and the gloal point on the edge is an @.
# The grid must be the same witdh and height 
# You can use a . to mark an empty space

# Grand Master
@startingGrid = (
 ['*','*','*','*','*','*','*','*'],
 ['*','.','.','A','O','O','O','*'],
 ['*','.','.','A','.','B','.','*'],
 ['*','.','T','T','C','B','.','@'],
 ['*','D','D','E','C','.','P','*'],
 ['*','.','F','E','G','G','P','*'],
 ['*','.','F','Q','Q','Q','P','*'],
 ['*','*','*','*','*','*','*','*']
);

# Now lets print out our grid board so we can see what it looks like.
# We will go through each row and then each column.
# As we do this we will record the list of cars (letters) we see into a hash

print "Here is your board.\n";

&printGrid(\@startingGrid);

# Lets find the cars on the board and the direction they are sitting

for $row (0 .. $#startingGrid) {
    for $col (0 .. $#{$startingGrid[$row]} ) {

        # Make spot the value of the bit on the grid we are looking at
        $spot = $startingGrid[$row][$col];

        # Lets record any cars we see into a "hash" of valid cars.
        # If the splot is a non-character we will ignore it cars are only characters
        unless ($spot =~ /\W/) {

            # We will record the direction of the car as the value of the hash key.
            # If the location above or below our spot is the same then the car must be vertical.
            # If its not vertical we mark as it as horizonal as it can't be anything else!

            if ($startingGrid[$row-1][$col] eq $spot || $startingGrid[$row+1] eq $spot) {
                $cars{$spot} = '|';
            } else {
                $cars{$spot} = '-';
            }
        }
    }
}

# Okay we should have printed our grid and worked out the unique cars
# Lets print out our list of cars in order

print "\nI have determined that you have used the following cars on your grid board.\n";
foreach $car (sort keys %cars) {
    print " $car$cars{$car}";
}
print "\n\n";

end;

&tryMoves();

end;

# Here are our subroutines for things that we want to do over and over again or things we might do once but for 
# clatiry we want to keep the main line of logic clear

sub tryMoves {

    # Okay, this is the hard work. Take the grid we have been given. For each car see what moves are possible
    # and try each in turn on a new grid. We will do a shallow breadth first search (BFS) rather than depth first. 
    # The BFS is achieved by throwing new sequences onto the end of a stack. You then keep pulling sequnces
    # from the front of the stack. Each time you get a new item of the stack you have to rebuild the grid to what
    # it looks like at that point based on the previous moves, this takes more CPU but does not consume as much
    # memory as saving all of the grid representations.

    my (@moveQueue);
    my (@thisMove);
    push @moveQueue, \@thisMove;

    # Whlst there are moves on the queue process them                
    while ($sequence = shift @moveQueue) { 

        # We have to make a current view of the grid based on the moves that got us here

        $currentGrid = dclone(\@startingGrid);
        foreach $step (@{ $sequence }) {
            $step =~ /(\w)-(\w)(\d)/;
            $car = $1; $dir = $2; $repeat = $3;

            foreach (1 .. $repeat) {
                &moveCarRight($car, $currentGrid) if $dir eq 'R';
                &moveCarLeft($car,  $currentGrid) if $dir eq 'L';
                &moveCarUp($car,    $currentGrid) if $dir eq 'U';
                &moveCarDown($car,  $currentGrid) if $dir eq 'D';
            }
        }

        # Lets see what are the moves that we can do from here.

        my (@moves);

        foreach $car (sort keys %cars) {
            if ($cars{$car} eq "-") {
                $l = &canGoLeft($car,$currentGrid);
                push @moves, "$car-L$l" if ($l);
                $l = &canGoRight($car,$currentGrid);
                push @moves, "$car-R$l" if ($l);
            } else {
                $l = &canGoUp($car,$currentGrid);
                push @moves, "$car-U$l" if ($l);
                $l = &canGoDown($car,$currentGrid);
                push @moves, "$car-D$l" if ($l);
            }
        }

        # Try each of the moves, if it solves the puzzle we are done. Otherwise take the new 
        # list of moves and throw it on the stack

        foreach $step (@moves) {

            $step =~ /(\w)-(\w)(\d)/;
            $car = $1; $dir = $2; $repeat = $3;

            my $newGrid = dclone($currentGrid);

            foreach (1 .. $repeat) {
                &moveCarRight($car, $newGrid) if $dir eq 'R';
                &moveCarLeft($car, $newGrid) if $dir eq 'L';
                &moveCarUp($car, $newGrid) if $dir eq 'U';
                &moveCarDown($car, $newGrid) if $dir eq 'D';
            }

            if (&isItSolved($newGrid)) {
                print sprintf("Solution in %d moves :\n", (scalar @{ $sequence }) + 1);
                print join ",", @{ $sequence };
                print ",$car-$dir$repeat\n";
                return;
            } else {

                # That did not create a solution, before we push this for further sequencing we want to see if this
                # pattern has been encountered before. If it has there is no point trying more variations as we already
                # have a sequence that gets here and it might have been shorter, thanks to our BFS

                if (!&seen($newGrid)) {
                    # Um, looks like it was not solved, lets throw this grid on the queue for another attempt
                    my (@thisSteps) = @{ $sequence };
                    push @thisSteps, "$car-$dir$repeat";
                    push @moveQueue, \@thisSteps;
                }
            }            
        }
    }
}    

sub isItSolved {

    my ($grid) = shift;

    my ($row, $col);
    my $stringVersion;

    foreach $row (@$grid) {
        $stringVersion .= join "",@$row;
    }

    # We know we have solve the grid lock when the T is next to the @, because that means the taxi is at the door
    if ($stringVersion =~ /\T\@/) {
        return 1;
    }
    return 0;
}    

sub seen {

    my ($grid) = shift;

    my ($row, $col);
    my $stringVersion;

    foreach $row (@$grid) {
        $stringVersion .= join "",@$row;
    }

    # Have we seen this before?
    if ($seen{$stringVersion}) {
        return 1;
    }
    $seen{$stringVersion} = 1;
    return 0;
}    

sub canGoDown {

    my ($car) = shift;

    return 0 if $cars{$car} eq "-";

    my ($grid) = shift;

    my ($row, $col);


    for ($row = $#{$grid}; $row >= 0; --$row) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                # See how many we can move
                $l = 0;
                while ($grid->[++$row][$col] eq ".") {
                    ++$l;
                }
                return $l;
            }
        }
    }
    return 0;
}

sub canGoUp {

    my ($car) = shift;

    return 0 if $cars{$car} eq "-";

    my ($grid) = shift;

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                # See how many we can move
                $l = 0;
                while ($grid->[--$row][$col] eq ".") {
                    ++$l;
                } 
                return $l;
            }
        }
    }
    return 0;
}

sub canGoRight {

    my ($car) = shift;

    return 0 if $cars{$car} eq "|";

    my ($grid) = shift;

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for ($col = $#{$grid->[$row]}; $col >= 0; --$col ) {
            if ($grid->[$row][$col] eq $car) {
                # See how many we can move
                $l = 0;
                while ($grid->[$row][++$col] eq ".") {
                    ++$l;
                } 
                return $l;
            }
        }
    }
    return 0;
}

sub canGoLeft {

    my ($car) = shift;

    return 0 if $cars{$car} eq "|";

    my ($grid) = shift;

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                # See how many we can move
                $l = 0;
                while ($grid->[$row][--$col] eq ".") {
                    ++$l;
                } 
                return $l;
            }
        }
    }
    return 0;
}

sub moveCarLeft {

    # Move the named car to the left of the passed grid. Care must be taken with the algoritm
    # to not move part of the car and then come across it again on the same pass and move it again 
    # so moving left requires sweeping left to right.

    # We need to know which car you want to move and the reference to the grid you want to move it on
    my ($car) = shift;
    my ($grid) = shift;

    # Only horizontal cards can move left
    die "Opps, tried to move a vertical car $car left" if $cars{$car} eq "|";

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                die "Tried to move car $car left into an occupied spot\n" if $grid->[$row][$col-1] ne ".";
                $grid->[$row][$col-1] = $car;
                $grid->[$row][$col] = ".";
            }
        }
    }
}

sub moveCarRight {

    # Move the named car to the right of the passed grid. Care must be taken with the algoritm
    # to not move part of the car and then come across it again on the same pass and move it again 
    # so moving right requires sweeping right to left (backwards).

    # We need to know which car you want to move and the reference to the grid you want to move it on
    my ($car) = shift;
    my ($grid) = shift;

    # Only horizontal cards can move right
    die "Opps, tried to move a vertical car $car right" if $cars{$car} eq "|";

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for ($col = $#{$grid->[$row]}; $col >= 0; --$col ) {
            if ($grid->[$row][$col] eq $car) {
                die "Tried to move car $car right into an occupied spot\n" if $grid->[$row][$col+1] ne ".";
                $grid->[$row][$col+1] = $car;
                $grid->[$row][$col] = ".";
            }
        }
    }
}


sub moveCarUp {

    # Move the named car up in the passed grid. Care must be taken with the algoritm
    # to not move part of the car and then come across it again on the same pass and move it again 
    # so moving right requires sweeping top down.

    # We need to know which car you want to move and the reference to the grid you want to move it on
    my ($car) = shift;
    my ($grid) = shift;

    # Only vertical cards can move up
    die "Opps, tried to move a horizontal car $car up" if $cars{$car} eq "-";

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                die "Tried to move car $car up into an occupied spot\n" if $grid->[$row-1][$col] ne ".";
                $grid->[$row-1][$col] = $car;
                $grid->[$row][$col] = ".";
            }
        }
    }
}

sub moveCarDown {

    # Move the named car down in the passed grid. Care must be taken with the algoritm
    # to not move part of the car and then come across it again on the same pass and move it again 
    # so moving right requires sweeping upwards from the bottom.

    # We need to know which car you want to move and the reference to the grid you want to move it on
    my ($car) = shift;
    my ($grid) = shift;

    # Only vertical cards can move up
    die "Opps, tried to move a horizontal car $car down" if $cars{$car} eq "-";

    my ($row, $col);    

    for ($row = $#{$grid}; $row >=0; --$row) {
        for $col (0 .. $#{$grid->[$row]} ) {
            if ($grid->[$row][$col] eq $car) {
                die "Tried to move car $car down into an occupied spot\n" if $grid->[$row+1][$col] ne ".";
                $grid->[$row+1][$col] = $car;
                $grid->[$row][$col] = ".";
            }
        }
    }
}

sub printGrid {

    # Print out a representation of a grid

    my ($grid) = shift; # This is a reference to an array of arrays whch is passed as the argument

    my ($row, $col);

    for $row (0 .. $#{$grid}) {
        for $col (0 .. $#{$grid->[$row]} ) {
                print $grid->[$row][$col], " ";
        }
        print "\n";
    }
}
5 голосов
/ 21 мая 2010

На самом деле есть статья из MIT, в которой конкретно упоминается час пик (я использовал поисковый термин "головоломки скользящего блока")

3 голосов
/ 25 мая 2010

Только что закончил писать свою реализацию и экспериментировать с ней. Я согласен с полигенными смазочными материалами, что пространство состояний действительно мало для классической игры (доска 6x6). Однако я попробовал умную реализацию поиска ( A * search ). Мне было любопытно по поводу сокращения исследуемого пространства состояний по сравнению с простым BFS.

Алгоритм * можно рассматривать как обобщение поиска BFS. Решение о том, какой путь исследовать следующим, определяется счетом, который объединяет длину пути (то есть количество ходов) и нижнюю границу количества оставшихся ходов. Способ, который я выбрал для вычисления последнего, состоит в том, чтобы получить расстояние от красной машины до выхода, а затем добавить 1 для каждого транспортного средства в пути, поскольку его необходимо переместить хотя бы один раз, чтобы расчистить путь. Когда я заменяю вычисление нижней границы константой 0, я получаю обычное поведение BFS.

Изучив четыре загадки из этого списка , я обнаружил, что поиск A * исследует в среднем 16% меньше состояний, чем обычная BFS.

3 голосов
/ 21 мая 2010

Вы должны рекурсировать (ваше решение для «возврата»). Это, наверное, единственный способ решить такие головоломки; вопрос в том, как сделать это быстро.

Как вы уже заметили, пространство поиска будет большим, но не слишком большим, если у вас доска разумного размера. Например, вы нарисовали сетку 6х6 с 12 машинами. Предполагая, что каждый из них - автомобиль размера 2, он дает 5 мест на человека, то есть самое большее 5 ^ 12 = 244 140 625 потенциальных позиций. Это даже вписывается в 32-разрядное целое число. Таким образом, одна возможность состоит в том, чтобы выделить огромный массив, один слот для каждой потенциальной позиции и использовать памятку, чтобы убедиться, что вы не повторяете позицию.

Следующее, что следует отметить, заключается в том, что большинство из этих «потенциальных» позиций, на самом деле, невозможно (они могут включать в себя наложение автомобилей). Поэтому вместо этого используйте хеш-таблицу для отслеживания каждой позиции, которую вы посетили. Это будет иметь небольшие накладные расходы памяти на запись, но, вероятно, будет более экономичным, чем решение с «огромным массивом». Однако для каждого доступа к записи потребуется немного больше времени.

Как говорится в статье MIT в @ ответ Даниэля , проблема в PSPACE-решении, то есть многие приемы, используемые для уменьшения сложности проблем NP, вероятно, не могут быть использованы.

Тем не менее, любое из двух приведенных выше решений проблемы повторного позиционирования должно работать для небольших сетей. Все будет определяться тем, насколько велика проблема, и сколько памяти имеет ваш компьютер; но показанный вами пример не должен вызывать никаких затруднений, даже для обычного настольного компьютера.

0 голосов
/ 21 мая 2010

Я написал решатель судоку. Хотя детали совершенно разные, я думаю, что общая проблема схожа. Во-первых, попытка провести интеллектуальную эвристику в решении судоку гораздо медленнее, чем решение методом грубой силы. Попытка каждого движения, с несколькими простыми эвристиками и без дубликатов - путь. Немного сложнее проверить наличие дублирующихся состояний доски в час пик, но не намного.

Если вы посмотрите на доску в вашем образце, есть только 4 действительных хода. В любой момент времени будет только несколько действительных ходов.

На каждом уровне рекурсии копируйте состояние доски и пробуйте каждый допустимый ход на доске. Для каждого пустого квадрата переместите каждую машину, которая может на этот квадрат. Если нового состояния доски нет в списке истории, перейдите на другой уровень. Под списком истории я имею в виду предоставление каждому уровню рекурсии доступа к каждой доске, которая привела к этому состоянию, возможно, в связанном списке. Используйте хэши, чтобы быстро отбросить неравные состояния.

Ключом к этому является наличие простого состояния платы, которое можно легко скопировать и изменить. Вероятно, массив с одним int на квадрат, который говорит, какая машина покрывает этот квадрат, если таковой имеется. Тогда вам просто нужно перебрать квадраты и выяснить законные ходы. Под законным ходом подразумеваются пустые квадраты между тестовым квадратом и ориентированной на него машиной.

Как и в случае с судоку, наихудшим вариантом будет генетический алгоритм.

0 голосов
/ 21 мая 2010

Я думаю, что рекурсия плохая идея, если вы не отслеживаете то, что уже посетили; Вы можете в конечном итоге возвращаться бесконечно, двигая машину туда-сюда.

Может быть, это хорошее начало: представлять и хранить каждое состояние доски в виде неориентированного графика. Затем для каждого возможного хода проверяйте прошлые состояния, чтобы убедиться, что вы не просто снова нажмете одно и то же состояние.

Теперь создайте еще один неориентированный граф, в котором узлы представляют состояния, а ребра - возможность перехода из одного состояния в другое посредством перемещения автомобиля. Исследуйте состояния, пока одно из них не станет решением. Затем следуйте по краям назад к началу, чтобы найти путь движения.

...