【问题标题】:DFS and BFS on a Trie in JavaJava 中 Trie 上的 DFS 和 BFS
【发布时间】:2019-08-17 14:40:46
【问题描述】:

我有一个看起来像这样的 Trie:

      Root
     /    \
    b      c
   /      / \
  a      a   h
 /      /   / \
t      t   a   e
          /   /
         t   e
            / \
           r   s
          /     \
         s       e

我正在尝试实现 DFS 和 BFS。 BFS 工作正常,使用队列:

public String breadthFirstSearch() {
    //FIFO Queue to hold nodes
    Queue<TrieNode> nodeQueue = new LinkedList<TrieNode>();

    //Output array
    ArrayList<Integer> out = new ArrayList<Integer>();

    //Start from root
    nodeQueue.add(this.root);

    //While queue is not empty
    while (nodeQueue.isEmpty() == false) {
        //Remove and return first queue element
        TrieNode current = nodeQueue.poll();
        //For node's children
        for (int i=0; i<26; i++) {
            //If not null
            if (current.offspring[i] != null) {
                //Add node to queue
                nodeQueue.add(current.offspring[i]);
                //Add node's index (char) to output array
                out.add(i);                    
            }
        }
    }
    //Return result
    return indexArrayToString(out);
}

输出:

b,c,a,a,h,t,t,a,e,t,e,r,s,s,e

现在,我正在尝试实现 DFS(相同的算法,但使用堆栈)但是输出不正确:

public String depthFirstSearch() {
    //LIFO Stack to hold nodes
    Stack<TrieNode> nodeStack = new Stack<TrieNode>();

    //Output array
    ArrayList<Integer> out = new ArrayList<Integer>();

    //Start from root
    nodeStack.push(this.root);

    //While stack is not empty
    while (nodeStack.isEmpty() == false) {
        //Remove and return first stack element
        TrieNode current = nodeStack.pop();
        //For node's children
        for (int i=0; i<26; i++) {
            //If not null
            if (current.offspring[i] != null) {
                //Add node to stack
                nodeStack.push(current.offspring[i]);
                //Add node's index (char) to output array
                out.add(i);                    
            }
        }
    }
    //Return result
    return indexArrayToString(out);
}

这给出了:

b,c,a,h,a,e,e,r,s,e,s,t,t,a,t

当我希望它给予时:

t,a,b,t,a,t,a,s,r,e,s,e,e,h,c

我不知道出了什么问题。

【问题讨论】:

  • 专业提示:使用递归实现 DFS 通常比使用显式堆栈更简单。
  • @Charlie 你要实现后序DFS遍历吗?
  • 与其打电话给indexArrayToString(out),不如直接打电话给out.toString()
  • 为了将堆栈用于后序 DFS,您应该从堆栈中弹出节点并在稍后的某个时间将其 char 放入“out”数组中(即不是在您发现它们之后)。您的 trie 节点似乎没有 char 字段。如果 BFS 不是问题,因为您在发现时输出了字符,因此您可以使用整数“i”来表示这些字符。在后序 DFS 中,除非您知道节点实际包含哪个字符,否则您不能这样做。
  • 可以维护一个 Map 或 Map 并在我们发现一个新的 trienode 时填充它,但实际上在 TrieNode 类中没有一个 char 字段看起来很尴尬

标签: java tree trie


【解决方案1】:

