从理论上讲,我可以将哪种数据结构用于具有共享内存的树?

时间:2016-05-06 06:13:58

标签: .net algorithm data-structures tree

真实世界的问题

我有一片树林。像20,000棵树一样。这片森林占据了太多的记忆。但是这些树是相似的 - 你可以找到一组树(约200棵树),这样它们就有一个非常大的子树(几十%)。

理论

知道:

  

树是相似的,即它们共享一个共同的连接子图,包括根(不一定包括叶子 - 但可能)。

是否存在允许有效存储该信息的任何数据结构?创建结构后,我只对阅读感兴趣。

它不一定是.NET的解决方案,我可以从头开始编码,我只需要这个想法:D当然,如果.NET中有一些鲜为人知的结构那种实现了这一点,我很高兴知道。

我有一种感觉,这个共享内存的东西可能与不可变结构有关,根据定义它们应该共享内存...

不幸的是,我的树不是二元搜索树。他们可以有任何数量的孩子。

至于阅读,这很简单。我总是从导航到。正如您在任何JSON或XML中所做的那样,给定一个值的确切路径。

相似性

连接的子图包括两个树中相同(可能)的根 始终包含根并向下跨越。在某些情况下,甚至可以到达树叶。查看示例(黄色部分是连接的子图,包括根):

enter image description here

根据这些规则,从数学上讲,所有的树都是相似的 - 连接的子图是空的,或者它只包含根,或者是归纳的 - 它包含根及其子... < / p>

4 个答案:

答案 0 :(得分:3)

您可以按不同的&#34;所有者&#34;对您的树节点的子节目进行分组。添加节点时,指定所有者(或使用默认值&#34;共享&#34;所有者)。遍历树时,还指定了所有者。这是一个草图代码:

class TreeNode {
    protected static readonly object SharedOwner = new object();
}

class TreeNode<T> : TreeNode {        
    private readonly T _data;
    private readonly Dictionary<object, List<TreeNode<T>>> _children;

    public TreeNode(T data) {
        this._data = data;
        _children = new Dictionary<object, List<TreeNode<T>>>();
    }

    public TreeNode<T> AddChild(T data, object owner = null) {
        if (owner == null)
            owner = SharedOwner;
        if (!_children.ContainsKey(owner))
            _children.Add(owner, new List<TreeNode<T>>());
        var added = new TreeNode<T>(data);
        _children[owner].Add(added);
        return added;
    }

    public void Traverse(Action<T> visitor, object owner = null) {
        TraverseRecursive(this, visitor, owner);
    }

    private void TraverseRecursive(TreeNode<T> node, Action<T> visitor, object owner = null) {
        visitor(node._data);
        // first traverse "shared" owner's nodes
        if (node._children.ContainsKey(SharedOwner)) {
            foreach (var sharedNode in node._children[SharedOwner]) {
                TraverseRecursive(sharedNode, visitor, owner);
            }
        }
        // then real owner's nodes
        if (owner != null && owner != SharedOwner && node._children.ContainsKey(owner)) {
            foreach (var localNode in node._children[owner]) {
                TraverseRecursive(localNode, visitor, owner);
            }
        }
    }
}

示例用法:

class Program {
    static void Main(string[] args) {
        // this is shared part
        var shared = new TreeNode<string>("1");
        var leaf1 = shared.AddChild("1.1").AddChild("1.1.1");
        var leaf2 = shared.AddChild("1.2").AddChild("1.2.1");
        var firstOwner = new object();
        var secondOwner = new object();
        // here we branch first time
        leaf1.AddChild("1.1.1.1", firstOwner);
        leaf2.AddChild("1.2.1.1", firstOwner);
        // and here another branch
        leaf1.AddChild("1.1.1.2", secondOwner);
        leaf2.AddChild("1.2.1.2", secondOwner);
        shared.Traverse(Console.WriteLine, firstOwner);
        shared.Traverse(Console.WriteLine, secondOwner);
        Console.ReadKey();
    }        
}

答案 1 :(得分:1)

“重用”具有不同叶子的树的一部分的问题是,您需要提供有关如何将公共部分的叶子映射到不同图形的其他信息。由于您的搜索可以在公共部分中的任何节点中结束,这意味着您需要将此公共子树中的每个节点映射到每个图形内的“实际”节点。

例如,这两个“相似”树A和B共享子树的公共部分(节点1367,{{ 1}}):

A and B share a common subtree

要重复使用“公共部分”,您可以执行以下操作:

Each tree needs its own lookup table

这是否可以节省空间?好吧,如果知道8A意味着您可以直接“计算”3而无需查找,那么在此特定示例中,您不需要映射“内部”任何图表的公共节点A33,节省了一点空间。

换句话说,如果这些公共子树不仅共享其结构,而且还共享其内容,那么您只需将出口节点(叶子)映射到单独的图节点。

(适用更新)

为了完整起见,我添加了一个@Evk's implementation图表,它将查找表存储在实际节点中。在空间方面,它不应该有所不同,但由于您在该答案中有一个有效的例子,因此可视化它可能是有用的:

enter image description here

既然你知道你正在处理的实际数据的细节,你可能会在这里和那里挤出一点空间,但我的建议仍然是:

  1. 向机器添加更多RAM,或
  2. 如果使用SSD,则使用基于磁盘的树(可能是b树)。

答案 2 :(得分:0)

如果我理解你的问题,解决方案的一部分就是让几棵树共享子树的根,以及叶子中的信息告诉假期所属的树。排列此信息的方式取决于您需要执行的查询类型。

根据新的解释,我理解你需要代表最大树并使用“停止列表”增强节点,该“停止列表”指示部分树中哪个停止在此节点,即不共享更多后代。

停止列表的相应数据结构再次取决于访问模式。

这种重复很可能比简单的树木更紧凑。

答案 3 :(得分:-3)

您是否尝试过AVL Trees(自动平衡二叉树)?如果没有,这种数据结构在这种情况下是有效的。