web-dev-qa-db-ja.com

ある単語を別の単語に変換するための最短経路

データ構造プロジェクトの場合、一度に1文字だけ変更して、2つの単語("cat""dog"など)の間の最短パスを見つける必要があります。パスを見つけるために使用するスクラブルワードリストが提供されます。例えば:

cat -> bat -> bet -> bot -> bog -> dog

幅優先探索を使用して問題を解決しましたが、より良いものを探しています(辞書をトライで表現しました)。

より効率的な方法(速度とメモリの観点から)のアイデアをいくつか教えてください。ばかげているおよび/または挑戦的な何かが好ましい。

私は私の友人の一人(彼は後輩です)に尋ねました、そして彼はこの問題にno効率的な解決策があると言いました。彼は、私がアルゴリズムのコースを受講したときに、その理由を学ぶだろうと言いました。それについて何かコメントはありますか?

WordからWordに移行する必要があります。 cat -> dat -> dag -> dogには行けません。トラバーサルも印刷する必要があります。

20
dacman

新しい答え

最近の更新を考えると、ヒューリスティックとしてハミング距離を使用してA *を試すことができます。距離を過大評価することはないので、許容できるヒューリスティックです

古い答え

レーベンシュタイン距離 の計算に使用される動的プログラムを変更して、操作のシーケンスを取得できます。

編集:文字列の数が一定の場合、問題は多項式時間で解決できます。それ以外の場合、それはNP困難です(ウィキペディアにすべてあります)..あなたの友人がNP困難であるという問題について話していると仮定します。

編集:文字列の長さが等しい場合は、 ハミング距離 を使用できます。

15
Jacob

辞書の場合、BFSが最適ですが、必要な実行時間はそのサイズ(V + E)に比例します。 n文字の場合、辞書の全体が〜a ^ nになる可能性があります。ここで、aはアルファベットサイズです。辞書にチェーンの最後にあるはずの単語以外のすべての単語が含まれている場合、考えられるすべての単語をトラバースしますが、何も見つかりません。これはグラフ走査ですが、サイズが指数関数的に大きくなる可能性があります。

構造を「インテリジェントに」参照して多項式時間で実行することで、より高速に実行できるかどうか疑問に思われるかもしれません。答えは、私が思うに、いいえです。

問題:

単語が辞書にあるかどうかを確認するための高速(線形)方法、2つの単語u、vが与えられ、シーケンスu-> aがあるかどうかを確認します。1 -> a2 -> ...-> an -> v。

nP困難です。

証明:次のような3SATインスタンスを取得します

(pまたはqまたはrではない)および(pまたはqまたはrではない)

0 000 00から始めて、2 22222に進むことができるかどうかを確認します。

最初の文字は「終了しました」、次の3ビットはp、q、rを制御し、次の2ビットは句を制御します。

