【问题标题】:Shortest path to transform one word into another将一个单词转换为另一个单词的最短路径
【发布时间】:2010-12-04 01:33:14
【问题描述】:

对于数据结构项目,我必须找到两个单词之间的最短路径(如"cat""dog"),一次只更改一个字母。我们得到了一个拼字游戏单词列表,用于寻找我们的路径。例如:

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

我已经使用广度优先搜索解决了这个问题,但我正在寻找更好的东西(我用 trie 表示字典)。

请给我一些关于更有效方法的想法(在速度和内存方面)。一些荒谬和/或具有挑战性的东西是首选。

我问了我的一个朋友(他是一名大三学生),他说对于这个问题没有没有有效的解决方案。他说当我参加算法课程时,我会知道为什么。有什么相关的吗?

我们必须逐字逐句。我们不能去cat -> dat -> dag -> dog。我们还必须打印出遍历。

【问题讨论】:

  • 在你的例子中,为什么赌在那里?你连续两次更改同一个字母,它应该是:cat -> bat -> bot -> bog -> dog
  • Dacman,您介意分享一下使用启发式算法与 BFS 相比您的性能提高了多少?
  • 顺便说一句,从猫到狗的最短路径是 cat >> cot >> cog >> dog :)

标签: algorithm shortest-path edit-distance hamming-distance


【解决方案1】:
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;
}

// 存储单词和最小链长的队列项 // 到达单词。

struct QItem
{
  string word;
  int len;
};

// 返回从 'start' 到 'target' 的最短链的长度 // 使用最小数量的相邻移动。 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/

