将一组图形边缘转换为完全“打印”树的有效方法

时间:2014-07-21 21:01:23

标签: algorithm perl tree-traversal

问题

我试图将一组图形边缘存储如下:

my $edges = {
    # Parent => set_of_children
    1 => [ 2, 6, 8 ],
    2 => [ 6 ],
    6 => [ 7 ],
};

并将其转换为完整的树(最终输出为JSON,但我完全可以使用Perl等效的数据结构,我可以提供给JSON模块的编码)

my $full_tree = {
    node => 1,
    children => [
        {
            node => 2,
            children => [ # Subtree expanded right here. List children
                {
                    node => 6,
                    children => [
                        { node => 7 }, # Leaf level, no children
                    ],
                }, # End Node 6
            ],
        }, # End Node 2
        {
            node => 6,
            children => [
                { node => 7 }, # IMPORTANT: Again expanding node #6!
            ],
        }, # End Node 6
        {
            node => 8,
        }, # Leaf level, no children
    ],
};

请注意重要部分:

  • 如果在树中遇到节点,我们会为它打印一个COMPLETE子树
  • 如果在树中遇到节点> 1次,我们会在每个位置打印完整的子树副本(见证节点6)。

我尝试了什么

我尝试了显而易见的(递归打印它 - 我甚至不会在这里发布代码,因为递归解决方案是算法和数据结构101的材料)。

我需要帮助

在Perl中,子程序调用有成本。对于一个非常大的树(6位数的节点,在多个地方有多个节点),我将支付两者的性能损失:

  1. 必须重新计算多个节点。

    这可以通过记忆来解决。

  2. 在子程序调用堆栈上堆栈的性能损失。

    因此,我想知道是否有一个很好的Perl方法来打印所需的数据结构而没有通过实际子程序递归实现的重复算法。我认为某种DFS / BFS作为一个循环,但我不是树算法方面的专家,因此不知道如何处理它。

    一组通用的Perl特定算法/数据结构建议就足够了,我本身不需要完整的工作代码。

    对于大规模,性能有点重要。

3 个答案:

答案 0 :(得分:-1)

我不知道你期望在这里找到什么魔法。你说一个递归的解决方案是算法101所以甚至不值得发布,但你期望找到什么其他解决方案?

我同意它很直接,如下所示:

use strict;
use warnings;

use JSON;

my $edges = {
    1 => [ 2, 6, 8 ],
    2 => [ 6 ],
    6 => [ 7 ],
};

sub buildtree {
    my ($edges, $cached, $current, @past) = @_;

    die "Circular call for $current in @past"
        if grep {$current eq $_} @past;

    return $cached->{$current} ||= {
        node => $current,
        ($edges->{$current} ? (children => [
            map {buildtree($edges, $cached, $_, $current, @past)} @{$edges->{$current}}
        ]) : ()),
    };
}

my $tree = buildtree($edges, {}, 1);

print to_json($tree, {pretty => 1});

输出:

{
   "node" : "1",
   "children" : [
      {
         "children" : [
            {
               "node" : "6",
               "children" : [
                  {
                     "node" : "7"
                  }
               ]
            }
         ],
         "node" : "2"
      },
      {
         "node" : "6",
         "children" : [
            {
               "node" : "7"
            }
         ]
      },
      {
         "node" : "8"
      }
   ]
}

如果您不想缓存结果,那么所有人必须做的就是将||=更改为简单的作业=

答案 1 :(得分:-2)

如果您的数据结构与您拥有的数据结构非常相似,则可以在不递归的情况下进行树遍历。只需遵循此算法,同时只保留两个变量:i(当前节点,从$i=1开始)和p(您之前所在的节点,从$p = undef开始)。

如果前一个节点是当前节点的子节点,则在下一个子节点存在时转到下一个子节点,否则(如果下一个子节点不存在)转到父节点。如果上一个节点不是当前节点的子节点,请转到第一个子节点。

遵循上述算法,您将只使用两个状态变量遍历整个树。

答案 2 :(得分:-2)

供讨论。这是一个可能的解决方案。

它构建一个平面哈希,其中包含一个只显示父节点和外出子节点的有向图。

第二遍遍历列表并生成一个用于打印的结构。

use warnings; use strict;
use JSON;

my $edges = {
    # Parent => set_of_children                                                                                           
    1 => [ 2, 6, 8 ],
    2 => [ 6 ],
    6 => [ 7 ],
};

my %graph;

foreach my $this_node (sort keys %$edges) {

    my @children = @{ $edges->{$this_node} };

    # autovivify this node and it's children                                                                              
    $graph{ $_ } //= { node => $_ }
        for ($this_node, @children);

    # link up children                                                                                                    
    $graph{ $this_node }{children} = [ map { $graph{ $_ } } @children ];
}

my $tree = build_tree( $graph{1} );

print to_json $tree, {pretty => 1};

########################################################################

sub build_tree {
    my $node = shift;
    my %tree  = (node => $node->{node});

    if (my $children = $node->{children}) {
        $tree{children} = [ map {build_tree($_)} @$children ];
    }

    return \%tree;
}

输出:

{
   "children" : [
      {
         "children" : [
            {
               "children" : [
                  {
                     "node" : "7"
                  }
               ],
               "node" : "6"
            }
         ],
         "node" : "2"
      },
      {
         "children" : [
            {
               "node" : "7"
            }
         ],
         "node" : "6"
      },
      {
         "node" : "8"
      }
   ],
   "node" : "1"
}

不确定这是否与您尝试过的相似。在第二遍中有一些递归,但我希望基本算法是合理有效的。这可以通过各种方式进一步优化,具体取决于瓶颈。