4

とにかく、最小数のヒューリスティックが幅優先検索以外で満たされることを保証する方法はありますか? おそらく、もう少し説明が役立つでしょう。

次のようなランダムなグラフがあります。

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

左上隅から始めて、右下隅に到達するのに必要な最小ステップ数を知る必要があります。接続された色の各セットは 1 つのノードであると想定されるため、たとえば、このランダム グラフでは、一番上の行にある 3 つの 1 はすべて 1 つのノードと見なされ、隣接する (対角線ではない) 接続されたすべてのノードが可能な次の状態になります。したがって、最初から考えられる次の状態は、一番上の行の 1 または 2 番目の行の 3 です。

現在、私は双方向検索を使用していますが、ツリー サイズの爆発性はかなり急速に上昇しています。私の人生では、問題を調整してノードに安全に重みを割り当て、幅優先検索にならずに目標に到達するための状態変化の数を最小限に抑えることができませんでした。これを街の地図と考えると、ヒューリスティックは目標に到達するまでの最小ターン数になります。

より複雑な問題のヒューリスティックの一部であるため、この検索の結果が最小のターン数であることが非常に重要です。

4

9 に答える 9

3

あなたは、数字の各グループが 1 つのノードを表し、各ノードが隣接するノードに接続されていると言いました。次に、これは単純な最短経路の問題であり、(たとえば) Dijkstra のアルゴリズムを使用でき、各エッジの重みは 1 (1 ターン) です。

于 2010-01-20T16:27:39.673 に答える
2

これは、ダイクストラのアルゴリズムのように聞こえます。最も困難な部分は、グラフを適切に設定すること (どのノードがどの子を取得するかを追跡すること) にありますが、そのために CPU サイクルをいくらか当てることができれば、その後は問題ありません。

幅優先検索をしたくないのはなぜですか?

ここ..私は退屈していました:-) これはRubyで書かれていますが、あなたが始めることができるかもしれません. 注意してください、それはテストされていません。

class Node
  attr_accessor :parents, :children, :value
  def initialize args={}
    @parents = args[:parents] || []
    @children = args[:children] || []
    @value = args[:value]
  end

  def add_parents *args
    args.flatten.each do |node|
      @parents << node
      node.add_children self unless node.children.include? self
    end
  end

  def add_children *args
    args.flatten.each do |node|
      @children << node
      node.add_parents self unless node.parents.include? self
    end
  end
end

class Graph
  attr_accessor :graph, :root
  def initialize args={}
    @graph = args[:graph]
    @root = Node.new
    prepare_graph
    @root = @graph[0][0]
  end

  private

  def prepare_graph
# We will iterate through the graph, and only check the values above and to the
# left of the current cell.

    @graph.each_with_index do |row, i|
      row.each_with_index do |cell, j|
        cell = Node.new :value => cell #in-place modification!
        # Check above
        unless i.zero?
          above = @graph[i-1][j]
          if above.value == cell.value
            # Here it is safe to do this: the new node has no children, no parents.
            cell = above
          else
            cell.add_parents above
            above.add_children cell # Redundant given the code for both of those
            # methods, but implementations may differ.
          end
        end
        # Check to the left!
        unless j.zero?
          left = @graph[i][j-1]
          if left.value == cell.value
            # Well, potentially it's the same as the one above the current cell,
            # so we can't just set one equal to the other: have to merge them.
            left.add_parents cell.parents
            left.add_children cell.children
            cell = left
          else
            cell.add_parents left
            left.add_children cell
          end
        end
      end
    end
  end
end
     #j = 0, 1, 2, 3, 4
graph = [
         [3, 4, 4, 4, 2], # i = 0
         [8, 3, 1, 0, 8], # i = 1
         [9, 0, 1, 2, 4], # i = 2
         [9, 8, 0, 3, 3], # i = 3
         [9, 9, 7, 2, 5]] # i = 4

maze = Graph.new :graph => graph

# Now, going from maze.root on, we have a weighted graph, should it matter.
# If it doesn't matter, you can just count the number of steps.
# Dijkstra's algorithm is really simple to find in the wild.
于 2010-01-20T15:32:34.463 に答える
1

