【问题标题】:How can I avoid a Stack Overflow using this Evaluation (BFS)如何使用此评估 (BFS) 避免堆栈溢出
【发布时间】:2025-12-23 20:55:11
【问题描述】:

我有一个已构建的 NFA,我正在运行此方法来评估机器以查看表达式是否有效。这适用于小的正则表达式,但是当我的正则表达式的大小以及 NFA 的大小变得太大时,这个搜索会向我抛出堆栈溢出。我相当肯定这是因为我已经实现了 BFS,正在使用递归,并且可能没有很好地处理我的基本情况。

此方法接受一个表达式和一个节点(从 NFA 的起始节点开始)。首先它检查表达式的长度是否为零,如果我在一个接受节点中(节点上的布尔值),那么我返回 true。 如果表达式长度为零,但当前节点不是接受节点,则返回 false。

如果这些都不评估,那么我会得到当前节点可以使用“e”(epsilon)转换到达的所有节点的列表,并评估它们。

如果没有“e”节点,那么我从输入表达式中删除第一个字符,制作表达式的缩短子字符串(删除表达式的前面),然后查找该节点可以的节点列表使用删除的字符和简化的表达式到达。

如果这些都没有命中,那么我返回 false

一个基本的正则表达式是 (a|b)*a 一个评估表达式的例子是 aaaa 每次通过时都会减少,aaaa->aaa->aa->a->

    private boolean evaluate(autoNode node, String expression)
{

    if(expression.length()==0 && node.getAccept())
    {
        return true;
    }
    else if(expression.length()==0 && !node.getAccept())
    {
        return false;
    }

    String evalExp = expression.charAt(0)+""; //The first character in the expression
    String redExp = expression.substring(1, expression.length()); 

    //for each epsilon transition, evaluate it
    if(node.getTransSet().contains("e"))
    {
        //if this node has an "e" transition then...
        ArrayList<autoNode> EpsilonTransMap = node.getPathMap("e");
        //The above ArrayList is a list of all the nodes that this node can reach
        //using the "e" / epsilon transition
        for(autoNode nodes : EpsilonTransMap)
        {               
            if(evaluate(nodes, expression))
            {
                return true;
            }
        }
    }
    //for each transition on that key evaluate it
    if(node.getTransSet().contains(evalExp))
    {
        //if this node has a transition from the front of the expression then...
        ArrayList<autoNode> TransitionKeyMap = node.getPathMap(evalExp);
        //The above ArrayList is a list of all the nodes that this node can reach
        //on a transition equal to the "key" removed from the front of the expression String
        for(autoNode nodes : TransitionKeyMap)
        {
            if(evaluate(nodes, redExp))
            {
                return true;
            }
        }
    }

    return false;
}

我知道我可能使用 bfs 搜索而不是 dfs 导致了我自己的问题。我想知道是否有人可以帮助我解决这个问题并通过一次发生太多事情来避免堆栈溢出。因为 while (a|b)*a 可以评估得很好......

((aa)+|(bb)+|(cc)+)(ba)(ca)

创建一个相当大的 NFA,这会在评估时导致堆栈溢出: “一个”

任何不会导致我完全放弃该方法的事情都将非常好并且不胜感激。

【问题讨论】:

  • 也许,您可以尝试将 NFA 转换为 DFA ,以减少回溯。

标签: java regex


【解决方案1】:

嗯,你实际上并没有 DFS BFS,但这并不重要。我想你不能使用带有字母“e”的正则表达式也不重要。

重要的是,每当您到达一个 epsilon 转换周期时,您都会遇到堆栈溢出。例如:

evaluate(n1,"aa") 找到从 n1 到 n2 的 epsilon 转换,并递归:

evaluate(n2,"aa") 找到从 n2 到 n1 的 epsilon 转换并递归:

evaluate(n1,"aa") .. 以此类推,递归直到堆栈溢出。

有很多方法可以解决这个问题......但即使你解决了它,这仍然是评估 NFA 的一个非常糟糕的算法——它可能需要在状态数量上呈指数级增长!

编辑——所以,这是用伪代码进行 NFA 评估的正确方法:

boolean evaluate(Node nfa, String str)
{
    Set<Node> fromStates = new Set();
    fromStates.add(nfa);
    closeEpsilons(fromStates);

    for (char chr in str)
    {
        if (fromStates.size()==0)
            return false;

        //find all the states we can get to from
        //fromStates via chr

        Set<Node> toStates = new Set();
        for (Node fromState in fromStates)
        {
            //OP's code would say .getPathMap(chr) here
            for(Node toState in fromState.getTransitionTargets(chr))
            {
                if (!toStates.contains(toState))
                    toStates.add(toState);
            }
        }
        closeEpsilons(toStates);

        //process the rest of the string with the state set we just found
        fromStates = toStates;
    }

    //string is done.  see if anything accepts
    for(Node state in fromStates)
    {
        if (state.accepts())
        {
            return true;
        }
    }
    return false;
}

//expand a state set with all states is reaches via epsilons
void closeEpsilons(Set<Node> states)
{
    Queue<Node> processQueue = new Queue();
    processQueue.addAll(states);

    while(!processQueue.isEmpty())
    {
        Node fromState = processQueue.removeFirst();

        //OP's code would say "getPathMap("e") here
        for(Node toState in fromState.getEpsilonTargets())
        {
            if (!states.contains(toState))
            {
                //found a new state
                states.add(toState);
                //we'll have to search it for epsilons
                processQueue.add(toState);
            }
        }
    }
}

【讨论】:

  • 我能够得到这个工作,并且在花了这么多时间编写一个 bum 方法之后,很高兴看到一个更清洁的方法。我显然还有很多东西要学,这让我很紧张,是的。这解决了堆栈溢出问题,这有帮助,我仍然有一些评估问题,但我认为这是来自我的 NFA 构造,所以我需要仔细看看,但这非常有帮助,尤其是在我把头撞到墙上之后。再次感谢!
  • 我有一个开源项目,做 NFA 构建和 DFA 构建,如果你想看一些真实的代码:mtimmerm.github.io/dfalex