【问题标题】:Recursive Tree Mapping递归树映射
【发布时间】:2011-07-24 11:56:53
【问题描述】:

我最近一直在研究树的实现以及我们如何表示和理解树。我的重点是将数学表达式转换为二叉树,我设置了以线性形式表示树的问题,例如字符串或数组,同时仍保留有关树及其子树的重要信息。

因此,我为二进制表达式树开发了一种非常简单的编码方式。但是,我在递归庄园中有效实施它时遇到了一些问题,这似乎是该概念背后的一个失败方面。

如果节点作为左子节点存在,则编码很简单,如果它作为右子节点存在,则映射为 1,如果它作为右子节点存在,则为 0。这种简单的编码允许我对整个平衡树和不平衡树进行编码,如下所示:

           ##                      ##
          /  \                    /  \
         1    0         OR       1    0
        / \  / \                     / \
       11 10 01 00                  01  00 

等到深度为 N 的树

是否有人对如何创建一个递归函数有任何建议,该函数将创建表示此类映射的前缀字符串(例如## 1 11 10 0 01 00)。

我被告知这将是困难的/不可能的,因为在保留和连接父值的同时必须跟踪 1 和 0 之间的交替。

我想知道是否有人对如何使用 C# 做到这一点有任何见解或想法??

【问题讨论】:

  • 您的方案中似乎有很多冗余。特别是,每个节点都对从根到它的路径进行编码。使用这种方案,列出叶节点的编码就足以完全表示树。不知道这是否有帮助,只是一个观察。
  • 我需要保留这条路线,以便我可以有效地确定子表达式以及它们如何作为整体进行交互。

标签: c# parsing data-structures recursion binary-tree


【解决方案1】:

即使对于经验丰富的程序员来说,递归构建树也是一项艰巨的挑战。考虑到它最初是在 2011 年 3 月发布的,我意识到我在这个问题上有点迟到了。迟到总比没有好?

创建树的一个重要因素就是确保您的数据集格式正确。您只需要一种将父母与孩子联系起来的方法。明确定义关联后,您就可以开始编写解决方案了。我选择使用这样的简单格式:

ParentId ChildId
1        2
1        3
2        4
3        5

等等

一旦建立了这种关系,我就开发了一种递归方法来遍历数据集以构建树。

首先,我识别所有父节点并将它们存储在一个集合中,使用父 ID 和子 ID 的组合为每个节点提供一个唯一标识符:

 private void IdentifyParentNodes()
{
    SortedList<string, MyTreeNode> newParentNodes = new SortedList<string,MyTreeNode>();
    Dictionary<string, string> parents = new Dictionary<string, string>();
    foreach (MyTreeNode oParent in MyTreeDataSource.Values)
    {
        if (!parents.ContainsValue(oParent.ParentId))
        {
            parents.Add(oParent.ParentId + "." + oParent.ChildId, oParent.ParentId);

            newParentNodes.Add(oParent.ParentId + "." + oParent.ChildId, oParent);
        }
    }

    this._parentNodes = newParentNodes;
}

然后根调用方法会循环遍历父母并调用递归方法来构建树:

// Build the rest of the tree
foreach (MyTreeNode node in ParentNodes.Values)
{
    RecursivelyBuildTree(node);
}

递归方法:

private void RecursivelyBuildTree(MyTreeNode node)
{
    int nodePosition = 0;

    _renderedTree.Append(FormatNode(MyTreeNodeType.Parent, node, 0));
    _renderedTree.Append(NodeContainer("open", node.ParentId));

    foreach (MyTreeNode child in GetChildren(node.ParentId).Values)
    {
        nodePosition++;
        if (IsParent(child.ChildId))
        {
            RecursivelyBuildTree(child);
        }
        else
        {
            _renderedTree.Append(FormatNode(MyTreeNodeType.Leaf, child, nodePosition));
        }
    }
    _renderedTree.Append(NodeContainer("close", node.ParentId));
}

用于获取父级子级的方法:

private SortedList<string, MyTreeNode> GetChildren(string parentId)
{
    SortedList<string, MyTreeNode> childNodes = new SortedList<string, MyTreeNode>();
    foreach (MyTreeNode node in this.MyTreeDataSource.Values)
    {
        if (node.ParentId == parentId)
        {
            childNodes.Add(node.ParentId + node.ChildId, node);
        }
    }
    return childNodes;
}

没有那么复杂或优雅,但它完成了工作。这是在 2007 年的时间范围内编写的,所以它是旧代码,但它仍然有效。 :-) 希望这会有所帮助。

【讨论】:

    【解决方案2】:

    嗯,我不知道我是否完全明白了你的问题,但似乎你想要树的前序遍历。我不知道 c# 的语法,但我认为伪代码如下:

    preorder_traversal(node)
        if(node != NULL)
            print(node)
            preorder_traversal(left_sub_child)
            preorder_traversal(right_sub_child)
        else
            return
    

    【讨论】:

      【解决方案3】:

      我不确定我是否理解您的问题,但这里有一些可能会有所帮助。一种解决方案可能是在 Graph 上实现图遍历例程(记住 Tree 是专门的 Graph),访问发生在您第一次遇到节点/顶点时。我很抱歉在您要求 C# 时发布 Java 代码,但我碰巧知道 Java...

      public void depthFirstSearch(Graph graph, Vertex start){
          Set<Vertex> visited = new HashSet<Vertex>(); // could use vertex.isVisited()...
          Deque<Vertex> stack = new ArrayDeque<Vertex>(); // stack implies depth first
      
          // first visit the root element, then add it to the stack so
          // we will visit it's children in a depth first order
          visit(start);
          visited.add(start);
          stack.push(start);   
      
          while(stack.isEmpty() == false){
              List<Edge> edges = graph.getEdges(stack.peekFirst());
              Vertex nextUnvisited = null;
              for(Edge edge : edges){
                  if(visited.contains(edge.getEndVertex)) == false){
                     nextUnvisited = edge.getEndVertex();
                     break; // break for loop
                  }
              }
              if(nextUnvisited == null){
                  // check the next item in the stack
                  Vertex popped = stack.pop();
              } else {
                  // visit adjacent unvisited vertex
                  visit(nextUnvisited);
                  visited.add(nextUnvisited);
                  stack.push(nextUnvisited); // visit it's "children"
              }
          }
      }
      
      public void visit(Vertex vertex){
          // your own visit logic (string append, etc)
      }
      

      您可以轻松地将其修改为广度优先搜索,方法是将 Deque 用作队列而不是堆栈,如下所示:

      stack.pop()  >>>>  queue.removeFirst()
      stack.push() >>>>  queue.addLast()
      

      请注意,为此目的,Graph 和 Edge 类支持以下操作:

      public interface Graph {
          ...
          // get edges originating from Vertex v
          public List<Edge> getEdges(Vertex v);
          ...
      }
      
      public interface Edge {
          ...
          // get the vertex at the start of the edge
          // not used here but kind of implied by the getEndVertex()...
          public Vertex getStartVertex();
          // get the vertex at the end of the edge
          public Vertex getEndVertex();
          ...
      }
      

      希望这能给你一些想法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-04
        • 2019-07-29
        • 2017-12-31
        • 2020-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-31
        相关资源
        最近更新 更多