4 Stimmen

Heuristik mit der geringsten Anzahl von Umdrehungen

Kann man irgendwie sicherstellen, dass die Heuristik der geringsten Anzahl von Umdrehungen durch irgendetwas anderes als durch eine Suche in der Breite zuerst erfüllt wird? Vielleicht wäre eine weitere Erklärung hilfreich.

Ich habe ein Zufallsdiagramm, das dem hier ähnelt:

0 1 1 1 2
3 4 5 6 7
9 a 5 b c
9 d e f f
9 9 g h i

Ausgehend von der linken oberen Ecke muss ich wissen, wie viele Schritte ich am wenigsten brauche, um zur rechten unteren Ecke zu gelangen. In diesem Zufallsdiagramm werden beispielsweise die drei 1en in der obersten Reihe als ein einziger Knoten betrachtet, und jeder benachbarte (nicht diagonale) verbundene Knoten ist ein möglicher nächster Zustand. Die möglichen nächsten Zustände sind also von Anfang an die 1 in der obersten Reihe oder die 3 in der zweiten Reihe.

Derzeit verwende ich eine bidirektionale Suche, aber die Explosivität der Baumgröße nimmt ziemlich schnell zu. Ich habe es beim besten Willen nicht geschafft, das Problem so anzupassen, dass ich den Knoten sicher Gewichte zuweisen kann und sie die geringste Anzahl von Zustandsänderungen gewährleisten, um das Ziel zu erreichen, ohne dass es zu einer Breitensuche wird. Wenn man sich das Problem wie einen Stadtplan vorstellt, wäre die Heuristik die geringste Anzahl von Abbiegungen, um das Ziel zu erreichen.

Es ist sehr wichtig, dass die geringste Anzahl von Umdrehungen das Ergebnis dieser Suche ist, da dieser Wert Teil der Heuristik für ein komplexeres Problem ist.

1voto

Joel Punkte 5588

Dieses Papier enthält eine etwas schnellere Version des Algorithmus von Dijsktra, die den konstanten Term verringert. Allerdings immer noch O(n), da man wirklich jeden Knoten betrachten muss.

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.54.8746&rep=rep1&type=pdf

1voto

JPvdMerwe Punkte 3259

EDIT: DIE VORHERIGE VERSION WAR FALSCH UND WURDE KORRIGIERT

Da ein Djikstra ausfällt. Ich empfehle ein einfaches DP, das den Vorteil hat, dass es in optimaler Zeit läuft und Sie keinen Graphen konstruieren müssen.

D[a][b] ist die minimale Entfernung zu x=a y y=b nur Knoten zu verwenden, bei denen die x<=a y y<=b .

Und da man sich nicht diagonal bewegen kann, muss man nur auf D[a-1][b] y D[a][b-1] bei der Berechnung von D[a][b]

Daraus ergibt sich die folgende Rekursionsbeziehung:

D[a][b] = min(if grid[a][b] == grid[a-1][b] then D[a-1][b] else D[a-1][b] + 1, if grid[a][b] == grid[a][b-1] then D[a][b-1] else D[a][b-1] + 1)

Allerdings scheitert in diesem Fall nur die obige Vorgehensweise:

0 1 2 3 4
5 6 7 8 9
A b d e g
A f r t s
A z A A A
A A A f d

Daher müssen Sie das Minimum jeder Gruppe von Knoten, die Sie bisher gefunden haben, zwischenspeichern. Und anstatt sich die D[a][b] Sie betrachten das Minimum der Gruppe bei grid[a][b] .

Hier ist etwas Python-Code: Hinweis grid ist das Gitter, das Sie als Eingabe erhalten, und es wird angenommen, dass das Gitter N von N

groupmin = {}

for x in xrange(0, N):
    for y in xrange(0, N):
        groupmin[grid[x][y]] = N+1#N+1 serves as 'infinity'

#init first row and column
groupmin[grid[0][0]] = 0
for x in xrange(1, N):
    gm = groupmin[grid[x-1][0]]
    temp = (gm) if grid[x][0] == grid[x-1][0] else (gm + 1)
    groupmin[grid[x][0]] = min(groupmin[grid[x][0]], temp); 

for y in xrange(1, N):
    gm = groupmin[grid[0][y-1]]
    temp = (gm) if grid[0][y] == grid[0][y-1] else (gm + 1)
    groupmin[grid[0][y]] = min(groupmin[grid[0][y]], temp); 

#do the rest of the blocks
for x in xrange(1, N):
    for y in xrange(1, N):
        gma = groupmin[grid[x-1][y]]
        gmb = groupmin[grid[x][y-1]]
        a = (gma) if grid[x][y] == grid[x-1][y] else (gma + 1)
        b = (gmb) if grid[x][y] == grid[x][y-1] else (gma + 1)
        temp = min(a, b)
        groupmin[grid[x][y]] = min(groupmin[grid[x][y]], temp); 

ans = groupmin[grid[N-1][N-1]]

Dies läuft in O(N^2 * f(x)) wobei f(x) ist die Zeit, die die Hash-Funktion benötigt, die normalerweise O(1) Zeit, und dies ist eine der besten Funktionen, die man sich wünschen kann, und sie hat einen viel niedrigeren Konstantenfaktor als die von Djikstra.

Sie sollten problemlos in der Lage sein, N's von bis zu einigen Tausend in einer Sekunde zu verarbeiten.

0voto

Jason Orendorff Punkte 39655

Kann man irgendwie sicherstellen, dass die Heuristik der geringsten Anzahl von Umdrehungen durch irgendetwas anderes als durch eine Suche in der Breite zuerst erfüllt wird?

