Algorithm 如何有效地实现三元搜索的floor()和ceil()操作?

Algorithm 如何有效地实现三元搜索的floor()和ceil()操作?,algorithm,data-structures,tree,time-complexity,binary-search-tree,Algorithm,Data Structures,Tree,Time Complexity,Binary Search Tree,我一直在研究Robert Sedgewick书中的TST(三元搜索尝试),以下是他的实现链接: 因此,由于TST是BST的一种修改,我想知道如何有效地实现地板和天花板操作。(他们没有在他的代码中的任何地方实现)。我想到的所有方法都很混乱,效率也不高。是的,您可以在TST上高效地实现这些操作 把TST想象成一个普通的trie可能会有帮助。我们可以解决如何在trie中执行前置搜索和后续搜索(您称之为下限和上限),然后将这些方法应用于TST。为简单起见,我将只讨论后续搜索,尽管这也可以很容易地适用于前

我一直在研究Robert Sedgewick书中的TST(三元搜索尝试),以下是他的实现链接:


因此,由于TST是BST的一种修改,我想知道如何有效地实现地板和天花板操作。(他们没有在他的代码中的任何地方实现)。我想到的所有方法都很混乱,效率也不高。

是的,您可以在TST上高效地实现这些操作

把TST想象成一个普通的trie可能会有帮助。我们可以解决如何在trie中执行前置搜索和后续搜索(您称之为下限和上限),然后将这些方法应用于TST。为简单起见,我将只讨论后续搜索,尽管这也可以很容易地适用于前置搜索

假设你想从词典中找到不晚于某个单词w的第一个单词。首先在trie中搜索单词w。如果你发现w是trie中的一个词,你就完了

否则,可能会发生一些事情。首先,您可能会发现您在trie中的某个节点处结束,该节点对应于不是单词的w。在这种情况下,您知道w是trie中某个单词的前缀,因此要找到后续词,您需要按照字典顺序查找以w为前缀的第一个字符串。要做到这一点,请继续沿着trie走下去,始终尽可能向左走,直到最终找到与单词对应的节点

第二,在尝试搜索w时,您可能会从trie中掉下来。在这种情况下,您将沿着路径读取一些前缀w。在这种情况下,您必须在某个节点结束,在该节点上您试图读取字符c,但没有标记为c的边。在这种情况下,请查看此节点的其他边,并找到第一条边,其字符位于c之后。如果有一个词存在,就把它取下来,然后通过尽可能向左走的方式,按照字典顺序找到该子类别中的第一个词。如果没有,请备份trie中的一个节点并重复此过程

总之,递归算法如下所示:

function findSuccessor(root, remainingChars) {
    /* If we walked off the trie, we need to back up. Return null
     * to signal an error.
     */
    if (root == null) return null;

    /* If we're on the trie and out of characters, we're either done
     * or we need to find the cheapest way to extend this path.
     */
    if (remainingChars == "") {
        if (root is a word) {
            return root;
        } else {
            return goLeftUntilYouFindAWord(root);
        }
    }

    /* Otherwise, keep walking down the trie. */
    let nextLetter = remainingChars[0];

    /* If there is a child for this letter, follow it and see
     * what happens.
     */
    if (root.hasChildFor(nextLetter)) {
        let result = findSuccessor(root.child(nextLetter), nextLetter.substring(1));

        /* If we found something, great! We're done. */
        if (result != null) return result;
    }

    /* If we're here, we either (a) have no transition on this
     * character or (b) we do, but the successor isn't there. In
     * either case, figure out which child we have that comes right
     * after nextLetter and go down there if possible.
     */
    char letterAfter = node.firstChildAfter(nextLetter);

    /* If no such child exists, there is no successor in this
     * subtrie. Report failure.
     */
    if (letterAfter == null) return null;

    /* Otherwise, get the first word in that subtrie. */
    return goLeftUntilYouFindAWord(node.child(letterAfter));
}
那么这到底是如何转化为TST的呢?嗯,我们需要能够检查是否存在子级-这是我们可以通过常规的BST查找来完成的-我们还需要能够找到特定级别的字符后面的第一个字符-我们可以通过BST中的后续搜索来完成。我们还需要能够找到子树中的第一个单词,我们可以通过在子指针的BST中始终向左走来实现这一点

总的来说,这里的运行时是O(L log |∑|),其中L是trie中最长字符串的长度,∑是允许的字符集。这样做的原因是,在最坏的情况下,我们必须沿着TST向下一路寻找后继节点,每次这样做时,我们都会执行固定数量的BST操作,每个操作都需要时间O(log∑∑|),因为每个节点最多有∑|子指针


如果您希望看到此操作的具体实现,我有一个实现
下限
上限
,它们与您描述的操作密切相关

@DougCurrie实际上我首先写的是,我会找到输入字符串中最长的前缀,然后找到所有具有该前缀的键,然后对下限和上限进行二进制搜索,但后来我意识到这是错误的。我想到的其他方法包括存储一组字符串,并且输入字符串的字符数不是线性的。