【讨论】:

    【解决方案2】:

    首先,您可以通过删除长度不正确的单词来加快速度。更多有限的字典将适合 CPU 的缓存。大概都是。

    此外,所有 strncmp 比较(假设您将所有内容都设为小写)都可以是 memcmp 比较,甚至是展开比较,这可以加快速度。

    您可以使用一些预处理器魔法并针对该字长硬编译该任务,或者针对常见字长滚动该任务的一些优化变体。所有这些额外的比较都可以“消失”以获得纯粹的展开乐趣。

    【讨论】:

      【解决方案3】:

      您要查找的内容称为编辑距离。有很多不同的类型。

      来自 (http://en.wikipedia.org/wiki/Edit_distance):“在信息论和计算机科学中,两个字符串之间的编辑距离是将其中一个字符串转换为另一个字符串所需的操作数。”

      这篇关于 Jazzy(java 拼写检查 API)的文章很好地概述了这些比较(这是一个类似的问题 - 提供建议的更正)http://www.ibm.com/developerworks/java/library/j-jazzy/

      【讨论】:

        【解决方案4】:

        新答案

        鉴于最近的更新,您可以尝试使用汉明距离作为启发式的 A*。这是一种可接受的启发式方法,因为它不会高估距离

        老答案

        您可以修改用于计算Levenshtein distance 的动态程序以获得操作序列。

        编辑:如果有恒定数量的字符串,则问题可以在多项式时间内解决。否则,它是 NP 难的(维基百科中都有).. 假设你的朋友正在谈论这个问题是 NP 难的。

        编辑:如果你的字符串长度相等,你可以使用Hamming distance

        【讨论】:

        • 举个例子应该是汉明距离。
        • 您不能修改 Levenshtein 函数来执行此操作,因为您的有效单词字典有限 - 因此最短的有效路径可能比字符串中的字符数长得多。
        • 对,词->词的概念在最近的编辑中才清晰。
        • shameless plug 实际上我在一两个月前问过一个关于这个问题的问题,除了在 Python 中,我最终使用带有汉明距离的 A* 作为启发式(虽然我当时不知道它叫汉明距离)。我得到了一些非常好的优化(一些特定于 python,一些不是)。问题在这里stackoverflow.com/questions/788084/… 并且在问题更新中我链接到我制作的一篇博客文章(大约一半......问题有很多更新),其中我描述了优化以及代码。跨度>
        • 我这样做了,效果非常好。也让我的教授感到惊讶!
        【解决方案5】:

        使用字典,BFS 是最佳的,但所需的运行时间与其大小成正比 (V+E)。对于 n 个字母,字典可能有 ~a^n 个整数,其中 a 是字母大小。如果字典包含所有单词,但应该在链末尾的单词除外,那么您将遍历所有可能的单词但不会找到任何东西。这是图遍历,但大小可能呈指数级增长。

        您可能想知道是否有可能做得更快 - 以“智能”方式浏览结构并在多项式时间内完成。答案是,我认为,不会。

        问题:

        你有一个快速(线性)的方法来检查一个单词是否在字典中,两个单词 u, v 并检查是否有一个序列 u -> a1 -> a 2 -> ... -> an -> v.

        是 NP 难的。

        证明:拿一些 3SAT 实例,比如

        (p or q or not r) and (p or not q or r)

        您将从 0 000 00 开始,并检查是否可以转到 2 222 22。

        第一个字符将是“我们完成了吗”,接下来的三个位将控制 p、q、r,而接下来的两个位将控制子句。

        允许的词是:

        • 以 0 开头且仅包含 0 和 1 的任何内容
        • 任何以 2 开头且合法的内容。这意味着它由 0 和 1 组成(除了第一个字符是 2 之外,所有子句位都根据变量位正确设置,并且设置为 1(因此这表明公式是可满足的)。
        • 以至少两个 2 开头然后由 0 和 1 组成的任何内容(正则表达式:222* (0+1)*,如 22221101 但不是 2212001

        要从 0 000 00 产生 2 222 22,你必须这样做:

        (1) 翻转适当的位 - 例如0 100 111 分四步。这需要找到 3SAT 解决方案。

        (2) 将第一位更改为 2:2 100 111。在这里您将被验证这确实是一个 3SAT 解决方案。

        (3) 变化 2 100 111 -> 2 200 111 -> 2 220 111 -> 2 222 111 -> 2 222 211 -> 2 222 221 -> 2 222 222。

        这些规则强制您不能作弊(检查)。只有当公式是可满足的并且检查是 NP-hard 时,才可能转到 2 222 22。我觉得它可能更难(可能是#P 或 FNP),但我认为 NP 硬度足以达到这个目的。

        编辑:您可能对disjoint set data structure 感兴趣。这将获取您的字典和可以相互访问的组词。您还可以存储从每个顶点到根或其他顶点的路径。这会给你一条路径,不一定是最短的。

        【讨论】:

        • 很棒的总结。如果原作者正在寻找真正有创意的东西,则可以将编辑距离与 word-reach 图结合使用,作为遗传算法的适应度函数。输出是从一个起始词到结束词的路径,所以最好的答案是最短的。 (虽然很酷,但这会最快地找到答案,但不会产生明确的答案。非常 TS。)我会坚持现实世界。消除循环,枚举路径并使用上述建议找到“最佳”路径。这被标记为“Java”,所以试试 JGraphT。
        • 酷,很少有人在 Stackoverflow 答案中看到 NP 硬度证明。 :-) 我也怀疑这个问题比NP(PSPACE-complete?)更难,如果字典只是作为一个成员oracle给出......但如果字典实际上是在输入中给出的,那么这个问题可以很简单地完成多项式时间,因为字典大小是输入的一部分(这是您的 NP 硬度证明中的缺陷)。
        【解决方案6】:

        对于查找链接有不同效率的方法 - 您可以为每个字长构建一个完整的图表,或者您可以构建一个 BK-Tree,例如,但你的朋友是对的 - BFS是最有效的算法。

        但是,有一种方法可以显着改善您的运行时间:不要从源节点执行单个 BFS,而是执行两个广度优先搜索,从图表的任一端开始,并在您找到一个公共节点时终止他们的边界集。如果只从一端搜索,您需要做的工作量大约是所需工作量的一半。

        【讨论】:

        • 请注意,我相信此方法仅适用于未加权图。在加权图上(其中某些边“成本更高”或比其他边长),以相同方式使用双向搜索并不能保证找到的最短路径。查看this linkthis topic。但是在这种情况下,一个字母不同的单词之间的步骤都是一样的
        【解决方案7】:

        我的直觉是您的朋友是正确的,因为没有更有效的解决方案,但这是假设您每次都在重新加载字典。如果您要保留一个正在运行的常见转换数据库,那么肯定会有一种更有效的方法来找到解决方案,但是您需要事先生成转换,并发现哪些转换有用(因为您无法生成他们全部!)可能是它自己的艺术。

        【讨论】:

          【解决方案8】:

          这是一个典型的dynamic programming 问题。检查编辑距离问题。

          【讨论】:

          • 不,不是。仔细阅读问题。有一个固定的给定字典,因此编辑距离几乎没有相关性。
          【解决方案9】:

          您可以找到最长的公共子序列,从而找到必须更改的字母。

          【讨论】:

            猜你喜欢
            • 2011-07-22
            • 2020-09-01
            • 2012-07-15
            • 1970-01-01
            • 1970-01-01
            • 2021-02-15
            • 1970-01-01
            • 1970-01-01
            • 2012-04-04
            相关资源
            最近更新 更多