我已经实现了我在评论中提到的基于地图的方法,即不修改原来的 TrieNode 类:

  public String depthFirstSearch() {
            //LIFO Stack to hold nodes
            Stack<TrieNode> nodeStack = new Stack<TrieNode>();
            //keep set of processed nodes (processed node is a node whose children were already pushed into the stack)
            Set<TrieNode> processed = new HashSet<TrieNode>();
            //boolean for checking presence of at least one child
            boolean hasChild=false;
            //map for trienode->char
            Map<TrieNode, Integer> map = new HashMap<TrieNode, Integer>();

            //Output array
            List<Integer> out = new ArrayList<Integer>();

            //Start from root
            nodeStack.push(this.root);

            //While stack is not empty
            while (nodeStack.isEmpty() == false) {

                //Peek at the top of stack
                TrieNode topNode = nodeStack.peek();

                //if it is not processed AND if it has at least one child, push its children into the stack from right to left. otherwise pop the stack

                hasChild=false;                
                if(!processed.contains(topNode))
                {

                   for (int i=25; i>=0; i--) 
                   {
                       //If not null
                       if (topNode.offspring[i] != null) 
                       {
                         //Add node to stack and map
                         nodeStack.push(topNode.offspring[i]);
                         map.put(topNode.offspring[i], i);
                         hasChild=true;                                               
                       }     

                   }//end for

                   processed.add(topNode); //after discovering all children, put the top into set of processed nodes

                   if(!hasChild) //top node has no children so we pop it and put into the list
                       {
                          TrieNode popNode = nodeStack.pop();
                          if(map.get(popNode)!=null)
                             out.add(map.get(popNode)); 

                       }

                }
                else //the node has been processed already so we pop it and put into the list

                {               
                    TrieNode popNode = nodeStack.pop();
                    if(map.get(popNode)!=null)
                       out.add(map.get(popNode)); 
                }


            }//end while stack not empty

            //Return result
            return indexArrayToString(out);

        }//end method

【讨论】:

    【解决方案2】:

    要获得所需的输出,您需要考虑何时将节点添加到 out 列表中。在您的代码中,您从根开始并以一种递归样式迭代它的后代,同时将它们直接添加到您的输出中。因此,您的输出与 BFS 的共同点多于 DFS。

    虽然有非常简单的 DFS 实现,比如

    DFS(TrieNode current){ 
      for(int i = 26; i >= 0; i--){
        if(current.offspring[i] != null){
          DFS(current.offspring[i]);
        }
      }
      out.add(current);
    }
    

    如果您出于任何原因想要保留大部分代码,您可以创建第二个堆栈来跟踪您在树中的位置以及何时应该将哪个节点添加到输出中。 明确地说,这可能看起来像这样:

    public String depthFirstSearch() {
      //LIFO Stack to hold nodes
      Stack<TrieNode> nodeStack = new Stack<TrieNode>();
    
      //Second stack that keeps track of visited nodes
      Stack<TrieNode> helpStack = new Stack<TrieNode>();
    
      //Output array
      ArrayList<Integer> out = new ArrayList<Integer>();
    
      //Start from root
      nodeStack.push(this.root);
    
      //While stack is not empty
      while (nodeStack.isEmpty() == false) {
        //Remove and return first stack element
        TrieNode current = nodeStack.peek();
    
        //We visited this node -> push it on the second stack
        helpStack.push(current);
    
        //We want to add nodes to the output once we reach a leaf node, so we need a
        //helper variable
        boolean hasOffspring = false;
    
        //For node's children - since we want to go the left path first we push the 
        //children in right-to-left fashion on the stack. Can vary with implementation.
        for (int i=25; i>=0; i--) {
            //If not null
            if (current.offspring[i] != null) {
                //Add node to stack
                nodeStack.push(current.offspring[i]);
                //not a leaf
                hasOffspring = true;                  
            }
         }
    
         //if we reached a leaf node add it and all previous nodes to the output until 
         //we reach a fork where we didn't already fo the other path
         if(!hasOffspring){
           TrieNode node1 = nodeStack.peek();
           TrieNode node2 = helpStack.peek();
           while(node1.equals(node2)){
             nodeStack.pop();
             helpStack.pop();
    
             //Add node's index (char) to output array
             out.add(node1);  
    
             //we are back at the root and completed the DFS
             if(nodeStack.isEmpty() || helpStack.isEmpty()) break;
             node1 = nodeStack.peek();
             node2 = nodeStack.peek();
           }
         }
     }
      //Return result
      return indexArrayToString(out);
    }
    

    【讨论】:

    • 不需要第二个堆栈
    • out.add(i); 那么你从哪里得到i?你从来没有存储过它。这就是我在 cmets 中描述的问题。由于该行,代码将无法编译
    • 抱歉打错了。应该是 out.add(node1)
    • 仍然毫无意义。该列表应包含整数或字符。如何将 TrieNode 列表转换为整数列表?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 2016-08-18
    • 1970-01-01
    相关资源
    最近更新 更多