我的应用程序需要不同的数据结构。
基本上我有一个由"节点"组成的自定义数据结构。 我的任务如下: 给定了许多不同的节点(我获得的节点数量未知)检索或创建新节点。它让我想起了具有多个参数的函数的缓存。唯一的区别是所有参数和返回值具有相同的类型,我返回的值可能会在稍后作为输入提供给我。
示例1 : 起初我得到节点A和C.现在我必须创建一个新节点(让它命名为AC)并返回它。 当我将来再次获得节点A和C 时,我需要能够非常快地确定我是否已经创建了AC节点并将其返回,或者,如果之前没有创建它,创建它并返回它。
示例2 : 当我得到节点C和A然后我必须返回/创建一个不同的节点!我无法返回AC,它必须是一个新节点(CA)。订单很重要!
稍后在处理它时,我也可能获得我之前创建的节点。 例如,在第三次调用我的数据结构时,我完全有可能收到节点" A和AC"。我必须再次创建一个新节点" A-AC",将其缓存并返回。
起初我使用了很多Dictionary<Tuple<Node, Node>, Node>
,但这有很多问题:
- 创建和比较元组对我的应用来说太慢了
- 参数的数量是固定的,我需要多个字典,每个字典都有不同的键(2元组,3元组,......)
我也有很多节点。我已经过滤了一些输入数据,但我必须处理至少1500万到2000万个不同的节点。
字典似乎没有削减它,性能和内存消耗似乎太高了。
我可以自由修改节点的实现方式,这样可能还有另一种技巧可以直接将多个节点链接到另一个节点?
如何尽可能有效地解决这个问题? 通常使用什么数据结构来解决这个问题?
答案 0 :(得分:2)
这是何时使用也称为Trie的数字搜索树的完美示例。基本上每个节点都有一个节点数组和一个空节点。当您按照示例1到A中的方式向下工作,然后到C,如果该C节点引用了空节点,那么您就知道已经加载了该节点。如果没有那么它还没有加载。我不认为有任何内置的trie实现,但它们并不难构建。我建了一次来存储英文字典并用它来查看是否存在单词。如果你正确构建它会占用内存中没有空间,并且访问时间为O(1)。
答案 1 :(得分:2)
似乎你有很多限制(时间,效率,内存占用)。说实话,我不知道你在哪里设置限制条件。
我曾经创建了一个小型数据结构,可以完成类似于你想要的东西。我想。
public class StackBlock
{
public string Component { get; set; }
public MyObject ResultingObject { get; set; }
public List<StackBlock> Blocks { get; set; }
}
这个想法是你使用它们来构造一个树,它可以作为已经创建的对象的缓存。对属性的简要描述:
"A"
或"C"
值"AC"
因此,如果您想存储"AC"
对象,这将是您保存的结构:
StackBlock
Component: "A"
ResultingObject: null
Blocks: [
StackBlock
Component: "C"
ResultingObject: MyObject "AC"
Blocks: [ ... ]
]
编辑非常简单地说,项目&#34; CGA&#34;将在:
中找到StackBlock "C" -> StackBlock "G" -> StackBlock "A" -> ResultingItem
您可以将堆叠块保持在一起,以实现更长和更长的组合。但是当你需要检索一个对象时,你所要做的就是按顺序遍历树:
请注意,对于每个步骤,如果找不到要查找的内容,则表示尚未创建,因此您必须创建对象,然后将其存储在树中。下次您提出申请时,它现在可用。
在你的情况下,&#34; AC&#34;和&#34; CA&#34;是不同的对象,树允许您将它们存储在不同的位置。
这也将确保您在对象已经在内存中时不创建对象,因为树结构只允许一个地方放置特定元素。
我希望这有点朝你想要的方向发展?
注意:在LINQ推出之前,这是我的一个非常古老的项目。我只能想象当您使用LINQ遍历树时,生成的代码相当优雅和简洁。我会饶你以前的做法。
回答以下评论
如果您有项目&#34; GA&#34;和项目&#34; CGA&#34;,它们不会成为相同 StackBlock&#34; A&#34;的一部分。 如果您已经缓存了这两个对象,那么树将如下所示:
StackBlock C
-> StackBlock G
-> StackBlock A
-> ResultingObject CGA
StackBlock G
-> StackBlock A
-> ResultingObject GA
注意:您将上层元素存储在List中。从该列表中,您可以找到第一个元素并开始向下钻取。
我想解决一个可能的混淆点:你看到两个Stackblocks&#34; G&#34;和两个StackBlocks&#34; A&#34;。 这些不是同一个对象。您在我的所有示例中看到的每个堆栈都是不同的对象(恰好有相同的字母)。
如果将StackBlock定义为结构而不是类,那么理解它可能会更好。它的工作方式相同,并且您无法在正在构建的树的不同层上重复使用相同的StackBlock。
因此,ResultingObject不应该是一个列表,因为我们应该只有一个对象叫做#34; CGA&#34;。练习的重点是防止创建重复的对象,因此这个数据结构专门定制为只允许一个地方放置缓存的对象。如果我充实了这个例子,也许会有所帮助,所以你可以看到一切都会结束的地方:
StackBlock C
-> StackBlock G
-> ResultingObject CG
-> StackBlock A
-> ResultingObject CGA
-> StackBlock B
-> ResultingObject CGB
StackBlock G
-> ResultingObject G
-> StackBlock A
-> ResultingObject GA
-> StackBlock X
-> ResultingObject GAX
-> StackBlock K
-> ResultingObject GAXK
查看名为&#34; G&#34;的两个Stackblock。其中一个位于顶层,因此它的ResultingObject只是G.但另一个是二级StackBlock。因此,它的ResultingObject是CG,因为你必须考虑你钻下的整个链。
我希望这有助于澄清它。一旦你理解了它,它就是一个简单的概念,但是我在描述它为什么会起作用时遇到一些困难:)