[英]How do I efficiently search this hierarchical structure?
我有一個如下所示的數據結構:
public class Node
{
public string Code { get; set; }
public string Description { get; set; }
...
public List<Node> Children { get; set; }
}
在給定指定的Code
,我想編寫一個返回特定節點的方法。 通常我會在層次結構中進行遞歸遍歷以找到節點,但我關注性能。 層次結構中將有數千個節點,並且此方法將被多次調用。
如何構建它以使其更快? 我是否可以使用現有的數據結構,可能在保留層次結構的同時對Code
執行二進制搜索,而不是自己重新實現某種形式的二進制搜索?
將所有節點添加到字典中,並將代碼作為鍵。 (你可以做一次),字典中的查找基本上是O(1)。
void FillDictionary(Dictionary<string, Node> dictionary, Node node)
{
if (dictionary.ContainsKey(node.Code))
return;
dictionary.Add(node.Code, node);
foreach (Node child in node.Children)
FillDictionary(dictionary, child)
}
如果你知道root,用法將是:
var dictionary = new Dictionary<string, Node>();
FillDictionary(dictionary, rootNode);
如果不這樣做,您可以使用相同的字典在所有節點上調用FillDictionary()
方法。
這是我和其他人談論的全面實現。 請注意,通過使用索引字典,您將使用更多的內存(僅對節點的引用)以換取更快的搜索。 我正在使用事件來動態更新索引。
編輯:添加評論並修正了一些內容。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create the root node for the tree
MyNode rootNode = new MyNode { Code = "abc", Description = "abc node" };
// Instantiate a new tree with the given root node. string is the index key type, "Code" is the index property name
var tree = new IndexedTree<MyNode, string>("Code", rootNode);
// Add a child to the root node
tree.Root.AddChild(new MyNode { Code = "def", Description = "def node" });
MyNode newNode = new MyNode { Code = "foo", Description = "foo node" };
// Add a child to the first child of root
tree.Root.Children[0].AddChild(newNode);
// Add a child to the previously added node
newNode.AddChild(new MyNode { Code = "bar", Description = "bar node" });
// Show the full tree
Console.WriteLine("Root node tree:");
PrintNodeTree(tree.Root, 0);
Console.WriteLine();
// Find the second level node
MyNode foundNode = tree.FindNode("def");
if (foundNode != null)
{
// Show the tree starting from the found node
Console.WriteLine("Found node tree:");
PrintNodeTree(foundNode, 0);
}
// Remove the last child
foundNode = tree.FindNode("foo");
TreeNodeBase nodeToRemove = foundNode.Children[0];
foundNode.RemoveChild(nodeToRemove);
// Add a child to this removed node. The tree index is not updated.
nodeToRemove.AddChild(new MyNode { Code = "blah", Description = "blah node" });
Console.ReadLine();
}
static void PrintNodeTree(MyNode node, int level)
{
Console.WriteLine(new String(' ', level * 2) + "[" + node.Code + "] " + node.Description);
foreach (MyNode child in node.Children)
{
// Recurse through each child
PrintNodeTree(child, ++level);
}
}
}
public class NodeEventArgs : EventArgs
{
public TreeNodeBase Node { get; private set; }
public bool Cancel { get; set; }
public NodeEventArgs(TreeNodeBase node)
{
this.Node = node;
}
}
/// <summary>
/// The base node class that handles the hierarchy
/// </summary>
public abstract class TreeNodeBase
{
/// <summary>
/// A read-only list of children so that you are forced to use the proper methods
/// </summary>
public ReadOnlyCollection<TreeNodeBase> Children
{
get { return new ReadOnlyCollection<TreeNodeBase>(this.children); }
}
private IList<TreeNodeBase> children;
/// <summary>
/// Default constructor
/// </summary>
public TreeNodeBase()
: this(new List<TreeNodeBase>())
{
}
/// <summary>
/// Constructor that populates children
/// </summary>
/// <param name="children">A list of children</param>
public TreeNodeBase(IList<TreeNodeBase> children)
{
if (children == null)
{
throw new ArgumentNullException("children");
}
this.children = children;
}
public event EventHandler<NodeEventArgs> ChildAdding;
public event EventHandler<NodeEventArgs> ChildRemoving;
protected virtual void OnChildAdding(NodeEventArgs e)
{
if (this.ChildAdding != null)
{
this.ChildAdding(this, e);
}
}
protected virtual void OnChildRemoving(NodeEventArgs e)
{
if (this.ChildRemoving != null)
{
this.ChildRemoving(this, e);
}
}
/// <summary>
/// Adds a child node to the current node
/// </summary>
/// <param name="child">The child to add.</param>
/// <returns>The child node, if it was added. Useful for chaining methods.</returns>
public TreeNodeBase AddChild(TreeNodeBase child)
{
NodeEventArgs eventArgs = new NodeEventArgs(child);
this.OnChildAdding(eventArgs);
if (eventArgs.Cancel)
{
return null;
}
this.children.Add(child);
return child;
}
/// <summary>
/// Removes the specified child in the current node
/// </summary>
/// <param name="child">The child to remove.</param>
public void RemoveChild(TreeNodeBase child)
{
NodeEventArgs eventArgs = new NodeEventArgs(child);
this.OnChildRemoving(eventArgs);
if (eventArgs.Cancel)
{
return;
}
this.children.Remove(child);
}
}
/// <summary>
/// Your custom node with custom properties. The base node class handles the tree structure.
/// </summary>
public class MyNode : TreeNodeBase
{
public string Code { get; set; }
public string Description { get; set; }
}
/// <summary>
/// The main tree class
/// </summary>
/// <typeparam name="TNode">The node type.</typeparam>
/// <typeparam name="TIndexKey">The type of the index key.</typeparam>
public class IndexedTree<TNode, TIndexKey> where TNode : TreeNodeBase, new()
{
public TNode Root { get; private set; }
public Dictionary<TIndexKey, TreeNodeBase> Index { get; private set; }
public string IndexProperty { get; private set; }
public IndexedTree(string indexProperty, TNode rootNode)
{
// Make sure we have a valid indexProperty
if (String.IsNullOrEmpty(indexProperty))
{
throw new ArgumentNullException("indexProperty");
}
Type nodeType = rootNode.GetType();
PropertyInfo property = nodeType.GetProperty(indexProperty);
if (property == null)
{
throw new ArgumentException("The [" + indexProperty + "] property does not exist in [" + nodeType.FullName + "].", "indexProperty");
}
// Make sure we have a valid root node
if (rootNode == null)
{
throw new ArgumentNullException("rootNode");
}
// Set the index properties
this.IndexProperty = indexProperty;
this.Index = new Dictionary<TIndexKey, TreeNodeBase>();
// Add the root node
this.Root = rootNode;
// Notify that we have added the root node
this.ChildAdding(this, new NodeEventArgs(Root));
}
/// <summary>
/// Find a node with the specified key value
/// </summary>
/// <param name="key">The node key value</param>
/// <returns>A TNode if found, otherwise null</returns>
public TNode FindNode(TIndexKey key)
{
if (Index.ContainsKey(key))
{
return (TNode)Index[key];
}
return null;
}
private void ChildAdding(object sender, NodeEventArgs e)
{
if (e.Node.Children.Count > 0)
{
throw new InvalidOperationException("You can only add nodes that don't have children");
// Alternatively, you could recursively get the children, set up the added/removed events and add to the index
}
// Add to the index. Add events as soon as possible to be notified when children change.
e.Node.ChildAdding += new EventHandler<NodeEventArgs>(ChildAdding);
e.Node.ChildRemoving += new EventHandler<NodeEventArgs>(ChildRemoving);
Index.Add(this.GetNodeIndex(e.Node), e.Node);
}
private void ChildRemoving(object sender, NodeEventArgs e)
{
if (e.Node.Children.Count > 0)
{
throw new InvalidOperationException("You can only remove leaf nodes that don't have children");
// Alternatively, you could recursively get the children, remove the events and remove from the index
}
// Remove from the index. Remove events in case user code keeps reference to this node and later adds/removes children
e.Node.ChildAdding -= new EventHandler<NodeEventArgs>(ChildAdding);
e.Node.ChildRemoving -= new EventHandler<NodeEventArgs>(ChildRemoving);
Index.Remove(this.GetNodeIndex(e.Node));
}
/// <summary>
/// Gets the index key value for the given node
/// </summary>
/// <param name="node">The node</param>
/// <returns>The index key value</returns>
private TIndexKey GetNodeIndex(TreeNodeBase node)
{
TIndexKey key = (TIndexKey)node.GetType().GetProperty(this.IndexProperty).GetValue(node, null);
if (key == null)
{
throw new ArgumentNullException("The node index property [" + this.IndexProperty + "] cannot be null", this.IndexProperty);
}
return key;
}
}
}
沒有任何基於比較的Code組織,你無法阻止樹的O(n)演練。 但是,如果您在讀取XML文件以構建節點樹的同時構建另一個數據結構(O(1)訪問字典或O(log n)訪問列表),則可以構建無需更多開銷即可快速訪問的其他結構。
將它們存儲在字典中,這為您提供了O(1)查找時間。
var dict = new Dictionary<string, Node>();
dict.Add(n.Code, n);
假設n
是一個水合Node
對象。 然后,您可以獲取特定的節點項
var node = dict["CODEKEY"];
它將根據提供的密鑰返回您的特定節點。 您甚至可以使用以下方法檢查節點
if (d.ContainsKey("CODEKEY"))
{
//Do something
}
為了知道節點在原始集合中的位置,您需要向Node類添加某種指針層次結構,以便它了解前一節點
如果您可以更改節點的順序,則可以創建二進制搜索樹 。
這個SO答案引用了一個你應該可以使用的庫嗎?
我會說實話; 我很難理解Itay的建議是如何完美無缺的。
以下是您所說的要求:
在給定指定的
Code
,我想編寫一個返回特定節點的方法。
所以Code
是獨一無二的,我接受了嗎? 然后沒有什么能阻止你將所有Node
對象放入Dictionary<string, Node>
。
在你對Itay的答案的評論中,你這樣說:
我得知字典是一個平面索引,我只是不明白該索引將如何進入我的分層數據結構並檢索正確的節點。
如果你的意思是你不明白字典將如何知道你的Node
在數據結構中的位置 ,那是因為它不是。 這有關系嗎? 您沒有在要求中說明您想知道節點在數據結構中的位置; 您只指定要獲取節點 。 為了做到這一點,字典只需要知道節點在內存中的位置,而不是一些完全獨立的數據結構。
提供一個簡化的例子(如果我在這里侮辱你的情報,我很抱歉,但是因為這可能至少澄清了其他人的觀點),假設你有一個包含所有唯一整數的普通LinkedList<int>
。 然后,您枚舉此列表並使用它來構造Dictionary<int, LinkedListNode<int>>
,這個想法是您希望能夠根據其值快速查找節點。
字典是否需要知道每個節點在鏈表中的位置? 當然不僅僅是它在記憶中的位置。 一旦使用字典在O(1)中找到了基於其值的節點,您當然可以使用節點本身輕松地向前或向后遍歷鏈表,這恰好意識到(按設計)鏈表包含它。
它與您的層次結構相同,只比鏈表更復雜。 但同樣的原則也適用。
為什么不使用SortedSet <Node>
來構建包含所有Node實例的BST? 比較器將基於Code
- 容器必須具有范圍,以使其在所有成員中是唯一的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.