Rush Hour-résoudre le jeu

Heure De Pointe

si vous n'êtes pas familier avec elle, le jeu se compose d'une collection de voitures de différentes tailles, soit horizontalement ou verticalement, sur un NxM grille qui a une seule sortie.

Chaque voiture peut avancer / reculer dans les directions où elle est installée, à condition qu'une autre voiture ne la bloque pas. Vous pouvez jamais changer la direction d'une voiture.

Il y en a un spécial voiture, habituellement, c'est le rouge. C'est dans la même ligne que la sortie est dans, et l'objectif du jeu est de trouver une série de mouvements (un mouvement de déplacement de la voiture N pas en arrière ou en avant) qui permettra à la voiture rouge pour sortir du labyrinthe.

j'ai essayé de penser comment résoudre ce problème informatique, et je ne peux vraiment pas penser à une bonne solution.

J'en ai trouvé quelques-uns:

  1. les retours en arrière. C'est assez simple-récursion et un peu plus de récursion jusqu'à ce que vous trouviez la réponse. Cependant, chaque voiture peut être déplacée de quelques façons différentes, et dans chaque État de jeu quelques voitures peuvent être déplacées, et l'arbre de jeu résultant sera énorme.
  2. une sorte d'algorithme de contrainte qui tiendra compte de ce qui doit être déplacé et fonctionnera de façon récursive. C'est une idée très approximative, mais c'est une idée.
  3. Graphs? Modéliser les États du jeu comme un graphique et appliquer une sorte de variation sur un algorithme de coloration, pour résoudre les dépendances? Encore une fois, c'est une idée très approximative.
  4. un ami a suggéré des algorithmes génétiques. C'est possible, mais pas facilement. Je n'arrive pas à trouver un bon moyen de faire une fonction d'évaluation, et sans ça, on n'a rien.

alors la question est - comment créer un programme qui prend une grille et la disposition du véhicule, et produit une série d'étapes nécessaires pour obtenir la voiture rouge?

sous-numéros:

  1. Trouver certains solution.
  2. trouver une solution optimale (nombre minimal de mouvements)
  3. l'Évaluation de la qualité d'un état actuel est

Exemple: Comment pouvez-vous déplacer les voitures dans ce cadre, de sorte que la voiture rouge peut "sortir" le labyrinthe jusqu'à la sortie sur la droite?

http://scienceblogs.com/ethicsandscience/upload/2006/12/RushHour.jpg

31
demandé sur Earlz 2010-05-21 00:53:41

7 réponses

pour L'Heure de pointe classique, ce problème est très tractable avec une première recherche étendue simple. La configuration initiale connue la plus dure réclamée nécessite 93 mouvements à résoudre, avec un total de seulement 24132 configurations accessibles. Même un algorithme de recherche étendue-première mise en œuvre naïvement peut explorer tout l'espace de recherche en moins d'une seconde sur une machine même modeste.

Références


le résolveur Java

voici le code source complet d'un résolveur exhaustif de première recherche, écrit en style 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");
    }
}

il y a deux lignes dignes de mention dans le code source:

  • Le break; lorsqu'une solution est trouvée
    • ceci est maintenant commenté de sorte que la première recherche étendue explore le entier espace de recherche, pour confirmer les chiffres donnés dans le site Web lié ci-dessus
  • Le current = sb.toString(); dans slide
    • Essentiellement cela compte chaque mouvement de n'importe quelle voiture comme un mouvement. Si une voiture est déplacée 3 espaces à gauche, ça fait 3 mouvements. Pour combiner cela en un seul mouvement (puisqu'il s'agit de la même voiture se déplaçant dans la même direction), il suffit de commenter cette ligne. Le site Web lié ne reconnaît pas combo, donc cette ligne est maintenant décommentée pour correspondre au nombre minimum de mouvements donnés. Avec le comptage combiné, le problème des 93 mouvements ne nécessite que 49 mouvements combinés. C'est-à-dire, s'il y a un gardien de parc dans le lot qui déplace ces en voiture, il n'avait besoin que d'entrer et de sortir 49 fois.

aperçu de l'algorithme

l'algorithme est essentiellement une première recherche étendue, mise en œuvre avec une file d'attente comme est typique. Une carte antérieure est conservée de sorte que tout état puisse être retracé à l'état initial. Une clé ne sera jamais reconfiguré, et que les inscriptions sont insérés dans la largeur-premier ordre de recherche, le chemin le plus court est garanti.

un État est représenté par NxM - longueur String . Chaque char représente une entité sur le tableau, stockée dans la rangée-ordre majeur.

États voisins sont trouvés en scannant les 4 directions d'un espace vide, à la recherche d'un type de voiture approprié, en le faisant glisser comme la chambre peut accommoder.

il y a beaucoup de travail redondant ici (par exemple de longues "allées" sont scannées plusieurs fois), mais comme mentionné ci-dessus, bien que la version généralisée est PSPACE-complète, la variante Rush Hour classique est très tractable par la force brute.

Wikipedia références

28
répondu polygenelubricants 2010-05-23 17:39:04

Voici ma réponse. Il résout le grand maître puzzle en un peu moins de 6 secondes.

Il utiliser une largeur de recherche (BFS). Le truc est de chercher une disposition de carte que vous avez déjà vue dans des recherches précédentes et d'annuler cette séquence. En raison de la BFS si vous avez vu cette mise en page avant que vous avez déjà obtenu là un chemin plus court alors laissez ce squence continuer à essayer de le résoudre plutôt que celui plus long.

#!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 = ; $dir = ; $repeat = ;

            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 = ; $dir = ; $repeat = ;

            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";
    }
}
7
répondu Rodos 2011-05-18 11:37:25