これは、この projeceuler http://projecteuler.net/index.php?section=problems&id=81と同じ問題のようです。

解の複雑さは O(n) n-> ノード数

必要なのはメモ化です。

各ステップで、最大 2 つの方向から取得できます。したがって、より安価なソリューションを選択してください。

それは次のようなものです(搭乗者の場合は0をとるコードを追加するだけです)

for i in row:
    for j in column:
         matrix[i][j]=min([matrix[i-1][j],matrix[i][j-1]])+matrix[i][j]

そして、左または下に移動すると、最も安価なソリューションが得られます

解は行列[MAX_i][MAX_j]にあります

上下に移動できる場合は、BigO よりもはるかに高くなります (最適なソリューションを見つけることができます)。

于 2010-01-20T15:31:58.363 に答える
1

この論文には、定数項を下げるダイスクトラのアルゴリズムのわずかに高速なバージョンがあります。ただし、実際にはすべてのノードを確認する必要があるため、それでも O(n) です。

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

于 2010-01-21T00:05:30.810 に答える
1

編集:以前のバージョンは間違っていて修正されました

ジクストラが出ているので。単純な DP をお勧めします。これには、最適な時間で実行され、グラフを作成する必要がないという利点があります。

D[a][b]は、とのノードのみx=aを使用する最小距離です。y=bx<=ay<=b

そして、斜めに動くことはできないので、計算するときはD[a-1][b]とを見るだけです。D[a][b-1]D[a][b]

これにより、次の再帰関係が得られます。

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)

ただし、この場合、上記のみを実行すると失敗します。

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

したがって、これまでに見つけたノードの各グループの最小値をキャッシュする必要があります。そして、あなたを見る代わりにD[a][b]、グループの最小値を見てくださいgrid[a][b]

ここにいくつかのPythonコードがあります:注gridは、入力として与えられたグリッドであり、グリッドNN

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

これは、ハッシュ関数の所要時間である通常のO(N^2 * f(x))時間で実行されます。これは、期待できる最高の関数の1つであり、Djikstraのものよりもはるかに低い定数係数を持っています.f(x)O(1)

1 秒間に最大数千の N を簡単に処理できるはずです。

于 2010-01-21T07:30:42.793 に答える
1

この調整されていない幅優先検索の C 実装は、100 x 100 のグリッドを 1 ミリ秒未満で処理できます。あなたはおそらくもっとうまくやれるでしょう。

int shortest_path(int *grid, int w, int h) {
    int mark[w * h];  // for each square in the grid:
                      // 0 if not visited
                      // 1 if not visited and slated to be visited "now"
                      // 2 if already visited
    int todo1[4 * w * h];  // buffers for two queues, a "now" queue
    int todo2[4 * w * h];  // and a "later" queue
    int *readp;            // read position in the "now" queue
    int *writep[2] = {todo1 + 1, 0};
    int x, y, same;

    todo1[0] = 0;
    memset(mark, 0, sizeof(mark));

    for (int d = 0; ; d++) {
        readp = (d & 1) ? todo2 : todo1;      // start of "now" queue
        writep[1] = writep[0];                // end of "now" queue
        writep[0] = (d & 1) ? todo1 : todo2;  // "later" queue (empty)

        // Now consume the "now" queue, filling both the "now" queue
        // and the "later" queue as we go. Points in the "now" queue
        // have distance d from the starting square. Points in the
        // "later" queue have distance d+1.
        while (readp < writep[1]) {
            int p = *readp++;
            if (mark[p] < 2) {
                mark[p] = 2;
                x = p % w;
                y = p / w;
                if (x > 0 && !mark[p-1]) {                // go left
                    mark[p-1] = same = (grid[p-1] == grid[p]);
                    *writep[same]++ = p-1;
                }
                if (x + 1 < w && !mark[p+1]) {            // go right
                    mark[p+1] = same = (grid[p+1] == grid[p]);
                    if (y == h - 1 && x == w - 2)
                        return d + !same;
                    *writep[same]++ = p+1;
                }
                if (y > 0 && !mark[p-w]) {                // go up
                    mark[p-w] = same = (grid[p-w] == grid[p]);
                    *writep[same]++ = p-w;
                }
                if (y + 1 < h && !mark[p+w]) {            // go down
                    mark[p+w] = same = (grid[p+w] == grid[p]);
                    if (y == h - 2 && x == w - 1)
                        return d + !same;
                    *writep[same]++ = p+w;
                }
            }
        }
    }
}
于 2010-01-20T23:30:39.823 に答える
1

