Skip to content

Networkx

Starslayerx edited this page May 7, 2021 · 40 revisions

green-pi
networkx
使用 NetworkX,您可以以标准和非标准数据格式加载和存储网络,生成多种类型的随机和经典网络,分析网络结构,构建网络模型,设计新的网络算法,绘制网络等等。

import networkx as nx

图论矩阵

  • 关联矩阵
    表示了每个节点和每条边之间的关系
    associated

  • 邻接矩阵
    邻接矩阵表示了两个节点之间的关系
    neighbor

  • 距离矩阵
    该矩阵的i行j列为节点i和j之间的距离

  • 导出矩阵

    nx.to_numpy_matrix(G) # 导出为numpy矩阵
    nx.to_scipy_sparse_matrix(G) # 导出为稀疏矩阵

建图

  • 无向图

    G = nx.Graph()
    G = nx.Graph(A) # 使用邻接矩阵A创建图G
  • 有向图

    D = nx.DiGraph()
    D = nx.DiGraph(A) # 使用邻接矩阵A创建图D
    D = nx.DiGraph({0: [3], 1: [2], 2: [3], 3: [0, 1]}) # 同时指定方向
  • 可重复边的的无向图

    M = nx.MultiGraph()
  • 可重复边的有向图

    M = MultiDigraph()
  • 创建完全图

    nx.complete_graph(n, create_using=None)

    n: 如果n为一个数,节点为0~(n-1),或者使用元祖指定节点
    create_using: 图的类型,例如无向图和有向图

  • 添加边

    G.add_edge('Alice', 'Bob')
  • 通过字典或列表,添加多条边

    drive_times  = {('Albany', 'Boston'): 3,
                    ('Albany', 'NYC'): 4,
                    ('Boston', 'NYC'): 4,
                    ('NYC', 'Philly'): 2}
    G.add_edges_from(drive_times)
  • 添加有权边

    G.add_weighted_edges_from(ebunch_to_add, weight='weight', **attr)
    • 传入元祖构成的列表,例如[(0, 1, 3.0), (1, 2, 7.5)]
  • 添加节点

    G.add_node('Alice')
  • 通过字典或列表,添加多个节点

    positions = dict(Albang = (-74, 43),
                     Boston = (-71, 42),
                     NYC = (-74, 41),
                     Philly = (-75, 40))
    G = nx.Graph()
    G.add_nodes_from(positions)

  • 图的节点

    list(G.nodes())
  • 图的边

    list(G.edges())
  • 边的权值

    nx.get_edge_attributes(G, name)

    name: 边属性的名字,例如'weight'权重,'color'颜色

节点

  • 节点度数

    nx.degree(G, nbunch=None, weight=None)

    返回{节点:度数}字典

  • 图中的所有节点

    nx.nodes(G)

    使用方法: list(nx.nodes(G))

  • 节点数量

    nx.number_of_nodes(G)
  • 相邻节点

    nx.neighbors(G, n)

    使用方法: list(nx.neighbors(G, 2)) 或 [n for n in G.neighbors(node)]

  • 不相邻节点

    nx.non_neighbors(graph, node)
  • 两个节点相同的节点

    nx.common_neighbors(G, u, v)

    返回 list(nx.common_neighbors(G, 1, 3))

  • 节点度数频度视图

    nx.degree_histogram(G)

    返回从0开始到最大度数列表

  • 边的视图

    nx.edges(G, nbunch=None)

    返回对应的边的列表,用两个节点表示一条边 [(node_i, node_j)]

  • 边的数量

    nx.number_of_edges(G)
  • 图的密度

    nx.density(G)

    density

绘图

