14

15マスのパズル用のA*ソルバーを作成しようとしています。

代替テキスト

目標は、タイルが自然な位置に表示されるようにタイルを再配置することです。一度にスライドできるタイルは1つだけです。パズルの各可能な状態は、検索グラフのノードです。

h(x)関数では、すべてのタイルにわたって、目標状態からのタイルの転位の合計を使用しています。上の画像では、5は位置0,0にあり、位置1,0に属しているため、h(x)関数に1を与えます。次のタイルは11で、0,1にあり、2,2に属しているため、h(x)に3を寄与します。等々。編集:私は今、これが彼らが「マンハッタン距離」または「タクシー距離」と呼んでいるものであることを理解しています。

私はg(x)のステップカウントを使用しています。私の実装では、状態グラフ内の任意のノードについて、gは前のノードのgからちょうど+1です。

連続するノードを見つけるために、パズルの「穴」をどこに移動できるかを調べます。表示されるパズルの状態(別名ノード)には3つの隣接点があります。穴は北、西、または東に移動できます。

私のA*検索は、20秒、180秒でソリューションに収束する場合もあれば、まったく収束しない場合もあります(10分以上待機)。hは妥当だと思います。gを適切にモデル化したかどうか疑問に思います。言い換えると、私のA *関数が最短経路ではない経路を経由してグラフ内のノードに到達している可能性はありますか?

たぶん私は十分長く待っていませんでしたか?たぶん10分では十分ではありませんか?

完全にランダムな配置の場合(パリティの問題がないと仮定)、A *ソリューションが調べる順列の平均数はいくつですか?(数学を見せてください)

コード内の論理エラーを探しますが、それまでの間、ヒントはありますか?

(ps:それはJavascriptで行われます)。

また、いいえ、これはCompSciの宿題ではありません。それは単なる個人的な探求です。私はJavascriptを学ぼうとしています。


編集:実行時間はヒューリスティックに大きく依存していることがわかりました。誰かが言及した記事からヒューリスティックに適用される10倍の係数を見たのですが、なぜ10倍なのか疑問に思いました。なぜ線形なのですか?これはjavascriptで行われるため、コードを変更して、現在検討中のノードでhtmlテーブルを動的に更新できます。これにより、アルゴリズムの進行中にアルゴリズムを確認することができました。通常のタクシー距離ヒューリスティックで、収束に失敗するのを観察しました。

一番上の列には5と12があり、彼らはぶらぶらし続けました。1、2、3、4が一番上の行に忍び寄るのが見えますが、その後はドロップアウトし、他の数字はそこに移動します。私が見たかったのは、1、2、3、4のようなものが上に忍び寄り、そこにとどまっていることでした。

私は自分自身に思いました-これは私がこれを個人的に解決する方法ではありません。これを手動で行うことで、一番上の行、次に2番目の行、次に3番目と4番目の行を同時に解決します。

そこで、h(x)関数を微調整して、上位の行と「左側の」列にさらに大きな重みを付けました。その結果、A*ははるかに速く収束しました。「無期限」ではなく、3分で実行されるようになりました。私が話した「覗き見」で、小さい数字が高い列に忍び寄り、そこにとどまるのを見ることができます。これは正しいことのように見えるだけでなく、はるかに高速に実行されます。

私はたくさんのバリエーションを試しているところです。A*ランタイムがヒューリスティックに非常に敏感であることはかなり明らかなようです。現在、私が見つけた最高のヒューリスティックは dislocation * ((4-i) + (4-j))、iとjが行と列であり、転位がタクシーの距離である場所の合計を使用しています。

私が得た結果の興味深い部分の1つは、特定のヒューリスティックを使用すると、パスを非常にすばやく見つけることができますが、それは明らかに最短パスではありません。これは、ヒューリスティックに重み付けしているためだと思います。あるケースでは、10秒で178ステップのパスを取得しました。私自身の手作業は87回の動きで解決策を生み出します。(10秒以上)。さらなる調査が必要です。

その結果、収束が速くなる必要があり、パスは間違いなく最短ではありません。これについてもっと考えなければなりません。


コード:

