词语阶梯,Javascript:我正在使用什么算法和数据结构?

3
我编写了以下算法来完成单词阶梯。您可以在我的github上找到完整的程序,包括jQuery可视化和json字典:https://github.com/NateBrady23/algorithms/tree/master/word_ladder 所以除了我用一些节点的每个级别来填充一些数组之外,我对我使用的算法名称和正在构建的数据结构名称几乎没有什么了解。
我从给定的输入单词和结束单词开始。我消除了字典中我不能使用的所有单词(输入单词和长度与输入单词不同的单词)。我构建了一个Node对象,它保存了跳转级别、值和prev,这样我如果找到路径,就可以从我的终点向后遍历。
从0级开始,我找到可以通过转换一个字母来创建的所有单词,并将其保存在1级wordNodes桶中。每个节点对象的prev属性指向它所在的0级节点。然后我从字典中删除所有这些单词。接下来,我遍历所有的1级wordNodes,对于每个节点,从修改过的字典中找到转换后的单词并存储在2级中,并指向创建它的单词,以此类推,直到找到目标单词或无法创建新级别(无法到达我的单词)。
鉴于上述可能表述不佳的摘要,我构建了这种类型的结构(如二叉搜索树)和我使用的算法是否有名称?此外,是否有一种方法可以显著改进下面的代码而不使用完全不同的结构/算法。
<style>
    span {
        margin: 10px;
    }

    .highlight {
        background-color: yellow;
        font-weight: bold;
    }
    span {
        display: inline-block;
    }
</style>
<div id="path">

</div>

<script src='js/jquery.min.js'></script>
<script src='js/dictionary.js'></script>
<script>
// var dict = JSON.parse('{"aa" : true, "aah" : true, "aahed" : true}');

    // Build our dictionary array
    // Create a Node object so we can traverse back up the tree
    function Node(value, prev, level) {
        this.prev = prev;
        this.level = level;
        this.value = value;
    }

    function getRegex(word) {
        var temp = [];

        for(var i = 0; i < word.length; i++){
            temp.push( word.substring(0,i) + '.' + word.substring(i+1) );
        }  
        var re_str = '^(' + temp.join('|') + ')$';
        return new RegExp(re_str, 'i');
    }

    function oneOffs(wordNode) {
        var list = [];
        var regex = getRegex(wordNode.value);

        if ($('#' + (wordNode.level+1)).length == 0) {
            $('#path').append('<div id="' + (wordNode.level+1) + '"></div>');
            $('#' + (wordNode.level+1)).append('<h1>Level: ' + (wordNode.level+1));
        }
        for (var word in dict) {
                // If we found a match
                if (dict.hasOwnProperty(word) && regex.test(word)) {
                    list.push(new Node(word, wordNode, wordNode.level + 1));
                    $('#' + (wordNode.level+1)).append('<span id="' + word + '">' + word +'</span>');
                    delete dict[word];
                }
        }
        return list;
    }

    // Get our start and end words
    inputWord = 'boggle';
    endWord = 'shaggy';

    $('#path').append('<div id="start"><h1>Transform Case</h1><span><b>Starting Word:</b> ' + inputWord + '</span><span><b>Ending Word:</b> ' + endWord + '</span></div>');
    $('#path').append('<div id="0"><h1>Level: 0</h1><span id="' + inputWord + '">' + inputWord + '</span></div>');

    // Have to remove our inputWord from the dictionary
    delete dict[inputWord];

    // Remove all words in dictionary not of proper length
    for (var word in dict) {
        if (dict.hasOwnProperty(word)) {
            if (word.length != inputWord.length) {
                delete dict[word];
            }
        }
    }


    // Create input and end nodes
    inputNode = new Node(inputWord, null, 0);
    endNode = new Node(endWord, null, 0);

    // Create our initial list of level 1 nodes
    list = oneOffs(inputNode);

    for (var node in list) {
        if (list[node].value === endNode.value) {
            endNode.level = list[node].level;
            endNode.prev = list[node].prev;
        }
    }

    // Build our tree
    while (list.length && !endNode.level) {
        newList = [];
        for (var i = 0; i < list.length; i++) {
            newNodeList = oneOffs(list[i]);
            for (var node in newNodeList) {
                if (newNodeList[node].value === endNode.value) {
                    endNode.level = newNodeList[node].level;
                    endNode.prev = newNodeList[node].prev;
                    break;
                }
            }
            if (newNodeList.length) {
                newList = newList.concat(newNodeList);
            }
        }
        list = newList;
    }

    // if we found the path, let's traverse back up and print out all the values
    if (endNode.level) {
        curr = endNode;
        $('#' + curr.value).addClass('highlight');
        console.log(curr.value);
        while(curr.prev) {
            $('#' + curr.prev.value).addClass('highlight');
            console.log(curr.prev.value);
            curr = curr.prev;
        }
    } else {
        console.log('No possible path');
    }


</script>

输入单词、输出单词和路径的示例是什么?看起来你正在尝试做类似于这个的事情(或相等?):https://dev59.com/FHI_5IYBdhLWcg3wHfOr - juvian
1
好的,你正在使用类似BFS算法的方法,从初始节点(即输入单词)开始,添加所有相邻节点(在你的情况下是只有一个字母不同的单词),然后添加所有一级单词的相邻节点,并继续执行此操作,直到找到该单词或没有更多节点需要处理。 - juvian
可能有其他算法或结构更好的方法,但只要您有足够的内存,这个算法就相当高效。不过,在如何转换/检测单词方面,您可能会有一些实现优化。 - juvian
1
如果有很多搜索需求的话,进行双向 BFS 可能会非常有帮助。 - David Eisenstat
2
@Nate可能有一些错误,但这是我使用bfs实现的代码,从起始单词和结束单词两端开始搜索。这样当你从两边到达一个单词时,就是最短的路径。这是我的代码:http://jsfiddle.net/juvian/y357xudt/3/,这里有更好的解释:http://yucoding.blogspot.com/2013/08/leetcode-question-127-word-ladder.html - juvian
显示剩余3条评论
1个回答

3
评论中指出这是一种广度优先搜索算法,但我不知道具体的名称。当然,你的数据结构是某种树形结构。
至于优化方面,我没有仔细查看代码,但有一件事情引起了我的注意。为了从一个步骤到另一个步骤,你会构造一个复杂的正则表达式来匹配特定级别的每个单词,然后循环遍历整个字典,看是否有任何单词与该正则表达式匹配。建立实际可能的单词并检查每个单词是否在字典中肯定更有效率。也就是说,如果你有“foo”,则检查你的字典中是否有“aoo”、“boo”、“coo”……“zoo”、“fao”、“fbo”、“fco”……“fzo”、“foa”、“fob”、“foc”……“foz”。由于你已经从字典中删除了访问过的单词,所以在这个列表中,你不会再次得到“foo”的多个新匹配。
这应该显著减少你需要进行的检查次数,并将其从正则表达式测试变为简单的属性查找。我猜这会大幅提高性能。

为每个可能的 foobar 组合创建一个新列表,然后将其与字典进行比较会更快吗?那不是要创建 3090 万个查找来创建第一层吗? - Nate
1
不是 26 ^ 6,因为我们只更改一个字母。对于 foobar,是 26 * 6。这样做需要进行 156 次查找,而不是 (dictionary.length) 次正则表达式测试。 - Scott Sauyet
这快了几个数量级。太棒了! - Nate
@Nate,你是在寻找最短路径还是任意路径? - juvian
@juvian 最短路径 - Nate
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接