Algorithm 如何存储Euler图结构?
我正在研究Euler路径问题,发现了一个问题:如何定义或存储Euler图结构 通常的方法是使用“伴随矩阵”,定义C[i][j]来存储i-j之间的边。它简洁有效!但这种矩阵受到两个节点之间的边唯一的情况的限制(图1) 如果有多个边(图2)会怎样?我的解决方案可能是使用自定义类,如“Graph”、“Node”、“Edge”来存储图形,但将图形划分为一些离散的结构,这意味着我们必须考虑更多的类细节,这可能会影响效率和简洁性。所以我非常渴望听到你的建议!非常感谢Algorithm 如何存储Euler图结构?,algorithm,graph,graph-algorithm,Algorithm,Graph,Graph Algorithm,我正在研究Euler路径问题,发现了一个问题:如何定义或存储Euler图结构 通常的方法是使用“伴随矩阵”,定义C[i][j]来存储i-j之间的边。它简洁有效!但这种矩阵受到两个节点之间的边唯一的情况的限制(图1) 如果有多个边(图2)会怎样?我的解决方案可能是使用自定义类,如“Graph”、“Node”、“Edge”来存储图形,但将图形划分为一些离散的结构,这意味着我们必须考虑更多的类细节,这可能会影响效率和简洁性。所以我非常渴望听到你的建议!非常感谢 class EulerPath {
class EulerPath
{
class Graph
{
Node[] Nodes;
Edge[] Edges;
}
class Node{...}
class Edge{...}
}
您可以使用邻接矩阵来存储具有多条边的图形。只需将
c[i][j]
的值设为顶点i
与顶点j
相邻的次数。第一种情况是1,第二种情况是3。另请参见——邻接矩阵并不是仅由1和0组成的,这只是简单图的邻接矩阵的特例
编辑:可以在邻接矩阵中表示第二个图形,如下所示:
1 2 3 4
1 0 3 1 1
2 3 0 1 1
3 1 1 0 0
4 1 1 0 0
0 [1]
1 [2,3]
2 [1,3]
3 [1,2]
您至少可以通过三种方式执行此操作: 邻接列表 这意味着您有一个名为al[N][N]的2D数组 al[N][N]这N是节点索引 al[N][N]这N是邻居节点索引 例如,具有此输入的图形:
0 => 1
1 => 2
2 => 3
3 => 1
邻接列表如下所示:
1 2 3 4
1 0 3 1 1
2 3 0 1 1
3 1 1 0 0
4 1 1 0 0
0 [1]
1 [2,3]
2 [1,3]
3 [1,2]
PS:由于这是一个2D数组,并且不会使用所有水平单元格,因此需要跟踪每个图形索引的连接邻居数,因为某些编程语言使用零初始化数组值,零是图形中的节点索引。这可以通过创建另一个数组来轻松完成,该数组将计算每个图索引的邻居数。本例示例:numLinks:[1,2,2,2]
矩阵
使用矩阵,您可以创建一个nx2d数组,并将一个1
值放置在行/列neighobor节点的交点处:
具有上述相同输入的示例:
0 1 2 3
0 0 1 0 0
1 1 0 1 1
2 0 1 0 1
3 0 1 1 0
类节点
最后一个方法是创建一个名为
Node
的类,该类包含Node类型的动态数组。您可以将连接的其他节点存储在该数组中考虑使用链表向量。添加一个类,该类将为顶点
以及权重
(命名为条目
)。您的权重最好是另一个向量或链表(最好是ll),它将包含相应顶点的所有可能权重。您的主类将有一个向量向量,或者一个链表向量(我更喜欢链表,因为在执行任何操作时,您很可能不需要随机访问,被迫遍历每个条目)。主类将有一个包含所有顶点的向量。在C++中,这看起来是这样的:
class Graph{
std::vector<std::forward_list<Entry>> adj_list;
std::vector<Vertex> vertices;
};
类图{
std::向量调整列表;
向量顶点;
};
其中对应于顶点[i]
的顶点
在adj_list[i]
中有相应的列表。由于每个条目
都包含与您所连接的顶点
相关的信息和相应的权重,因此您的图形将由此类表示。什么类型的操作的效率
如果你想在互联网上的两个IP地址之间找到一条路由,那么你的邻接矩阵可能是一百万个节点的平方,即十亿字节的条目。当查找连接到一个给定节点的所有节点上升为n时,您可以查找每个节点一百万次的查找,以查找连接到该节点的节点。效率极低
如果您的问题只涉及几个节点,并且很少运行,那么邻接矩阵是简单直观的
对于大多数涉及遍历图的问题,更好的解决方案可能是创建一个名为node的类,该类具有一个属性,即它所连接的所有节点的集合(比如列表)。对于大多数现实世界的应用程序,连接节点的列表远小于所有节点的总数,因此计算起来更加紧凑。此外,它在查找边方面非常高效-您可以在每个节点的固定时间内获得所有连接节点的列表
如果使用此结构,其中有一个节点类,该节点类将其连接到的所有节点的集合作为属性,然后在创建新边(例如,在节点a和节点B之间)时,将B添加到a连接到的节点集合中,将a添加到B连接到的节点集合中。请原谅我的Java/C#,类似
class Node{
Arraylist<Node> connectedNodes;
public Node() // initializer
{
connectedNodes = new ArrayList<Node>;
}
}
// and somewhere else you have this definition:
public addEdgeBetween(Node firstNode, Node secondNode) {
firstNode.connectedNodes.Add(secondNode);
secondNode.connectedNodes.Add(firstNode);
}
类节点{
Arraylist连接节点;
public Node()//初始值设定项
{
connectedNodes=新的ArrayList;
}
}
//在其他地方你有这样的定义:
公共addEdgeBetween(节点firstNode、节点secondNode){
firstNode.connectedNodes.Add(第二个节点);
secondNode.connectedNodes.Add(第一个节点);
}
与删除边类似,删除A到B集合中的引用,反之亦然。无需定义单独的边类,边在交叉链接两个节点的结构中是隐式的
这就是实现这种结构所需要做的一切,它(对于大多数现实世界的问题)使用的内存比邻接矩阵少得多,对于大多数问题来说,对于大量的节点来说速度要快得多,并且最终要灵活得多
定义一个节点类也为添加各种增强打开了一个逻辑空间。例如,您可能会决定为每个节点生成两步之外的所有节点的列表,因为这样可以改进路径查找。您可以轻松地将其添加为另一个集合