我有一个演示树数据结构的集合,它的节点是:
Node:
Id
Name
ParentID
现在,我想为此集合中的每个节点获取level
,我尝试使用以下代码,但我想知道这是否是实现它的最佳方式。
Func<int, int> GetLevel = (nodeID) =>
{
int _level = 0;
var _node = source.First(p => p.Id == nodeID);
// while hasn't reached the root yet
while (_node .ParentID.HasValue)
{
_level++;
_node = source.First(p => p.Id == _node.ParentID);
}
return _level;
};
// source is a collection of Node.
var query = from node in source
select new
{
Id = node.Id,
Name = node.Name,
ParentID = node.ParentID,
Level = GetLevel(node.Id)
};
我认为GetLevel
函数的开销能够减少。或者没有这个功能可能有更好的方法直接获得它!
任何想法!
答案 0 :(得分:3)
使用此Node class,您可以轻松完成此操作。
public class Subject
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
}
创建一个树并显示每个节点的级别:
var list = new List<Subject>
{
new Subject {Id = 0, ParentId = null, Name = "A"},
new Subject {Id = 1, ParentId = 0, Name = "B"},
new Subject {Id = 2, ParentId = 1, Name = "C"},
new Subject {Id = 3, ParentId = 1, Name = "D"},
new Subject {Id = 4, ParentId = 2, Name = "E"},
new Subject {Id = 5, ParentId = 3, Name = "F"},
new Subject {Id = 6, ParentId = 0, Name = "G"},
new Subject {Id = 7, ParentId = 4, Name = "H"},
new Subject {Id = 8, ParentId = 3, Name = "I"},
};
var rootNode = Node<Subject>.CreateTree(list, n => n.Id, n => n.ParentId).Single();
foreach (var node in rootNode.All)
{
Console.WriteLine("Name {0} , Level {1}", node.Value.Name, node.Level);
}
答案 1 :(得分:2)
您可以使用广度优先遍历在n
步骤中自上而下执行此操作。就我所见,你的方法是n*log(n)
?
使用Level
字段
class Node
{
public string Id;
public string ParentID;
public int Level;
public Node SetLevel(int i)
{
Level = i;
return this;
}
}
void Main()
{
var source = new List<Node>(){
new Node(){ Id = "1" },
new Node(){ Id = "2", ParentID="1"},
new Node(){ Id = "3", ParentID="1"},
new Node(){ Id = "4", ParentID="2"}
};
var queue = source.Where(p => p.ParentID == null).Select(s => s.SetLevel(0)).ToList();
var cur = 0;
while (queue.Any())
{
var n = queue[0];
queue.AddRange(source.Where(p => p.ParentID == n.Id).Select(s => s.SetLevel(n.Level + 1)));
queue.RemoveAt(0);
}
source.Dump();
}
输出:
Id ParentID Level
1 null 0
2 1 1
3 1 1
4 2 2
但这一切都取决于Linq部分(.Where)的复杂性
答案 2 :(得分:1)
由于您说您需要获取此集合中每个节点的级别,因此您可能会生成从节点到级别急切的映射。
这可以在O(n)时间内进行,并进行适当的广度优先遍历。
(未测试的):
public static Dictionary<Node, int> GetLevelsForNodes(IEnumerable<Node> nodes)
{
//argument-checking omitted.
// Thankfully, lookup accepts null-keys.
var nodesByParentId = nodes.ToLookup(n => n.ParentID);
var levelsForNodes = new Dictionary<Node, int>();
// Singleton list comprising the root.
var nodesToProcess = nodesByParentId[null].ToList();
int currentLevel = 0;
while (nodesToProcess.Any())
{
foreach (var node in nodesToProcess)
levelsForNodes.Add(node, currentLevel);
nodesToProcess = nodesToProcess.SelectMany(n => nodesByParentId[n.Id])
.ToList();
currentLevel++;
}
return levelsForNodes;
}
答案 3 :(得分:1)
您正在做的更紧凑的版本如下,注意我使用了简化的数据结构进行测试,并且Flatten返回IEnumerable&lt;树&gt;树中每个节点从变量树开始向下。如果您有权访问该源,我会将递归深度函数作为树类的一部分。如果你经常这样做或者你的树很大(或两者都是),我特别想要在字典或树结构本身中缓存深度的解决方案。如果你不这样做,这将很好。我使用它来从GUI中通过相对较小的树结构,没有人认为操作很慢。复杂度是获得每个节点深度的O(N log N)平均情况。如果您想查看我明天可以提供的所有代码。
Func<Tree, int> Depth = null;
Depth = p => p.Parent == null ? 0 : Depth(p.Parent) + 1;
var depth = tree.Flatten().Select(p => new { ID = p.NodeID(), HowDeep = Depth(p) });
答案 4 :(得分:0)
现在你正在多次处理很多事情。 我会递归地建立关卡。这样您就不会有任何处理开销。
另外,如果你经常运行这个功能,我会把节点类中的级别作为一个变量放入,当添加一个节点时会自动计算。
源代码如下。
答案 5 :(得分:0)
尝试使用.ToDictionary
,如下所示:
var dictionary =
source.ToDictionary(n => n.Id, n => n.ParentId);
Func<int, int> GetLevel = nid =>
{
var level = -1;
if (dictionary.ContainsKey(nid))
{
level = 0;
var pid = dictionary[nid];
while (pid.HasValue)
{
level++;
pid = dictionary[pid.Value];
}
}
return level;
};
这是相当有效的,因为您的最终查询将通过所有节点递归。因此,建立字典的费用很低。
根据节点的深度,您可能会发现构建字典比在任何情况下进行多级强力搜索更快。