A* が常に最短パスを見つけるためには、ヒューリスティックで実際のコストを常に過小評価する必要があります (ヒューリスティックは「許容可能」です)。グリッド上でユークリッド距離またはマンハッタン距離を使用するような単純なヒューリスティックは、計算が高速であり、実際のコスト以下であることが保証されているため、うまく機能します。

残念ながら、あなたの場合、ノードのサイズ/形状について簡単な仮定を立てることができない限り、できることはあまりありません。たとえば、次の場合に A から B に移動することを検討してください。

B 1 2 3 A
C 4 5 6 D
C 7 8 9 C
C e f g C
C C C C C

最短経路は A -> D -> C -> B ですが、空間情報を使用すると、おそらく 3 は D よりもヒューリスティック コストが低くなります。

状況によっては、答えがすぐに得られる限り、実際には最短経路ではない解決策を受け入れることができる場合があります。Christer Ericson (PS3 の God of War 3 のプログラマー) によるトピックに関する素敵なブログ投稿があります: http://realtimecollisiondetection.net/blog/?p=56

許容できないヒューリスティックについての私のアイデアは次のとおりです。ポイントから、目標に到達するまで水平に移動し、到達するまで垂直に移動し、行った状態変更の数を数えます。他のテスト パス (垂直方向と水平方向など) も計算し、最小値を最終的なヒューリスティックとして選択できます。ノードがほぼ同じサイズで規則的な形をしている場合 (私の例とは異なります)、これはかなりうまくいくかもしれません。より多くのテスト パスを実行すればするほど、精度は向上しますが、遅くなります。

役に立てば幸いです。意味をなさないものがある場合はお知らせください。

于 2010-01-20T21:53:56.323 に答える
0

幅優先探索以外の何かがヒューリスティックなターン数を確実に満たすようにする方法はありますか?

より速い方法、またはより簡単な方法?:)

2つの領域が中央で交わるまで、幅優先探索を両端から交互に行うことができます。都市地図のようにグラフにファンアウトが多い場合、これははるかに高速になりますが、最悪の場合も同じです。それは本当にグラフに依存します。

于 2010-01-20T19:06:15.907 に答える
0

これは単純な BFS を使用した私の実装です。Dijkstra も機能します (stl::priority_queueをコストの降順でソートするa に置き換えますstl::queue) が、非常にやり過ぎです。

ここで注意すべきことは、ノードが特定の配列内のセルに正確に対応していないグラフを実際に検索していることです。このグラフを表示するために、単純な DFS ベースのフラッドフィルを使用しました (BFS を使用することもできますが、私にとっては DFS の方が少し短くなっています)。これにより、接続された同じ文字コンポーネントがすべて検出され、それらが同じ色/ノードに割り当てられます。したがって、フラッドフィルの後、color[row][col] の値を見ることで、各セルが基になるグラフのどのノードに属しているかを知ることができます。次に、セルを繰り返し処理し、隣接するセルの色が同じでない (つまり、異なるノードにある) すべてのセルを見つけます。したがって、これらはグラフのエッジです。私は維持しますstl::set重複したエッジを排除するためにセルを反復処理するときのエッジの。その後、エッジのリストから隣接リストを作成するだけで、bfs の準備が整います。

コード (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";
}

これは簡単なハックであり、多くの改善を行うことができます。#defineしかし、ランタイムの複雑さに関しては最適にかなり近く、数千のサイズのボードに対して十分に高速に実行できると思います ( ofを変更することを忘れないでくださいSIZE)。また、私はあなたが提供した1つのケースでのみテストしました. したがって、Knuth が言ったように、「上記のコードのバグに注意してください。私はそれが正しいことを証明しただけで、試したわけではありません。」:)。

于 2010-01-20T23:12:57.260 に答える