var stop = false; 
function Astar(start, goal, callback) {
    // start and goal are nodes in the graph, represented by 
    // an array of 16 ints.  The goal is:  [1,2,3,...14,15,0] 
    // Zero represents the hole. 

    // callback is a method to call when finished. This runs a long time, 
    // therefore we need to use setTimeout() to break it up, to avoid
    // the browser warning like "Stop running this script?"

    // g is the actual distance traveled from initial node to current node.
    // h is the heuristic estimate of distance from current to goal.
    stop = false;
    start.g = start.dontgo = 0;

    // calcHeuristic inserts an .h member into the array
    calcHeuristicDistance(start);

    // start the stack with one element
    var closed = [];       // set of nodes already evaluated.
    var open = [ start ];  // set of nodes to evaluate (start with initial node)

    var iteration = function() {
        if (open.length==0) {
            // no more nodes.  Fail. 
            callback(null);
            return;
        }
        var current = open.shift();  // get highest priority node

        // update the browser with a table representation of the 
        // node being evaluated
        $("#solution").html(stateToString(current));

        // check solution returns true if current == goal
        if (checkSolution(current,goal)) {
            // reconstructPath just records the position of the hole 
            // through each node
            var path= reconstructPath(start,current);
            callback(path);
            return;
        }

        closed.push(current);

        // get the set of neighbors.  This is 3 or fewer nodes.
        // (nextStates is optimized to NOT turn directly back on itself)
        var neighbors = nextStates(current, goal);

        for (var i=0; i<neighbors.length;  i++) {
            var n = neighbors[i];

            // skip this one if we've already visited it
            if (closed.containsNode(n)) continue;

            // .g, .h, and .previous get assigned implicitly when 
            // calculating neighbors.  n.g is nothing more than
            // current.g+1 ;

            // add to the open list
            if (!open.containsNode(n)) {
                // slot into the list, in priority order (minimum f first)
                open.priorityPush(n);
                n.previous = current;
            }
        }

        if (stop) {
            callback(null);
            return;
        }

        setTimeout(iteration, 1);
    };

    // kick off the first iteration
    iteration();

    return null;
}
4

8 に答える 8

5

A-star 検索は、まだ解決されていないすべてのパスが現在のソリューションよりも少ない手数で解決できないことを証明することにより、最適なソリューションを見つけます。あなたは最善の解決策を探しているのではなく、最速の解決策を探しています。したがって、最初の解を返すことでアルゴリズムを最適化し、移動回数をヒューリスティック関数よりも低く重み付けすることで、ヒューリスティックが過大評価を返す可能性があります。

通常、ヒューリスティック関数自体は、マンハッタン距離と線形競合によってモデル化するのが最適です。マンハッタンの距離は、他の回答とウィキペディアの記事でよく説明されており、あなたはそれを理解しているようです。線形競合は、ソリューションに到達するために交換する必要があるブロックの各ペアのマンハッタン距離に 2 を追加します。たとえば、行に「3 2 1 4」が含まれている場合、1 つと 3 つを交換する必要があり、そのためには 1 つを別の行に移動する必要があります。

パターン データベースを使用することはオプションであり、検索で特定のデッドエンドを回避するのに役立ちます。15 個のパズルでこれを行う場合のメモリ使用量は管理しやすいはずです。

于 2009-12-23T23:02:34.603 に答える
4

A*の代わりにIDA*を使用してください。必要なメモリははるかに少なくなります。ヒューリスティックとして、高橋健一郎が開発した「歩行距離」は、わずか25 kBのメモリを使用しますが、はるかに効果的です。
ここここに英語の翻訳があります。

于 2012-01-31T16:10:15.303 に答える
2

テストデータには何を使用していますか? ランダムだと半分くらいの確率でパズルが解けません。残りを同じ位置に保持したまま 2 つの牌を切り替えることはできないため、2 つの牌が入れ替わったままほぼ終了位置に到達した場合、目的の位置に移動できない可能性があり、検索アルゴリズムもありません。正常に終了する可能性があります。

19世紀、アメリカのパズルマスター、サム・ロイドは、15と14を逆にしてこれらのおもちゃを販売し、タイルを切り替える解決策を示すことができた人に大きな賞品を提供しました(おそらく、私が持っている小さなドライバーを除く). 今日の法的状況では、彼があえてしたかどうかはわかりません.

1 つの可能性は、正しい構成または 15-14 構成のいずれかにしようとすることです。

于 2009-12-23T21:47:05.850 に答える
2

はい、それがこの問題が行われていると聞いた方法です。g(x) は発生したタイル スライドの数であり、h(x) はすべてのタイルが必要な正方形から離れている合計距離です。今日まで、このアプローチ (マンハッタン ヒューリスティック) 以外に使用されているものは見たことがありませんでしたが、いわゆる斜めのショートカットを見つけました。ぜひチェックしてみてください。

于 2009-12-23T19:40:58.690 に答える
1