这里是官网绘图文档

  • 基础绘图

    nx.draw(G, pos=None, ax=None, **kwds)
    • pos: key为node,value为元祖坐标的字典 或 预定的布局
    • ax: 可在某个轴上绘制该图(matplotlib) 还可以使用with_labels等参数
  • 绘制边和节点的标签

    def draw_with_labels(G, pos=nx.random_layout(G)):
      nx.draw(G,pos)
      nx.draw(G, pos, with_labels=True)
      labels = nx.get_edge_attributes(G,'weight')
      nx.draw_networkx_edge_labels(G,pos,edge_labels=labels)
      plt.show()
  • 详细绘图

    nx.draw_networkx(G, pos=None, arrows=True, with_labels=True, **kwds)

    arrows: 对于有向图是否画箭头 arrowstyle: 箭头样式,默认’-|>’.详见matplotlib.patches.ArrowStyle
    arrowsize: 箭头大小,默认10.
    with_labels: 是否显示节点标签
    ax: 在matplotlib的某个axes对象上绘图
    nodelist: 只画出指定的几个节点
    edgelist: 只画出指定的几条边
    node_size: 节点大小,默认300
    node_color: 节点颜色,默认’#1f78b4’
    node_shape: 节点形状,默认'o',可选'so^>v<dph8’
    alpha: 节点透明度
    cmap: Matplotlib colormap, optional (default=None)
    edge_color: 边的颜色
    style: 边的样式,默认’solid’,可选(solid|dashed|dotted,dashdot)
    font_size: 标签字体大小
    font_color: 字体颜色
    font_weight: 字体宽度
    font_family: 字体样式
    label: 图例标签

  • 只绘制节点

    nx.draw_networkx_nodes(G, pos, nodelist=None, node_size=300, node_color='#1f78b4', node_shape='o', alpha=None, cmap=None, vmin=None, vmax=None, ax=None, linewidths=None, edgecolors=None, label=None)

    返回值:matplotlib.collections.PathCollection类型的节点

  • 只绘制边

    draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=None, arrowstyle='-|>', arrowsize=10, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, node_size=300, nodelist=None, node_shape='o', connectionstyle=None, min_source_margin=0, min_target_margin=0)

    返回值:matplotlib.collection.LineCollection类型的边

  • 绘制节点标签

    draw_networkx_labels(G, pos, labels=None, font_size=12, font_color='k', font_family='sans-serif', font_weight='normal', alpha=None, bbox=None, horizontalalignment='center', verticalalignment='center', ax=None)

    返回字典类型

  • 绘制边标签

    draw_networkx_edge_labels(G, pos, edge_labels=None, label_pos=0.5, font_size=10, font_color='k', font_family='sans-serif', font_weight='normal', alpha=None, bbox=None, horizontalalignment='center', verticalalignment='center', ax=None, rotate=True)
    • edge_labels: 可以使其等于nx.get_edge_attributes(G, 'weight')将边权重作为标签信息绘制 或者可以使用字典 返回字典类型
  • 下面几种绘图方式是直接使用某种类型绘图

    # 节点在圆环均匀分布绘图
    nx.draw_circular(G, node_color='b', node_size=2000, with_labels=True)
    
    # Draw the graph G with a Kamada-Kawai force-directed layout.
    draw_kamada_kawai(G, **kwargs)
    
    # Draw a planar networkx graph with planar layout.
    draw_planar(G, **kwargs)
    
    # Draw the graph G with a random layout.
    draw_random(G, **kwargs)
    
    # Draw the graph G with a spectral 2D layout.
    draw_spectral(G, **kwargs)
    
    # Draw the graph G with a spring layout.
    draw_spring(G, **kwargs)
    
    # Draw networkx graph with shell layout.
    draw_shell(G, **kwargs)
  • 绘图布局
    绘图函数有pos参数,可以使用字典自行规定每个节点的坐标,或者使用预制的layout

    • circular_layout: 顶点在圆环上均匀分布
    • random_layout: 顶点随机分布
    • shell_layout: 顶点在同心圆上分布
    • spring_layout: 用Fruchterman-Reingold算法排列顶点
    • spectral_layout: 根据图的Laplace特征向量排列顶点

特殊图