A schneller Weg oder einen einfacheren Weg? :)

Sie können abwechselnd von beiden Enden aus in die Breite suchen, bis sich die beiden Regionen in der Mitte treffen. Dies ist viel schneller, wenn der Graph eine große Ausdehnung hat, wie z. B. bei einem Stadtplan, aber der schlimmste Fall ist derselbe. Es kommt wirklich auf den Graphen an.

0voto

MAK Punkte 25200

Dies ist meine Implementierung unter Verwendung eines einfachen BFS. Ein Dijkstra würde auch funktionieren (ersetzen Sie ein stl::priority_queue die nach absteigenden Kosten für die stl::queue ), aber das wäre wirklich zu viel des Guten.

Dabei ist zu beachten, dass wir eigentlich in einem Graphen suchen, dessen Knoten nicht genau den Zellen in der gegebenen Matrix entsprechen. Um zu diesem Graphen zu gelangen, habe ich ein einfaches DFS-basiertes Floodfill verwendet (man könnte auch BFS verwenden, aber DFS ist für mich etwas kürzer). Dabei werden alle zusammenhängenden Komponenten mit gleichem Zeichen gefunden und der gleichen Farbe/Knoten zugeordnet. So können wir nach dem Floodfill herausfinden, zu welchem Knoten jede Zelle im zugrundeliegenden Graphen gehört, indem wir uns den Wert von colour[row][col] ansehen. Dann iteriere ich einfach über die Zellen und finde alle Zellen heraus, bei denen benachbarte Zellen nicht die gleiche Farbe haben (d. h. zu verschiedenen Knoten gehören). Dies sind also die Kanten unseres Graphen. Ich pflege eine stl::set von Kanten, wenn ich über die Zellen iteriere, um doppelte Kanten zu eliminieren. Danach ist es eine einfache Angelegenheit, eine Adjazenzliste aus der Liste der Kanten zu erstellen und wir sind bereit für ein bfs.

Code (in C++):

#include <queue>
#include <vector>
#include <iostream>
#include <string>
#include <set>
#include <cstring>

using namespace std;
#define SIZE 1001
vector<string> board;

int colour[SIZE][SIZE];
int dr[]={0,1,0,-1};
int dc[]={1,0,-1,0};

int min(int x,int y){ return (x<y)?x:y;}
int max(int x,int y){ return (x>y)?x:y;}

void dfs(int r, int c, int col, vector<string> &b){
    if (colour[r][c]<0){
        colour[r][c]=col;
        for(int i=0;i<4;i++){
            int nr=r+dr[i],nc=c+dc[i];
            if (nr>=0 && nr<b.size() && nc>=0 && nc<b[0].size() && b[nr][nc]==b[r][c])
                dfs(nr,nc,col,b);
        }
    }
}

int flood_fill(vector<string> &b){
    memset(colour,-1,sizeof(colour));
    int current_node=0;
    for(int i=0;i<b.size();i++){
        for(int j=0;j<b[0].size();j++){
            if (colour[i][j]<0){
                dfs(i,j,current_node,b);
                current_node++;
            }
        }
    }
    return current_node;
}

vector<vector<int> > build_graph(vector<string> &b){
    int total_nodes=flood_fill(b);
    set<pair<int,int> > edge_list;
    for(int r=0;r<b.size();r++){
        for(int c=0;c<b[0].size();c++){
            for(int i=0;i<4;i++){
                int nr=r+dr[i],nc=c+dc[i];
                if (nr>=0 && nr<b.size() && nc>=0 && nc<b[0].size() && colour[nr][nc]!=colour[r][c]){
                    int u=colour[r][c], v=colour[nr][nc];
                    if (u!=v) edge_list.insert(make_pair(min(u,v),max(u,v)));
                }
            }
        }
    }
    vector<vector<int> > graph(total_nodes);
    for(set<pair<int,int> >::iterator edge=edge_list.begin();edge!=edge_list.end();edge++){
        int u=edge->first,v=edge->second;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    return graph;
}

int bfs(vector<vector<int> > &G, int start, int end){
    vector<int> cost(G.size(),-1);
    queue<int> Q;
    Q.push(start);
    cost[start]=0;
    while (!Q.empty()){
        int node=Q.front();Q.pop();
        vector<int> &adj=G[node];
        for(int i=0;i<adj.size();i++){
            if (cost[adj[i]]==-1){
                cost[adj[i]]=cost[node]+1;
                Q.push(adj[i]);
            }
        }
    }
    return cost[end];
}

int main(){
    string line;
    int rows,cols;
    cin>>rows>>cols;
    for(int r=0;r<rows;r++){
        line="";
        char ch;
        for(int c=0;c<cols;c++){
            cin>>ch;
            line+=ch;
        }
        board.push_back(line);
    }
    vector<vector<int> > actual_graph=build_graph(board);
    cout<<bfs(actual_graph,colour[0][0],colour[rows-1][cols-1])<<"\n";
}

Dies ist nur ein schneller Hack, es können noch viele Verbesserungen vorgenommen werden. Aber ich denke, es ist ziemlich nahe am Optimum, was die Laufzeitkomplexität angeht, und sollte schnell genug für Boards mit einer Größe von mehreren Tausend laufen (vergessen Sie nicht, die #define von SIZE ). Außerdem habe ich es nur mit dem einen Fall getestet, den Sie angegeben haben. Also, wie Knuth sagte: "Hüten Sie sich vor Fehlern im obigen Code; ich habe ihn nur als korrekt bewiesen, nicht ausprobiert." :).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X