許可される単語は次のとおりです。

  • 0で始まり、0と1のみを含むもの
  • 2で始まり、合法であるもの。これは、0と1で構成されていることを意味します(最初の文字が2であることを除いて、すべての句ビットは変数ビットに従って正しく設定され、1に設定されます(したがって、これは式が満足できることを示します)。
  • 少なくとも2つの2で始まり、0と1で構成されるもの(正規表現:222 *(0 + 1)*、22221101のように、2212001ではない

0 000000から2222 22を生成するには、次のようにする必要があります。

(1)適切なビットを反転します-例: 0 100 1114ステップで。これには、3SATソリューションを見つける必要があります。

(2)最初のビットを2:2 100 111に変更します。ここで、これが実際に3SATソリューションであることが確認されます。

(3)変更2100111-> 2200111-> 2220111-> 2222111-> 2222211-> 2222221-> 2222222。

これらのルールは、チート(チェック)できないことを強制します。 2 222 22に進むことは、式が満足できる場合にのみ可能であり、それをチェックすることはNP困難です。それはもっと難しいかもしれないと思いますが(おそらく#PまたはFNP)、その目的にはNP困難で十分だと思います。

編集素集合データ構造 に興味があるかもしれません。これはあなたの辞書とお互いから到達できるグループの単語を取ります。すべての頂点からルートまたは他の頂点へのパスを保存することもできます。これにより、必ずしも最短のパスではなく、パスが提供されます。

9
sdcvvc

findingリンクの効率を変える方法があります-単語の長さごとに完全なグラフを作成するか、 BK- Tree たとえば、しかしあなたの友人は正しいです-BFSは最も効率的なアルゴリズムです。

ただし、ランタイムを大幅に改善する方法があります。ソースノードから単一のBFSを実行する代わりに、グラフの両端から開始し、フロンティアセットで共通ノードを見つけたときに終了する2つの幅優先探索を実行します。 。あなたがしなければならない仕事の量は、片方の端だけから検索する場合に必要な量の約半分です。

3
Nick Johnson

最初に、適切な長さではない単語を削除することで、少し速くすることができます。制限された辞書の多くは、CPUのキャッシュに収まります。おそらくすべてです。

また、すべてのstrncmp比較(すべてを小文字にしたと仮定)は、memcmp比較、または展開された比較である可能性があり、これにより高速化が可能になります。

プリプロセッサの魔法を使用して、そのWordの長さのタスクをハードコンパイルするか、一般的なWordの長さのタスクのいくつかの最適化されたバリエーションをロールすることができます。これらの余分な比較はすべて、純粋に展開された楽しみのために「消え去る」ことができます。

2
evildave

これは典型的な 動的計画法 問題です。距離の編集の問題を確認してください。

1
Freddy

あなたが探しているのは編集距離と呼ばれています。多くの異なるタイプがあります。

From( http://en.wikipedia.org/wiki/Edit_distance ): "情報理論とコンピュータサイエンスでは、2つの文字列間の編集距離は、1つを変換するために必要な操作の数です。それらを他に。」

Jazzyに関するこの記事(JavaスペルチェックAPI)には、これらの種類の比較の概要があります(これは、同様の問題です-修正案を提供します) http://www.ibm .com/developerworks/Java/library/j-jazzy /

1
Aidos

最長共通部分列を見つけることができるため、変更する必要のある文字を見つけることができます。

0
Calyth

私の直感では、より効率的な解決策はないという点で、あなたの友人は正しいと思いますが、それはあなたが毎回辞書をリロードしていることを前提としています。一般的な遷移の実行中のデータベースを保持する場合、解決策を見つけるためのより効率的な方法は確かにありますが、事前に遷移を生成し、どの遷移が役立つかを見つける必要があります(生成できないため)それらすべて!)はおそらくそれ自身の芸術です。

0
TTT
bool isadjacent(string& a, string& b)
{
  int count = 0;  // to store count of differences
  int n = a.length();

  // Iterate through all characters and return false
  // if there are more than one mismatching characters
  for (int i = 0; i < n; i++)
  {
    if (a[i] != b[i]) count++;
    if (count > 1) return false;
  }
  return count == 1 ? true : false;
}

// Wordを格納するキューアイテムと// Wordに到達するための最小チェーン長。

struct QItem
{
  string Word;
  int len;
};

//隣接する移動の最小数を使用して「開始」から「ターゲット」に到達するための最短チェーンの長さを//返します。 Dは辞書です

int shortestChainLen(string& start, string& target, set<string> &D)
{
  // Create a queue for BFS and insert 'start' as source vertex
  queue<QItem> Q;
  QItem item = {start, 1};  // Chain length for start Word is 1
  Q.Push(item);

  // While queue is not empty
  while (!Q.empty())
  {
    // Take the front Word
    QItem curr = Q.front();
    Q.pop();

    // Go through all words of dictionary
    for (set<string>::iterator it = D.begin(); it != D.end(); it++)
    {
        // Process a dictionary Word if it is adjacent to current
        // Word (or vertex) of BFS
        string temp = *it;
        if (isadjacent(curr.Word, temp))
        {
            // Add the dictionary Word to Q
            item.Word = temp;
            item.len = curr.len + 1;
            Q.Push(item);

            // Remove from dictionary so that this Word is not
            // processed again.  This is like marking visited
            D.erase(temp);

            // If we reached target
            if (temp == target)
              return item.len;
        }
    }
  }
  return 0;
}

// Driver program
int main()
{
  // make dictionary
  set<string> D;
  D.insert("poon");
  D.insert("plee");
  D.insert("same");
  D.insert("poie");
  D.insert("plie");
  D.insert("poin");
  D.insert("plea");
  string start = "toon";
  string target = "plea";
  cout << "Length of shortest chain is: "
       << shortestChainLen(start, target, D); 
  return 0; 
}

コピー元: https://www.geeksforgeeks.org/Word-ladder-length-of-shortest-chain-to-reach-a-target-Word/

0
Ankush Garg