我有一个节点/顶点列表,以及连接这些节点的线/边列表。这些列表没有以任何方式排序或排序,但包含了特定数据集的所有边和节点。
边缘是由笛卡尔坐标(x1,y1)和(x2,y2)定义的线段,每个节点位置也由坐标(x,y)表示。 附有depicts a typical test case的图片清楚地显示了两棵树,其根为R1和R2,每个节点(包括叶节点)(标记为Lx,突出显示的橙色文本和蓝色圆圈)均带有相应的坐标。
class Node
{
Point coordinates; // x,y coordinates of node <int,int>
Node parentNode; // parent node of current node. ( may not be necessary as parentID may suffice to keep reference to parent node)
List<Node> children; // List of all child nodes of this node
List<Edge> edges; // list of all edges connected to this node
string Data; // relevant data of each node
long nodeID; // current nodes ID
long parentID; // ID of current node's parent node
}
每个边缘表示为:
class Edge
{
Point p1; // first end coordinates of line segment
Point p2; // coordinates of the other end of the segment
}
从所附图像中可以清楚地看到,边缘N1-N2将表示为p1 =(0,0),p2 =(20,20)或p1 =(20,20),p2 =(0, 0)。顺序是随机的。
假设1:由于节点R1和R2上节点的类型,它们可以清楚地识别为根节点。 (同心圆与红色外圈)。 假设2:还提供了直接连接到节点的所有边缘的列表,例如,节点N8将具有段:N8-L7,N8-R2,N8-N9,N8-N7。
我的问题是我该如何用C#编写一个函数,该函数具有两个输入,即边的列表和节点的列表,并返回一个根节点,或参考子节点返回树的根节点,这也会与附图中的描述相同/正确。
List<Node> getRootNodes(List<Node> nodes, List<Edge> edges)
{
// implementation here
List<Node> roots = new List<Node>();
//more logic
//
//
return roots; //returned list may have more than one root node!
}
我已经能够列出每个节点的边缘,但是无法找到构造树的方法。 我已阅读过Kruskal's Algorithm,但不确定是否可以适应此问题。我不确定是否会保留图中所示的顺序。
所有代码都使用C#,但是任何C风格的语言解决方案都可以。
NB:我在该网站上看到的答案都假定树节点按照父节点和子节点的顺序是已知的。我可以说两个节点由一条边连接,但是不能确定哪个节点是父节点,哪个节点是子节点。
谢谢
格雷格M
答案 0 :(得分:0)
您说有两个假设:
N8-L7, N8-R2, N8-N9, N8-N7
。我将假设也有段L7-N8, R2-N8, N9-N8, N7-N8
。如果没有,则可以从您提到的现有细分中轻松地构建它们。
在回答我的问题时,您还说过,根没有父级,每个节点只有一个父级。这使这变得容易得多。
首先,创建一个以节点名作为键的字典,其值是它所连接的节点的列表。这将是Dictionary<string, List<string>>
。在上面的示例中,您将:
key value
N8 L7, R2, N9, N7
L7 N8
R2 N8
N9 N8
N7 N8
在上面的列表中,仅N8被完全填充。您的字典将包含所有节点的所有连接。
要构建它:
var segmentDict = new Dictionary<string, List<string>>();
foreach (var segment in SegmentList)
{
List<string> nodeConnections;
if (!segmentDict.TryGetValue(segment.From, out nodeConnections))
{
// This node is not in dictionary. Create it.
nodeConnections = new List<string>();
segmentDict.Add(segment.From, nodeConnections);
}
nodeConnections.Add(segment.To);
}
现在,我们将构建一个新的Dictionary<string, List<string>>
,它最初是空的。这将是最后一棵树,每个节点只有子节点。
由于您知道根节点没有父节点,并且一个节点最多只能有一个父节点,因此您可以从根节点开始并开始建立连接。扫描字典,并为每个根节点添加字典,并在finalTree
中创建一个带有空子列表的条目:
var finalTree = new Dictionary<string, List<string>>();
var nodeQueue = new Queue<string>();
foreach (var nodeName in segmentDict.Keys)
{
if (nodeName.StartsWith("R")) // if it's a root node
{
finalTree.Add(nodeName, new List<string>()); // add tree node
nodeQueue.Enqueue(nodeName); // and add node to queue
}
}
现在,开始处理队列。对于从队列中拉出的每个节点名称:
finalTree
中为该节点创建一个条目。segmentDict
获取该节点的连接列表。finalTree
中没有该节点的条目,则将该节点添加到队列中,并将其添加到finalTree
中该节点的连接列表中。重复此操作,直到队列为空。
代码看起来像这样:
while (nodeQueue.Count > 0)
{
var fromNode = nodeQueue.Dequeue();
var nodeChildren = finalTree[fromNode];
foreach (var toNode in segmentDict[fromNode])
{
if (finalTree.ContainsKey(toNode))
{
// This node has already been seen as a child node.
// So this connection is from child to parent. Ignore it.
break;
}
nodeChildren.Add(toNode); // update children for this node
finalTree.Add(toNode, new List<string>()); // create tree entry for child node
nodeQueue.Enqueue(toNode); // add child to queue
}
}
我在这里所做的是从树的根部到叶子,因此,当我第一次遇到节点时,我知道它是父子链接而不是子父链接。因此,所有父子链接都将被删除。
然后,通过遍历finalTree
并在每个根节点上进行深度优先遍历,即可得到树:
foreach (var kvp in finalTree)
{
if (kvp.Key.StartsWith("R"))
{
PrintTree(kvp.Key, kvp.Value);
}
}
void PrintTree(string nodeName, List<string> children)
{
Console.WriteLine("node {1} has children {2}.", nodeName, string.Join(",", children);
foreach (var child in children)
{
PrintTree(child, finalTree[child]);
}
}
当然,您需要调整输出,但这显示了如何从根开始遍历树。