连通图

  • 随机图
    当图的节点联通率为p > p*=(ln n)/n时,图很有可能为连通的,在p*位置,图的连通率会发生突变

  • 构造完全图(每两个节点之间相互相连)

    def all_pairs(nodes):
      for i, u in enumerate(nodes):
          for j, v in enumerate(nodes):
              if i>j:
                  yield u,v
                  
    def make_complete_graph(n):
      G = nx.Graph()
      nodes = range(n)
      G.add_nodes_from(nodes)
      G.add_edges_from(all_pairs(nodes))
      return G
  • 连通图(每两个节点都有路径相连)
    该函数接受一个图和一个起始节点start,从起始节点开始,返回可以到达的节点集

    def reachable_nodes(G, start):
      seen = set()
      stack = [start]
      while stack:
          node = stack.pop()
          if node not in seen:
              seen.add(node)
              stack.extend(G.neighbors(node))
      return seen

    该函数判断图G是否连通

    def is_connected(G):
      start = next(iter(G))
      reachable = reachable_nodes(G, start)
      return len(reachable) == len(G)
  • 将图以dot格式写入文件

    nx.write_dot(G, path)

    nx.write_dot(G,'a.dot')
    !neato -T png a.dot > a.png

ER图

ER图有两个特点,n为节点数目,p为两个节点间存在一条边的概率

  • 下面的生成器枚举了所有的边,并选择哪些便应该添加到图中

     def flip(p):
         return np.random.random() < p; # 返回0-1随机分布数字
    
     def random_pairs(nodes, p):
         for edge in all_pairs(nodes):
             if flip(p):
                 yield edge
  • 生成并返回ER图

    def make_random_graph(n, p):
        G = nx.Graph()
        nodes = range(n)
        G.add_nodes_from(nodes)
        G.add_edges_from(random_pairs(nodes, p))
        return G
  • 多次跌代计算图的连通率

    def prob_connected(n, p, iters=100):
        tf = [is_connected(make_random_graph(n, p))
              for i in range(iters)]
        return np.mean(tf)

算法

最短路径

迪杰斯特拉算法 Dijkstra's algorithm

dijkstra算法用于求一个节点到其余节点的最短路径,且边权要为正数

  • 初始化:初始节点距离为0,其余为正无穷,标记起始节点
  • 选择最短路径节点:选择距离出发节点最近的节点,标记,收录到最优路径中(第一次为开始节点)
  • 更新最短路:添加相邻节点,根据目前点的最短路,加上相邻节点的边的权重,若得到的距离小于目前该节点的距离,则更新该节点的距离,并标记其后面节点为当前节点 不断重复后面两步,直到要求的节点被收录到最优路径里为止

贝尔曼-福特算法 Bellman–Ford algorithm

贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。

  1. 求两个节点之间的最短路径
nx.shortest_path(G, source=None, target=None, weight=None, method='dijkstra')
  • weight: (weight/distance/cost)
  • method: 采用算法(‘dijkstra’, ‘bellman-ford’)

返回值:最短路径节点组成的列表或字典

  • 如果只传入source开始节点,返回由目标节点为key的最短路径集合(只传入target目标节点同理)
  • 如果两个节点都没传入,则返回一个嵌套字典{开始节点:{结束节点:[路径]}}
  1. 求两个节点之间的全部最短路径
nx.all_shortest_paths(G, source, target, weight=None, method='dijkstra')

返回值:返回一个含有所有最短路径的生成器

  • 使用方法: print( p for p in nx.all_shortest_paths(G, source, target, weight=None, method='dijkstra') )
  1. 计算最短路径长度
 nx.shortest_path_length(G, source=None, target=None, weight=None, method='dijkstra')

返回值:

  • 若只指定了source和target中的一个,则返回字典类型(同上)
  • 若两个值都未指定,则返回一个迭代器(source, dictionary)
  • 使用方法: p = dict( nx.shortest_path_length(G, source=None, target=None, weight='weight', method='dijkstra') )
  1. 平均最短路径长度
nx.average_shortest_path_length(G, weight=None, method=None)

average_shortest_path

  1. 判断两节点之间是否存在通路
nx.has_path(G, source, target)