私が学んだこと

  • どうやらこれはよく知られていることですが、私にはそうではありませんでした。A* 収束はヒューリスティック関数に非常に敏感です。
  • 上位 2 行を他の行よりも大きく重み付けするヒューリスティックを作成すると、はるかに迅速に収束しますが、パスは一般的にはるかに長くなります。
  • ここに示されている対角 H(x) 関数は、15 平方パズルの場合、マンハッタン距離よりもはるかに速く収束することがわかりました。
  • より迅速な収束を促進するヒューリスティック関数を使用しても、実行時間には大きなばらつきがあります。10 秒でパスを見つけることもあります。場合によっては10分。時にはもっと長く。
  • 対角ヒューリスティックを使用して、見つかったパスで必要な移動の数は、30 から 110 の範囲です。
于 2009-12-24T17:40:57.333 に答える
1

私はそのようなアルゴリズムを一度プログラムしました ( windowsApp )。次のような経験があります

1) ロボットが (ほぼ) 最適なソリューションを使用している場合、ロボットが動作しているのを見るのが最も楽しいです。(人間の観察者にとって、ロボットがどのように「考える」かを理解することは不可能であり、混乱から秩序への移行は突然です)

2) 最適解を見つけたい場合は、h() 関数で真の距離を過小評価する必要があります。過大評価すると、最適な値が見つかりません。

3) 潜在的な状態空間は 15!/2 (10^12) と巨大です。不適切なヒューリスティック関数を使用すると、データ セットがメイン メモリのサイズをはるかに超えて大きくなり、データ アクセスごとに複数のディスク アクセスが必要になります。これが発生すると、実行時間は「無限」になります。

于 2009-12-31T17:51:22.533 に答える
0

最初に中間目標を狙うと収束が早くなるかもしれません。たとえば、上と右の行のみをスコアリングします。これらの行を配置するのにそれほど時間はかからないはずです。その後、残りの 3x3 を解くことができます。

于 2009-12-23T22:19:40.833 に答える
-3
check this
import javax.swing.*; 
import java.awt.*;
import java.awt.event.*;
import java.lang.Object;

class Puzzle extends JPanel implements ActionListener
{
    JButton[] b = new JButton[16];
    Puzzle()
    {
        b[0] = new JButton("4");
        b[1] = new JButton("11");
        b[2] = new JButton("5");
        b[3] = new JButton("9");
        b[4] = new JButton("1");
        b[5] = new JButton("10");
        b[6] = new JButton("12");
        b[7] = new JButton("13");
        b[8] = new JButton("15");
        b[9] = new JButton("14");
        b[10] = new JButton("3");
        b[11] = new JButton("2"); 
        b[12] = new JButton("7");
        b[13] = new JButton("8");
        b[14] = new JButton("6");
        b[15] = new JButton("");
        GridLayout grid = new GridLayout(4,4);
        setLayout(grid);
        for(int i=0;i<16;i++)
            add(b[i]);
        for(int i=0;i<16;i++)
            b[i].addActionListener(this);
    }
    public void actionPerformed(ActionEvent e)
    {
        /*if(e.getSource()==b[11])
        {
            if(b[15].getText()=="")
            {
                b[15].setText("");
            }
        }
        else if(e.getSource()==b[3])
        {
            if(b[2].getText()=="")
            {
                b[2].setText("");
            }
        }*/
        for(int i=0;i<16;i++)
        {
            System.out.println(e.getSource());
            if(e.getSource()==b[i])
            {
                if(i==5 || i==6 || i==9 || i==10)
                {   
                    if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                else if(i==4 || i==8)
                {   
                    if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                else if(i==7 || i==11)
                {   
                    if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==0)
                {   
                    if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==3)
                {   
                    if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==15)
                {   
                    if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==12)
                {   
                    if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==1 || i==2)
                {   
                    if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }                   
                    else if(b[i+4].getText()=="")
                    {
                        b[i+4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
                if(i==13 || i==14)
                {   
                    if(b[i+1].getText()=="")
                    {
                        b[i+1].setText(b[i].getText());
                        b[i].setText("");
                    }
                    else if(b[i-1].getText()=="")
                    {
                        b[i-1].setText(b[i].getText());
                        b[i].setText("");
                    }                   
                    else if(b[i-4].getText()=="")
                    {
                        b[i-4].setText(b[i].getText());
                        b[i].setText("");
                    }
                }
            }
        }
        //System.out.println(e.getActionCommand());
        }

    public static void main(String[] args)
    {
        JFrame frame = new JFrame("15-Puzzle");             

        //frame.setContentPane(panel);

JComponent newContentPane = new Puzzle();
        //newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);





        //panel.add(button);  
        frame.setSize(400,400);


        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}
于 2010-04-16T18:21:08.140 に答える