il y a en fait un papier du MIT qui fait spécifiquement référence à L'Heure de pointe (j'ai utilisé le terme de recherche" puzzles à glissières")

5
répondu Daniel DiPaolo 2010-05-20 20:57:41

Vous devriez recurse (votre "retour en arrière" de la solution). C'est probablement la seule façon de résoudre des puzzles comme celui-ci; la question Est de savoir comment le faire rapidement.

Comme vous l'avez remarqué, l'espace de recherche sera grande, mais pas trop grand, si vous avez une taille raisonnable conseil d'administration. Par exemple, vous avez dessiné une grille de 6x6 avec 12 voitures dessus. En supposant que chaque voiture est de taille 2, cela donne 5 espaces / per, donc au plus 5^12 = 244.140.625 positions potentielles. Cela correspond même à un entier de 32 bits. Donc on la possibilité est d'allouer un tableau énorme, une fente par position potentielle, et utiliser la memoization pour s'assurer que vous ne répétez pas une position.

la prochaine chose à noter est que la plupart de ces positions" potentielles " ne sont pas, en fait, possible (ils impliqueraient des voitures se chevauchant). Ainsi, à la place, utilisez une table de hachage pour garder la trace de chaque position que vous avez visitée. Cela aura une petite mémoire aérienne par entrée, mais il sera probablement plus efficace de l'espace que la solution de" tableau énorme". Cela prendra cependant un peu plus de temps pour chaque accès.

comme le dit le papier du MIT dans réponse de @Daniel , le problème est PSPACE-complet, ce qui signifie que beaucoup des trucs utilisés pour réduire la complexité des problèmes NP ne peut probablement pas être utilisé.

cela dit, l'une ou l'autre des deux solutions ci-dessus au problème des positions répétées devrait fonctionner pour les petites grilles. Tout sera déterminé par la taille du problème, et la quantité de mémoire de votre ordinateur; mais l'exemple que vous avez affichée doit être pas mal du tout, même pour un pc ordinaire.

3
répondu Jesse Beder 2017-05-23 12:09:51

vient de finir d'écrire mon implémentation et d'expérimenter avec elle. Je suis d'accord avec polygenelubricants que l'espace d'état est vraiment petit pour le jeu classique (6x6 conseil d'administration). Cependant, j'ai essayé une implémentation de recherche intelligente ( a* search ). J'étais curieux au sujet de la réduction de l'espace d'état exploré par rapport à un BFS simple.

a * algorithme peut être considéré comme une généralisation de la recherche BFS. La décision de la voie à explorer est déterminé par un score qui combine la longueur du chemin (c.-à-d. le nombre de mouvements) et une limite inférieure sur le nombre de mouvements restants. La façon dont j'ai choisi de calculer cette dernière, est d'obtenir la distance de la voiture rouge de la sortie, puis ajouter 1 pour chaque véhicule dans le chemin, il doit être déplacé au moins une fois afin de dégager la voie. Quand je remplace le calcul de la limite inférieure par une constante 0, j'obtiens un comportement BFS régulier.

après inspection de quatre puzzles de cette liste , j'ai trouvé Qu'une recherche de* explore en moyenne 16% moins d'États qu'un BFS régulier.

2
répondu Eyal Schneider 2010-05-24 23:28:57

je pense que la récursion est une mauvaise idée, sauf si vous gardez une trace de ce que vous avez déjà visité; vous pourriez finir par se reproduire infiniment en déplaçant une voiture d'avant en arrière.

peut-être que c'est un bon début: représenter et stocker chaque État de la carte comme un graphe non dirigé. Ensuite, pour chaque mouvement possible, vérifiez avec les états passés pour vous assurer que vous ne frappez pas juste le même état à nouveau.

maintenant faites un autre graphe non-dirigé où les noeuds représentent les États et les arêtes représentent la capacité à passer d'un état à un autre par le déplacement d'une voiture. Explorer les états jusqu'à ce que l'un d'eux est une solution. Suivez ensuite les bords du début à trouver une trajectoire déplacer.

0
répondu Ross 2010-05-20 23:01:45

j'ai écrit un solveur sudoku. Bien que les détails sont complètement différents, je pense que le problème est similaire. D'une part, essayer de faire des heuristiques intelligentes dans un solveur sudoku est beaucoup plus lent qu'une solution de force brute. Essayer chaque mouvement, avec quelques heuristiques simples et pas de duplicata est la voie à suivre. Il est un peu plus difficile de vérifier s'il y a des doubles États du tableau à l'Heure de pointe, mais pas beaucoup.

si vous regardez le tableau dans votre échantillon, il n'y a que 4 valide se déplace. À tout moment, il n'y aura que quelques mouvements valides.

à chaque niveau de récursion, copiez l'état du tableau et essayez chaque mouvement valide sur le tableau. Pour chaque case vide, déplacer chaque voiture sur place. Si le nouvel état du tableau n'est pas dans la liste de l'histoire, alors recommencer un autre niveau. Par liste d'histoire, j'entends donner à chaque niveau de récursion accès à chaque carte qui a conduit à cet état, probablement dans une liste liée. Utilisez des coups de fouet pour jeter rapidement inégal Unis.

la clé pour cela est d'avoir un État de carte simple qui peut être facilement copié et modifié. Probablement un tableau avec un int par carré disant quelle voiture couvre ce carré, s'il y en a. Ensuite, vous avez juste besoin d'itérer à travers les carrés et de comprendre les mouvements juridiques. Un coup légal signifie cases vides entre les carrés et une voiture orientée vers elle.

comme pour sudoku, la pire option possible serait un algorithme génétique.

0
répondu drawnonward 2010-05-21 03:19:29