c – CYK算法如何工作?

我必须检查一个字符串是否可以从Chomsky正常形式的给定上下文中派生出来.我正在使用C.

关于CYK算法的维基百科文章中有非常好的pseudocode,但我无法理解它.

有人可以通过给我一个CYK算法的伪代码来帮助我,或者解释一下wiki文章中的那个吗?

最佳答案 CYK算法将以乔姆斯基正规形式的CFG作为输入.这意味着每个产品都有形式

> S→a,对于某些终端a,或
> S→AB,对于一些非终结符A和B.

现在,假设您有一个字符串w,并且您想要查看是否可以从起始符号为S的语法派生它.有两个选项:

>如果w是单个字符长,那么解析它的唯一方法是使用S→a形式生成某个字符a.那么看看是否有任何单字符制作符合a.
>如果w长度超过一个字符,那么解析它的唯一方法是对一些非终结符A和B使用S→AB形式的生成.这意味着我们需要将字符串w分成两个部分x和y其中A派生x,B派生y.一种方法是尝试所有可能的方法将w分成两部分,看看它们是否有效.

请注意,这里的选项(2)最终是一个递归解析问题:要查看是否可以从S派生w,看看是否可以从A中导出x和从B中导出y.

有了这个洞察力,这里是递归函数的伪代码,你可以用它来看看非终结符S是否派生了一个字符串w:

bool canDerive(nonterminal S, string w) {
    return canDeriveRec(S, w, 0, w.size());
}

/* Can you derive the substring [start, end) of w from S? */
bool canDeriveRec(nonterminal S, string w, int start, int end) {
    /* Base case: Single characters */
    if (end - start == 1) {
        return whether there is a production S -> a, where a = w[start];
    }

    /* Recursive case: Try all possible splits */
    for (each production S -> AB) {
        for (int mid = start + 1; mid < end; mid++) {
            if (canDeriveRec(A, w, start, mid) &&
                canDeriveRec(B, w, mid, end)) {
                return true;
            }
        }
    }
    return false;
}

此算法可以正常工作,但如果您绘制出递归的形状,您将找到它

>它会产生大量的冗余递归调用,但是
>没有那么多可能的递归调用.

实际上,不同可能调用的数量是O(n2 N),其中n是输入字符串的长度(对于开始和结束索引的每个可能组合),N是语法中的非终结符号.这些观察结果表明,该算法可以从记忆或动态编程中受益,具体取决于您认为哪种方法更好.

当您采用上述递归算法并记住结果时,或者将上述递归算法转换为动态编程问题时,您可以获得CYK算法.

有O(n2 N)个可能的递归调用.对于每次尝试的生产,它都做O(n)工作.如果对于非终结符号平均存在P个产生,则这意味着整个运行时间为O(n3 NP),对于固定语法而言为O(n3).

点赞