返回布尔值

  • 最短路应用范例(设备更新)

    年份 1 2 3 4
    当年购买价格/万元 25 26 28 31
    使用年份费用/万元 10 14 18 26
    当年末出售价格/万元 20 16 13 11

    计算后得到以下表: 从i年初到j年末费用

    i \ j 1 2 3 4 5
    1 0 15 33 54 82
    2 0 0 16 34 55
    3 0 0 0 18 36
    4 0 0 0 0 21
    5 0 0 0 0 0

    我们可以构造以下有向图

    import numpy as np
    
    M = np.zeros((5,5))
    buy = np.array([25, 26, 28, 31])
    repail = np.array([10, 14, 18, 26])
    sell = np.array([20, 16, 13, 11])
    
    spent_year = list()
    def years(repail, sell):
        for i in range(len(repail)):
            spent_year.append(np.sum(repail[:i+1]) - sell[i])
        return spent_year
    
    spent_year = years(repail, sell)
    
    for i in range(len(buy)):
        for j in range(len(buy) + 1):
            if i < j:
                M[i, j] = buy[i] + spent_year[j-i-1]
    
    path = nx.shortest_path(D, source=0, target=4, weight='weight', method='dijkstra')
    print('Least fee year:', [i+1 for i in list(path)])
    
    fee = nx.shortest_path_length(D, 0, 4, weight='weight')
    print('最小费用:', fee)
    import matplotlib.pyplot as plt
    %matplotlib inline
    import networkx as nx
    D = nx.DiGraph(M)
    def draw_with_labels(D, pos=nx.shell_layout(D)):
        nx.draw(D,pos)
        nx.draw(D, pos, with_labels=True)
        labels = nx.get_edge_attributes(D,'weight')
        nx.draw_networkx_edge_labels(D,pos,edge_labels=labels)
        plt.show()
    draw_with_labels(D)

最小生成树🌳

最小生成树可以用来解决最短路程类问题,给定地点,要求每个点间可达,求最短路程

  • kruskal算法
    该算法就是将节点间的距离排序,依次选取最短的边,并且保证不构成回路,否则不选择该点,最后得到n-1条边后就的到了最小生成数
nx.minimum_spanning_tree(G, weight='weight', algorithm='kruskal', ignore_nan=False)

min_tree1
实例代码

G = nx.Graph()
pos= dict(v1 = (10, 20),
          v2 = (10, 10),
          v3 = (90, 10),
          v5 = (90, 20),
          v4 = (130, 10))
G.add_nodes_from(pos)
G.add_weighted_edges_from([('v1', 'v2', 8), 
                           ('v1', 'v5', 2), 
                           ('v1', 'v3', 4), 
                           ('v5', 'v3', 1), 
                           ('v2', 'v3', 4), 
                           ('v5', 'v4', 5), 
                           ('v3', 'v4', 2)])
nx.draw(G, pos, with_labels=True)

weight = nx.get_edge_attributes(G, 'weight')
nx.draw_networkx_edge_labels(G, pos, edge_labels=weight)
plt.show()

# 最小生成树
T = nx.minimum_spanning_tree(G)
nx.draw(T, pos = pos, with_labels=True)
weight2 = nx.get_edge_attributes(T, 'weight')
nx.draw_networkx_edge_labels(T, pos, edge_labels=weight2)
plt.show()

min_tree2

欧拉图

概念 解释
欧拉通路 通过图中每条边且只通过一次,并且经过每一顶点的通路
欧拉环游 图的环游是指经过图的每条边至少一次的闭途径
欧拉回路 通过图中每条边且只通过一次,并且经过每一顶点的回路
半欧拉图 具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图,有且仅有两个度数为奇数的结点
欧拉图 具有欧拉回路的图
  • 欧拉定理
    一个非空连通图是欧拉图当且仅当它的每个顶点的度数都是偶数 (简单说欧拉通路就是首尾不相接,而欧拉回路要求首尾相接,欧拉回路就是一笔画,有欧拉回路的图就是欧拉图)

  • 推论

    1. 无向连通图 G 是欧拉图,当且仅当 G 不含奇数度结点
    2. 无向连通图 G 含有欧拉通路,当且仅当 G 有零个或两个奇数度的结点
    3. 有向连通图 D 是欧拉图,当且仅当该图 D 中每个结点的入度=出度
  • 判断是否为欧拉图

    nx.is_eulerian(G)
  • 欧拉回路

    nx.eulerian_circuit(G, source=None, keys=False)

    返回一个构成欧拉回路的边边组成的迭代器 list(nx.eulerian_circuit(G))

  • 欧拉化
    将一个无向图转化为欧拉图(就是通过复制产生重复边,使其奇度定点全部为偶度顶点,此外还要使添加的重复边尽量少)

    nx.eulerize(G)

    返回一个可重复边类型的图MultiGraph

