標籤:圖論 python 演算法
仿寫 networkx 的功能
# -*- coding: cp936 -*-''' 簡單圖 Graph:要求: 關於節點: 功能1.add_node: 通過 add_node 一次性加一個節點 字串,數字,任何可以被雜湊的 python 對象都可以當做節點 包括自訂的類型或者另一個圖( 構成超圖 ) 格式: >>> G.add_node( 1 ) 功能2.add_nodes_from: 通過其他容器加入節點,比如list, dict,set,檔案或者另一個圖 格式: >>> G.add_nodes_from( [2, 3] ) >>> H = Graph() >>> H.add_path( range( 10 ) ) >>> G.add_nodes_from( H ) # 構成超圖 關於邊: 功能1.add_edge: 加入一條邊 格式: >>> G.add_edge( 1, 2 ) # 在節點 1 和節點 2 之間加一條邊 功能2.add_edges_from: 加入邊集 格式: >>> G.add_edges_from( [ ( 2, 4 ), ( 1, 3 ) ] ) # 通過邊集來加邊 >>> G.add_edges_from( H.edges() ) # 通過另一個容器來加邊 註: 當加邊操作的兩個節點不存在時,會自動建立出來 關於屬性: 每個圖,邊,節點都可以擁有 '索引值對' 屬性 初始時都為空白,但是允許被動態添加和修改屬性 可以通過 add_node, add_edge 操作,或者直接對節點,邊,圖的字典進行修改 功能1.節點屬性: >>> G = Graph( name = 'scheme' ) >>> G.graph { 'name': 'scheme' } >>> G.add_node( 1, city = 'Nanjing' ) >>> G.nodes[1] { 'city': 'Nanjing' } >>> G.node { 1: {'city': 'Nanjing'} } >>> G.add_node( 1 ) # 但是再加入相同的節點時,屬性不會消失掉 >>> G.node {1: {'city': 'Nanjing'}} >>> G.add_node( 1, nowtime = '8:00' ) # 加入相同的節點,帶上新的屬性時,原來的屬性會被更新 >>> G.node {1: { 'city': 'Nanjing', 'nowtime': '8:00' } } >>> G.add_node( 1, nowtime = '8:10' ) >>> G.node {1: { 'city': 'Nanjing', 'nowtime': '8:10' } } >>> G.nodes[1]['nowtime'] = '10:00' # 但是當節點 1 不存在時,這樣寫屬性需要報錯 >>> del G.nodes[1]['nowtime'] >>> G.add_nodes_from( range( 7, 9 ), city = 'shanghai' ) 註: pass 功能2.邊的屬性: >>> G.add_edge( 1, 2 weight = 100 ) >>> G.edge { 1: { 2: { 'weight': 100 } }, 2: { 1: { 'weight': 100 } } } >>> G.add_edges_from( [ ( 1, 3 ), ( 2, 3 ) ], weight = 111 ) >>> G.edge { 1: { 2: { 'weight': 100 }, 3: { 'weight': 111 } }, 2: { 1: { 'weight': 100 }, 3: { 'weight': 111 } }, 3: { 1: { 'weight': 111 }, 2: { 'weight': 111 } } } >>> G.add_edges_from( [ ( 1, 3, { 'weight': 1 } ), ( 3, 4, { 'weight': 2 } ) ] ) >>> G.edge { 1: { 2: { 'weight': 100 }, 3: { 'weight': 1 } }, 2: { 1: { 'weight': 100 }, 3: { 'weight': 111 } }, 3: { 1: { 'weight': 1 }, 2: { 'weight': 111 }, 4: { 'weight': 2 } }, 4: { 3: { 'weight': 2 } } } >>> G.edge[1][2]['weight'] = 111111 # 允許直接操作 或者 >>> G[1][2]['weight'] = 1111 利用 python 的特性提供快捷操作: 比如: >>> 1 in G True >>> [ n for n in G if n < 3 ] # 節點迭代 [1, 2] >>> len(G) # 節點個數 5 >>> G[1] # 與該點相鄰的所有點的屬性 { 2: { 'weight': 100 }, 3: { 'weight': 1 } } 提供 adjacency_iter 來遍曆邊: >>> for node, nbrsdict in G.adjacency_iter(): for nbr, attr in nbrsdict.items(): if 'weight' in attr: ( node, nbr, attr['weight'] ) (1, 2, 100) (1, 3, 1) (2, 1, 100) (2, 3, 111) (3, 1, 1) (3, 2, 111) (3, 4, 2) (4, 3, 2) >>> [ ( start, end, attr['weight']) for start, end, attr in G.edges( data = True ) if 'weight' in attr ] [(1, 2, 100), (1, 3, 1), (2, 3, 111), (3, 4, 2)] 其他一些功能: pass'''class Graph( object ): def __init__( self, data = None, **attr ): self.graph = {} self.node = {} self.adj = {} if data is not None: pass self.graph.update( attr ) self.edge = self.adj @property def name( self ): return self.graph.get( 'name', '' ) @name.setter def name( self, newname ): self.graph['name'] = newname def __str__( self ): return self.name def __iter__( self ): return iter( self.node ) def __contains__( self, node ): try: return node in self.node except TypeError: return False def __len__( self ): return len( self.node ) def __getitem__( self, node ): return self.adj[node] def add_node( self, node, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) if node not in self.node: self.adj[node] = {} self.node[node] = attr_dict else: self.node[node].update( attr_dict ) def add_nodes_from( self, nodes, **attr ): for node in nodes: try: newnode = node not in self.node except TypeError: node_, node_dict = node if node_ not in self.node: self.adj[node_] = {} newdict = attr.copy() newdict.update( node_dict ) self.node[node_] = newdict else: olddict = self.node[node_] olddict.update( attr ) olddict.update( node_dict ) continue if newnode: self.adj[node] = {} self.node[node] = attr.copy() else: self.node[node].update( attr ) def remove_node( self, start ): try: nbrs = list( adj[start].keys() ) del self.node[start] except KeyError: raise Exception( "The node %s is not in the graph."%( start ) ) for end in nbrs: del self.adj[start][end] del self.adj[start] def remove_nodes_from( self, nodes ): for start in nodes: try: del self.node[start] for end in list( self.adj[start].keys() ): del self.adj[end][start] del self.adj[start] except KeyError: pass def nodes( self, show_info = False ): def nodes_iter( show_info = False ): if show_info: return iter( self.node.item() ) return iter( self.node ) return list( nodes_iter( show_info = show_info ) ) def number_of_nodes( self ): return len( self.node ) def order( self ): return len( self.node ) def has_node( self, node ): try: return node in self.node except TypeError: return False def add_edge( self, start, end, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) if start not in self.node: self.adj[start] = {} self.node[start] = {} if end not in self.node: self.adj[end] = {} self.node[end] = {} data_dict = self.adj[start].get( end, {} ) data_dict.update( attr_dict ) self.adj[start][end] = data_dict self.adj[end][start] = data_dict def add_edges_from( self, edges, attr_dict = None, **attr ): if attr_dict is None: attr_dict = attr else: try: attr_dict.update( attr ) except AttributeError: raise Exception( "The attr_dict argument must be a dictionary." ) for edge in edges: elem_num = len( edge ) if elem_num == 3: start, end, start_end_dict = edge elif elem_num == 2: start, end = edge else: raise Exception( "Edge tuple %s must be a 2-tuple or 3-tuple."%( edge ) ) attr_dict.update( start_end_dict ) self.add_edge( start, end, attr_dict ) def remove_edge( self, start, end ): try: del self.adj[start][end] if start != end: del adj[end][start] except KeyError: raise Exception( "The edge %s-%s is not in the graph"%( start,end ) ) def remove_edges_from( self, edges ): for edge in edges: start, end = edge[:2] if start in self.adj and end in self.adj[start]: del self.adj[start][end] if start != end: del self.adj[end][start] def has_edge( self, start, end ): try: return end in self.adj[start] except KeyError: return False def neighbors( self, node ): try: return list( self.adj[node] ) except KeyError: raise Exception( "The node %s is not in the graph."%( node,) ) def neighbors_iter( self, node ): try: return iter( self.adj[n] ) except KeyError: raise Exception( "The node %s is not in the graph."%( node,) ) def edges( self, nodes = None, show_info = False ): def edges_iter( nodes, show_info = False ): seen = {} if nodes is None: nodes_nbrs = self.adj.items() else: nodes_nbrs = ( ( node, self.adj[node] ) for node in self.nodes_container( nodes ) ) if show_info: for node, nbrs in nodes_nbrs: for nbr, data in nbrs.items(): if nbr not in seen: yield ( node, nbr, data ) seen[node] = 1 else: for node, nbrs in nodes_nbrs: for nbr, data in nbrs.items(): if nbr not in seen: yield ( node, nbr ) seen[node] = 1 del seen return list( edges_iter( nodes, show_info = show_info ) ) def get_edge_data( self, start, end, default = None ): try: return self.adj[start][end] except KeyError: return default def adjacency_list( self ): return list( map( list, iter( self.adj.values() ) ) ) def adjacency_iter( self ): return iter( self.adj.items() ) def degree( self, nodes_container = None, weight = None ): ''' 功能: 返回一個節點或者一系列節點的度( degree ) 參數: nodes_container: 可以被迭代的容器,可選,預設值為所有節點 weight: 字串或者為空白,可選,預設為空白, 若是為None,則所有的邊的權重都設為 1, degree 則是所有與節點相鄰的 weight 權重的邊的個數之和 傳回值: 字典或者數字 字典: 一系列節點與它們的索引值對 數字: 一個指定節點的度 例如: >>> G = nx.Graph() # or DiGraph, MultiGraph, MultiDiGraph, etc >>> G.add_path( [ 0, 1, 2, 3 ] ) >>> G.degree(0) 1 >>> G.degree( [ 0, 1 ] ) { 0: 1, 1: 2 } >>> list( G.degree( [ 0, 1 ] ).values() ) [ 1, 2 ] ''' if nodes_container in self: return next( self.degree_iter( nodes_container, weight ) )[1] else: return dict( self.degree_iter( nodes_container, weight ) ) def degree_iter( self, nodes_container = None, weight = None ): ''' 功能: 返回一個( node, degree ) 的迭代對象 參數: nodes_container: 可以被迭代的容器,可選,預設值為所有節點 weight: 字串或者為空白,可選,預設為空白, 若是為None,則所有的邊的權重都設為 1, degree 則是所有與節點相鄰的 weight 權重的邊的個數之和 傳回值: 返回一個( node, degree ) 的迭代對象 ''' if nodes_container is None: nodes_nbrs = self.adj.items() else: nodes_nbrs = ( ( node, self.adj[node] ) for node in self.nodes_container_iter( nodes_container ) ) if weight is None: for node, nbrs in nodes_nbrs: yield ( node, len( nbrs ) + ( node in nbrs ) ) else: for node, nbrs in nodes_nbrs: yield (node, sum( ( nbrs[nbr].get( weight, 1 ) for nbr in nbrs ) ) + ( node in nbrs and nbrs[node].get( weight, 1 ) ) ) def clear( self ): self.name = '' self.adj.clear() self.node.clear() self.graph.clear() def copy( self ): from copy import deepcopy return deepcopy( self ) def is_multigraph( self ): return False def is_directed( self ): return False def subgraph( self, nodes ): from copy import deepcopy nodes = self.nodes_container_iter( nodes ) H = self.__class__() H.graph = deepcopy( self.graph ) for node in nodes: H.node[node] = deepcopy( self.node[node] ) for node in H.node: H_nbrs = {} H.adj[node] = H_nbrs for nbr, attr in self.adj[node].items(): if nbr in H.adj: H_nbrs[nbr] = attr H.adj[nbr][node] = attr return H def nodes_with_selfloops( self ): return [ node for node, nbrs in self.adj.items() if node in nbrs ] def selfloop_edges( self, show_info = False ): if show_info: return [ ( node, node, nbrs[node] ) for node, nbrs in self.adj.items() if node in nbrs ] else: return [ ( node, node ) for node, nbrs in self.adj.items() if node in nbrs ] def number_of_selfloops( self ): return len( self.selfloop_edges() ) def size( self, weight = None ): s = sum( self.degree( weight = weight ).values() ) / 2 if weight is None: return int( s ) else: return float( s ) def number_of_edges( self, start = None, end = None ): if start is None: return int( self.size() ) if end in self.adj[start]: return 1 else: return 0 def add_path( self, nodes, **attr ): node_list = list( nodes ) edges = zip( node_list[:-1], node_list[1:] ) self.add_edges_from( edges, **attr ) def add_cycle( self, nodes, **attr ): node_list = list( nodes ) edges = zip( node_list, node_list[1:] + [node_list[0]] ) self.add_edges_from( edges, **attr ) def nodes_container_iter( self, nodes_container = None ): ''' 功能: 返回存在圖中且在 nodes_container 中的節點的迭代對象 參數: nodes_container: 可以被迭代的容器,可選,預設值為所有節點 傳回值: nodes_iter: iterator ''' if nodes_container is None: container = iter( self.adj.keys() ) elif nodes_container in self: container = iter( [nodes_container] ) else: def container_iter( node_list, adj ): try: for node in node_list: if node in adj: yield node except TypeError as e: message = e.args[0] import sys sys.stdout.write( message ) if 'iter' in message: raise Exception( "nodes_container is not a node or a sequence of nodes." ) elif 'hashable' in message: raise Exception( "Node %s in the sequence nbunch is not a valid node."%n ) else: raise container = container_iter( nodes_container, self.adj ) return container