(2020.02.26)
图数据结构有如下四种实现方式。
实现方式对比
Edge list structure边列表
用非排序list分别保存一个图的顶点(V)和边(E)。V保存(n个)顶点名。E保存(m个)边名,并且每个边对象包含两个指针,分别是该边的两个顶点。
复杂度分析:
添加顶点,O(1);添加一条边,O(1);删除一个顶点(考虑到要在E中删除所有相关边),O(m),删除一个边,O(1).
顶点用set表示更快。
Adjacent list structure邻近列表
用一个list保存图的顶点V,每个vertex对应一个collection I(v),这个I(v)中保存顶点v的incident edge(无无向图)。传统上I(v)是个list,所以这种表达方式成为adjacent list.
2020.02.27
Adjacent map structure(all-round optimal性能最佳)
用list/dict保存顶点V,每个顶点v对应了一个map/dict,其中的key是与v相连的其他顶点,value是两个顶点相连的边。(we let the opposite endpoint of each incident edge serve as a key in the map, with the edge structure serving as the value.)
{..., v : {u: e, w: g}, ...}
该表达在graph的4种表达方式里全方面性能最优。
adjacent matrix structure(适用于dense graph)
根据顶点个数n生成一个n*n的矩阵M,vertices are indiced from 0 to n-1,矩阵中的M(i, j)点保存的顶点i到顶点j的信息。如果用boolean表达,1则表示两点间有边,否则为0;对有向图,1和-1可用于表达incoming和outgoing。如果非boolean表达,矩阵中的值可保存边的权值。该矩阵是对称阵。对于sparse graph,这种表达过于redundant。该表达适用于dense graph.
复杂度分析对比
(2022.07.01 Fri)
图的实现
这里按adjacent map形式实现图结构。图由类Graph实现,其中的顶点和边分别由类Vertex和类Edge实现。顶点和边类中都加入了__hash__
方法,便于分别做为映射或set的键。图的ADT包含如下方法:
- vertices/vertex_count:图的所有顶点,以及顶点个数
- edges/edge_count:图的所有边,以及边的个数
- get_edge(u, v):根据给定顶点,判断顶点间有无边
- degree(v):计算顶点v的度
- incident_edge(v, outgoing=True):与顶点v相邻的所有边
- insert_vertex(v):插入顶点v
- insert_edge(u, v, x=None):插入由顶点u和v组成的边
class Vertex:
__slots__ = '_element'
def __init__(self, x):
self._element = x
def element(self):
return self._element
def __hash__(self):
return hash(id(self))
class Edge:
__slots__ = '_origin', '_destination', '_element'
def __init__(self, a, b, c):
self._origin = a
self._destination = b
self._element = c
def element(self):
return self._element
def endpoints(self):
return (self._origin, self._destination)
def opposite(self, x):
return self._destination if x is self._origin else self._origin
def __hash__(self):
return hash((self._origin, self._destination))
class Graph:
def __init__(self, directed=False):
self._outgoing = {}
self._incoming = {} if directed else self._outgoing
def is_directed(self):
return self._incoming is not self._outgoing
def vertex_counting(self):
return len(self._outgoing)
def vertices(self):
return self._outgoing.keys()
def edge_counting(self):
total = sum(len(self._outgoing[v] for v in self._outgoing))
return total if self.is_directed() else total // 2
def edges(self):
result = set()
for v in self._outgoing.values():
result.update(v.values())
return result
def get_edge(self, u, v):
return self._outgoing[u].get(v) # returns None is v not adjacent
def degree(self, v, outgoing=True):
adj = self._outgoing if outgoing else self._incoming
return len(adj[v])
def incident_edges(self, v, outgoing=True):
adj = self._outgoing if outgoing else self._incoming
for k in adj[v].values():
yield k
def insert_vertex(self, v, outgoing=True):
v = Vertex(v)
self._outgoing[v] = {}
if self.is_directed():
self._incoming = {}
return v
def insert_edge(self, u, v, x=None):
e = Edge(u, v, x)
self._outgoing[u][v] = e
self._incoming[v][u] = e
return 'done'
Reference:
1 M. Goodrich and etc, Data Structures and Algorithms in Python.