二分图(二部图)

简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
bipartite

  from networkx.algorithms import bipartite
  • 创建二部图

    B = nx.Graph()
    # 使用属性 "bipartite" 建立二部图
    B.add_nodes_from([1, 2, 3, 4], bipartite=0)
    B.add_nodes_from(["a", "b", "c"], bipartite=1)
    # 边只能在两个不同集合之间
    B.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")])
  • 访问二部图节点集合

    bottom_nodes, top_nodes = bipartite.sets(B)
  • 判断是否为二部图

    nx.is_bipartite(G)
  • 二部图的最大(基数)匹配

    nx.maximum_matching(G, top_nodes=None)

匹配 Matching

匹配指这个图之中,任意两条边都没有公共的顶点
pipei

  • 最大基数匹配:指求出图中的一个matching,使得matching包含的边数最多。

  • 最大权重匹配

    # 这个函数是近似算法
    nx.max_weight_matching(G, maxcardinality=False, weight='weight')

    若要求最小权重匹配,将权值取负,然后求最大匹配就可以了

  • 完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。

匈牙利算法

匈牙利算法用于解决最小化指派问题: 将i个人分配j个工作,每个人由于技能的不同,完成工作的时间不同,求最好的分配方式。
这里使用效率矩阵C(i,j)表示i人做工作j的时间(所有元素都要大于等于0)
可以转化为规划问题:

min = C(i,j)x(i,j)
sum(x(i,j), j) = 1
sum(x(i,j), i) = 1
x(i,j) = 0 / 1

  • 第一步: 得到效率矩阵C,将矩阵每一行或列减去该行最小的数,将其变化成了每行都含0的矩阵C1
  • 第二步: 用圈零法求新矩阵C1中独立零元素
    1. 进行行检验: 如果该行只有一个0元素,将该元素圈起来,并将该元素同一列的元素删掉(打叉) 重复行检验,直到每一行都没有未标记过的0元素,或小于等于两个为标记的0元素为止
    2. 进行列检验: 类似以上步骤
      现在有三种情况:
  1. 独立0元素数量等于矩阵阶,此时每个人都可以直接分配工作
  2. 存在未标记的0元素,例如存在两个,那么表明有两种情况,随便选取一个代表一种情况
  3. 不存在未标记的0元素,但是圈0的数量 < 矩阵的阶
  • 第三步: 进行试指派 若为前两种情况,则可以进行指派
    若为第三种情况,则进行下一步
  • 第四步: 做最小直线覆盖当前所有0元素
    1. 将矩阵中所有不含圈0元素的行打勾
    2. 对打勾的行中的所有0元素的列打勾
    3. 对于所有打勾列中圈0元素所在列打勾
    4. 重复2~3直到不能进一步打勾为止
    5. 对没有打勾的行画横线,对已经打勾的列画一条竖线 在未被直线覆盖的元素中挑出最小的元素,将打勾行的每个元素减去这个最小元素,对打勾列的每个元素加上这个最小元素,这样就将0元素数量增加了。然后再重复前面过程即可。

若要求最大指派问题,可以转化矩阵b(i,j) = M - c(i,j),其中M为矩阵c中最大元素,任何求b矩阵的最小化问题

竞赛图 tournament graph

竞赛图是通过在无向完整图中为每个边缘分配方向而获得的有向图。
tournament

  • 判断是否为竞赛图
    nx.is_tournament(G)

