对于Data Structures项目,我必须找到两个单词之间的最短路径(如"cat"
和"dog"
),一次只能更改一个字母。我们给出了一个拼字游戏单词列表,用于查找我们的路径。例如:
cat -> bat -> bet -> bot -> bog -> dog
我使用广度优先搜索解决了这个问题,但我正在寻找更好的东西(我用trie代表字典)。
请给我一些更有效的方法(在速度和记忆方面)的想法。有些荒谬和/或挑战是首选。
我问过我的一个朋友(他是一名大三学生),他说有没有有效解决这个问题的方法。他说我会学习为什么我参加算法课程。对此有何评论?
我们必须一个接一个地移动。我们不能去cat -> dat -> dag -> dog
。我们还必须打印出遍历。
答案 0 :(得分:15)
新答案
鉴于最近的更新,您可以尝试使用汉明距离作为启发式的A *。这是一个可接受的启发式算法,因为它不会高估距离
OLD ANSWER
您可以修改用于计算Levenshtein distance的动态程序,以获取操作序列。
编辑:如果字符串数量恒定,则问题在多项式时间内是可解的。否则,它是NP难的(维基百科都有)...假设你的朋友正在谈论NP难的问题。
编辑:如果您的字符串长度相等,则可以使用Hamming distance。
答案 1 :(得分:9)
使用字典,BFS是最佳的,但所需的运行时间与其大小(V + E)成正比。对于n个字母,字典可能有〜a ^ n entires,其中a是字母大小。如果字典包含所有单词,但是应该在链的末尾,那么你将遍历所有可能的单词,但不会找到任何单词。这是图遍历,但大小可能呈指数级大。
您可能想知道是否可以更快地进行操作 - “智能地”浏览结构并在多项式时间内完成。答案是,我认为,没有。
问题:
你有一种快速(线性)的方法来检查一个单词是否在字典中,两个单词u,v并且要检查是否有序列u - > a 1 - > a 2 - > ... - > a n - >诉
是NP-hard。
证明:采取一些3SAT实例,如
(p或q或不r)和(p或不q或r)
你将从0 000 00开始,并检查是否可以转到2 222 22.
第一个字符将是“我们完成了”,接下来的三个位将控制p,q,r,其次两个将控制子句。
允许的词语是:
要从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难时,才可能进入2 222 22。我觉得它可能更难(可能是#P或FNP),但我认为NP硬度就足够了。
修改:您可能对disjoint set data structure感兴趣。这将使您可以从彼此访问的词典和组词。您还可以存储从每个顶点到根或某个其他顶点的路径。这将为您提供一条路径,而不是最短路径。
答案 2 :(得分:3)
有查找链接的效率方法 - 您可以为每个字长构建一个完整的图表,或者您可以构建一个BK-Tree,例如,但您的朋友是对的 - BFS是最有效的算法。
然而,有一种方法可以显着改善您的运行时:不是从源节点执行单个BFS,而是从图的任一端开始执行两次广度优先搜索,并在找到公共节点时终止他们的边疆集。您需要做的工作量大约是从一端搜索时所需的工作量的一半。
答案 3 :(得分:2)
首先,删除不正确长度的单词,可以让它更快一些。更多有限字典将适合CPU的缓存。可能全部。
此外,所有strncmp比较(假设您将所有内容都设为小写)可以是memcmp比较,甚至是展开的比较,这可以是加速。
您可以使用一些预处理器魔术并对该字长进行硬编译任务,或者为常用字长滚动一些优化的任务变体。所有这些额外的比较都可以“消失”,以获得纯粹的展开乐趣。
答案 4 :(得分:1)
这是典型的dynamic programming问题。检查编辑距离问题。
答案 5 :(得分:1)
您要查找的内容称为编辑距离。有许多不同的类型。
From(http://en.wikipedia.org/wiki/Edit_distance):“在信息理论和计算机科学中,两个字符串之间的编辑距离是将其中一个字符转换为另一个字符所需的操作数。”
这篇关于Jazzy(java拼写检查API)的文章很好地概述了这些类型的比较(这是一个类似的问题 - 提供建议的更正)http://www.ibm.com/developerworks/java/library/j-jazzy/
答案 6 :(得分:0)
您可以找到最长的公共子序列,因此找到必须更改的字母。
答案 7 :(得分:0)
我的直觉是,你的朋友是正确的,因为没有更有效的解决方案,但这是假设你每次都在重新加载字典。如果你要保持一个正常转换的运行数据库,那么肯定会有一种更有效的方法来寻找解决方案,但你需要事先生成转换,并发现哪些转换是有用的(因为你不能生成他们都是!)可能是它自己的艺术。
答案 8 :(得分:0)
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;
};
//返回到达目标的最短链的长度&#39;来自&#39;开始&#39; //使用最少数量的相邻移动。 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/