哈密顿图 hamiltonian graph

哈密顿图是一个无向图,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。在图论中是指含有哈密顿回路的图,闭合的哈密顿路径称作哈密顿回路(Hamiltonian cycle),含有图中所有顶点的路径称作哈密顿路径.

  • 返回竞赛图的哈密顿图
    nx.hamiltonian_path(G)

TSP 旅行商问题

旅行商问题是这样一个问题:给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。

  • 克里斯托菲德斯算法
    克里斯托菲德斯算法 (Christofides algorithm) 是旅行商问题在度量空间(即距离对称且满足三角不等式)上的一个近似算法。
    该算法可以保证相对最优哈密尔顿回路长度有3/2的近似比。截至2017年,这一算法仍然是一般性旅行商问题的算法中近似比最好的结果。 算法步骤:
  1. 构造图上的最小的生成树T(图为完全图且满足三角不等式)
  2. 令O为原图顶点集中在T上度数为奇数的顶点集。根据握手定理O中有偶数个顶点
  3. 构造点集O在原完全图上的最小完美匹配M
  4. 将 M 和 T 的边集取并,构造重图 H ,满足其每个顶点都是偶数度的
  5. H 可以形成一个欧拉回路
  6. 将前一步得到的图改造为一个哈密尔顿回路:只需要跳过前一步欧拉回路中重复的顶点即可

下面使用google or-tools解决该问题

"""Simple traveling salesman problem between cities."""

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp


def create_data_model():
    """为该问题创建数据"""
    data = {}
    data['distance_matrix'] = [
     [0, 7, 4, 5, 8, 6, 12, 13, 11, 18],
     [7, 0, 3, 10, 9, 14, 5, 14, 17, 17],
     [4, 3, 0, 5, 9, 10, 21, 8, 27, 12],
     [5, 10, 5, 0, 14, 9, 10, 9, 23, 16],
     [8, 9, 9, 14, 0, 7, 8, 7, 20, 19],
     [6, 14, 10, 9, 7, 0, 13, 5, 25, 13],
     [12, 5, 21, 10, 8, 13, 0, 23, 21, 18],
     [13, 14, 8, 9, 7, 5, 23, 0, 18, 12],
     [11, 17, 27, 23, 20, 25, 21, 18, 0, 16],
     [18, 17, 12, 16, 19, 13, 18, 12, 16, 0]
]    # 距离矩阵
    data['num_vehicles'] = 1 # 汽车(销售员)数量
    data['depot'] = 0 # 开始结束节点
    return data

""" 打印结果 """
def print_solution(manager, routing, solution):
    """Prints solution on console."""
    print('Objective: {} miles'.format(solution.ObjectiveValue()))
    index = routing.Start(0)
    plan_output = 'Route for vehicle 0:\n'
    route_distance = 0
    while not routing.IsEnd(index):
        plan_output += ' {} ->'.format(manager.IndexToNode(index))
        previous_index = index
        index = solution.Value(routing.NextVar(index))
        route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
    plan_output += ' {}\n'.format(manager.IndexToNode(index))
    print(plan_output)
    plan_output += 'Route distance: {}miles\n'.format(route_distance)


def main():
    """Entry point of the program."""
    # Instantiate the data problem.
    data = create_data_model()

    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])

    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)


    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(distance_callback)

    # Define cost of each arc.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

    # Setting first solution heuristic.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.CHRISTOFIDES)

    # Solve the problem.
    solution = routing.SolveWithParameters(search_parameters)

    # Print solution on console.
    if solution:
        print_solution(manager, routing, solution)


if __name__ == '__main__':
    main()

若要采用其他算法,可以修改FirstSolutionStrategy

扩展:

中国邮递员问题

  • 若图中有欧拉回路,因为欧拉回路通过所有的边,因此任何一个欧拉回路即为此问题的解。
  • 若图中不存在欧拉回路,其中必存在有奇数个边的端点,且这类的端点一定大于等于2个。此时将图欧拉化,然后再求欧拉回路。 若为有权图,则每两个节点之间权为两个节点间的最短路径。