文章目录

  • 概念、性质和实现
  • 定义
  • 完全图
  • 顶点数、边数和顶点度数的关系
  • 路径
  • 连通图
  • 带权图和网络
  • 邻接矩阵表示
  • 邻接表表示
  • 邻接多重表表示
  • 图的十字链表表示
  • 图结构的python实现
  • 1.邻接矩阵表示
  • 2.邻接表表示
  • 基本图算法
  • 图的遍历和生成树
  • 深度优先遍历
  • 宽度优先遍历
  • 深度优先遍历的非递归算法
  • 生成树
  • 7.4 最小生成树
  • 7.5 最短路径
  • 7.6 AOV/AEV网及其算法
  • AOV网、拓扑排序和拓扑序列
  • 工程和工作安排
  • 拓扑排序和拓扑序列
  • 拓扑排序算法
  • 实现技术和函数定义
  • AOE网和关键路径
  • 关键路径算法
  • 变量定义
  • 算法


概念、性质和实现

定义

一个图是一个二元组python设置宽度为30右对齐_邻接矩阵,其中:
python设置宽度为30右对齐_邻接矩阵_02是非空又穷的顶点集合;
python设置宽度为30右对齐_完全图_03是顶顶啊偶对(称为边)的集合;
python设置宽度为30右对齐_邻接矩阵_02中的顶点也称为图python设置宽度为30右对齐_python设置宽度为30右对齐_05的顶点,python设置宽度为30右对齐_完全图_03中的边也称为图python设置宽度为30右对齐_python设置宽度为30右对齐_05的边。
图分为有向图和无向图两类。

完全图

完全图:任意两个顶点之间都要边的图,称为完全图。
python设置宽度为30右对齐_Graph_08个顶点的无向完全图有python设置宽度为30右对齐_邻接矩阵_09条边;
python设置宽度为30右对齐_Graph_08个顶点的有向完全图有python设置宽度为30右对齐_邻接矩阵_11条边。

一个顶点的度就是与它邻接的边的条数,对于有向图,顶点的度分为入度和出度。

顶点数、边数和顶点度数的关系

顶点数python设置宽度为30右对齐_Graph_08,边数python设置宽度为30右对齐_邻接矩阵_13和顶点度数:
python设置宽度为30右对齐_完全图_14
其中python设置宽度为30右对齐_完全图_15表示顶点python设置宽度为30右对齐_python设置宽度为30右对齐_16的度数。

路径

路径的长度就是该路径上的边的条数;
回路(环)指起点和终点相同的路径;
如果一个环路除起点和终点外的其他顶点均不相同,则称为简单回路;
简单路径是内部不包含回路的路径,也就是,该路径上的顶点除了起点和终点可能相同外,其他顶点均不相同。

有根图:如果在有向图python设置宽度为30右对齐_python设置宽度为30右对齐_05里存在一个顶点python设置宽度为30右对齐_邻接矩阵_18,从顶点python设置宽度为30右对齐_邻接矩阵_18到图python设置宽度为30右对齐_python设置宽度为30右对齐_05中其他每个顶点均有路径,则称python设置宽度为30右对齐_python设置宽度为30右对齐_05为有根图,顶点python设置宽度为30右对齐_邻接矩阵_18为图python设置宽度为30右对齐_python设置宽度为30右对齐_05的一个根。

连通图

连通:python设置宽度为30右对齐_python设置宽度为30右对齐_16python设置宽度为30右对齐_邻接矩阵_25存在路径,则这两个顶点连通。
连通无向图
强连通有向图

带权图和网络

python设置宽度为30右对齐_python设置宽度为30右对齐_05中的每条边都被赋予一个权值,则称图python设置宽度为30右对齐_python设置宽度为30右对齐_05为一个带权图。
带权的连通无向图称为网络。

一个图中一些有用的操作:
ADT Graph:     # 一个图抽象数据类型
 Graph(self)    # 图构造操作,创建一个新图
 is_empty(self)   # 判断是否为一个空图
 vertex_num(self)   # 获得这个图中的顶点个数
 edge_num(self)   # 获得这个图中边的条数
 vertices(self)   # 获得这个图中的顶点集合
 edges(self)    # 获得这个图中的边集合
 add_vertex(self, vertex) # 将顶点vertex加入这个图
 add_edge(self, v1, v2) # 将从v1到v2的边加入这个图
 get_edge(self, v1, v2) # 获得v1到v2边有关的信息,没有时返回特殊值
 out_edges(self, v)  # 取得从v出发的所有边
 degree(self, v)   # 检查v的度

邻接矩阵表示
邻接表表示
邻接多重表表示
图的十字链表表示

图结构的python实现

1.邻接矩阵表示
inf = float("inf") # inf的值大于任何float类型的值
class GraphError(ValueError):
    pass
class Graph:  # 基本图类,采用邻接矩阵表示
    def __init__(self, mat, unconn=0):
        vnum = len(mat)
        for x in mat:
            if len(x) != vnum:  # 检查是否为方阵
                raise ValueError("Argument for 'Graph'.")
        self._mat = [mat[i][:] for i in range(vnum)]    # 做拷贝
        self._unconn = unconn
        self._vnum = vnum
    
    def vertex_num(self):
        """顶点数目"""
        return self._vnum

    def _invalid(self, v):
        """无效顶点"""
        return 0 > v or v >= self._vnum
    
    def add_vertex(self):
        """增加顶点的函数"""
        raise GeneratorExit("Adj-Matrix does not support 'add_vertex'.")

    def add_edge(self, vi, vj, val=1):
        """添加vi到vj的边"""
        if self._invalid(vi) or self._invalid(vj):
            raise GeneratorExit(str(vi) + ' or ' + str(vj) + "is not a valid vertex.")
        self._mat[vi][vj] = val
    
    def get_edge(self, vi, vj):
        """获取vi到vj的边"""
        if self._invalid(vi) or self._invalid(vj):
            raise GeneratorExit(str(vi) + ' or ' + str(vj) + "is not a valid vertex.")
        return self._mat[vi][vj]


    def out_edges(self, vi):
        """获取从vi出发的边"""
        if self._invalid(vi):
            raise GeneratorExit(str(vi) + "is not a valid vertex.")
        return self._out_edges(self._mat[vi], self._unconn)

    @staticmethod
    def _out_edges(row, unconn):
        edges = []
        for i in range(len(row)):
            if row[i] != unconn:
                edges.append((i, row[i]))
        return edges

    def __str__(self):
        """为这个类的对象提供一种转换为字符串的形式"""
        return "[\n" + ",\n".join(map(str, self._mat)) + "\n]" + "\nUnconnected: " + str(self._unconn)
2.邻接表表示

继承邻接矩阵表示的图类

## 邻接表实现 继承邻接矩阵的类
class GraphAL(Graph):
    def __init__(self, mat=[], unconn=0):
        vnum = len(mat)
        for x in mat:
            if len(x) != vnum:
                raise ValueError("Argument for 'GraphAL'.")
        self._mat = [Graph._out_edges(mat[i], unconn) for i in range(vnum)]
        self._vnum = vnum
        self._unconn = unconn

    def add_vertex(self):
        """增加顶点"""
        self._mat.append([])
        self._vnum += 1
        return self._vnum - 1

    def add_edge(self, vi, vj, val=1):
        """增加vi到vj的边"""
        if self._vnum == 0:
            raise GraphError("Cannot add edge to empty graph.")
        if self._invalid(vi) or self._invalid(vj):
            raise GraphError(str(vi) + ' or ' + str(vj) + "is not valid vertex.")
        row = self._mat[vi]
        i = 0
        while i < len(row):
            if row[i][0] == vj:  # 修改mat[vi][vj]的值
                self._mat[vi][i] = (vj, val)
                return
            if row[i][0] > vj:  # 原来没有到vj的边,退出循环后加入边
                break
            i += 1
        self._mat[vi].insert(i, (vj, val)) # 在j处插入边

    def get_edge(self, vi, vj):
        """获取vi到vj的边"""
        if self._invalid(vi) or self._invalid(vj):
            raise GraphError(str(vi) + ' or ' + str(vj) + "is not valid vertex.")
        for i, val in self._mat[vi]:
            if i == vj:
                return val
        return self._unconn

    def out_edges(self, vi):
        """vi的出边"""
        if self._invalid(vi):
            raise GraphError(str(vi) + "is not valid vertex.")
        return self._mat[vi]

基本图算法

1.物联网和移动电话网的路由
2.集成电路(和印刷电路板)的设计和布线
3.运输和物流中的各种规划安排问题
4.工程项目的计划安排
5.许多社会问题计算,如金融监管(例如关联交易检查)

图的遍历和生成树
深度优先遍历

按照深度优先搜索(Depth-First Search)的方式实施整个遍历过程。假定从指定顶点python设置宽度为30右对齐_邻接矩阵_18出发:
1.首先访问顶点python设置宽度为30右对齐_邻接矩阵_18,并将其标记为已访问。
2.检查python设置宽度为30右对齐_邻接矩阵_18的邻接顶点,从中选一个尚未访问的顶点,从它出发继续进行深度优先搜索(这是递归)。不存在这种邻接顶点时回溯(邻接顶点可能排了一种顺序)。
3.反复上述操作直到从python设置宽度为30右对齐_邻接矩阵_18出发可达的所有顶点都已访问(递归)。
4.如果图中还存在未访问的顶点,则选出一个未访问顶点,由它出发重复前述过程,直到图中所有顶点都已访问为止。
通过深度优先遍历顺序得到的顶点序列称为该图的深度优先搜索序列(Depth-First Search序列),简称为DFS序列。

宽度优先遍历

通过宽度优先搜索(Breadth-First Search)的方式实施遍历。假设从指定顶点python设置宽度为30右对齐_邻接矩阵_18出发:
1.先访问顶点python设置宽度为30右对齐_邻接矩阵_18并将其标记为已访问。
2.依次访问python设置宽度为30右对齐_邻接矩阵_18的所有相邻顶点python设置宽度为30右对齐_邻接矩阵_35(可能规定某种顺序),再依次访问与python设置宽度为30右对齐_邻接矩阵_35邻接的所有尚未访问过的顶点,直到所有可达顶点都已访问。
3.如果图中还存在未访问的顶点,则选出一个未访问顶点,由它出发重复前述过程,直到图中所有顶点都已访问为止。
通过宽度优先遍历顺序得到的顶点序列称为该图的宽度优先搜索序列(Breadth-First Search序列),简称为BFS序列。

深度优先遍历的非递归算法
class StackUnderflow(ValueError): # 栈下溢(空栈访问)
    pass

class SStack(): # 基于顺序表技术实现的栈类
    def __init__(self): # 用list对象, _elems存储栈中的元素
        self._elems = [] # 所有栈操作都映射到list操作
    
    def is_empty(self):
        """判断是否为空"""
        return self._elems == []
    
    def top(self):
        """栈顶元素"""
        if self._elems == []:
            raise StackUnderflow("in SStack.top()")
        return self._elems[-1]
    
    def push(self, elem):
        """插入元素"""
        self._elems.append(elem)
    
    def pop(self):
        """删除元素"""
        if self._elems == []:
            raise StackUnderflow("in SStack.pop()")
        return self._elems.pop()


def DFS_graph(graph, v0):
    vnum = graph.vertex_num()
    visited = [0]*vnum      # visited记录已访问顶点
    visited[v0] = 1
    DFS_seq = [v0]       # DFS_seq记录遍历序列
    st = SSstack()
    st.push((0, graph.out_edges(v0))) # 入栈(i, edges), 说明下次访问边edges[i]
    while not st.is_empty():
        i, edges = st.pop() # edges的是某个顶点的边表,i是边表的下标
        if i < len(edges):
            v, e = edges[i]
            st.push((i+1, edges)) # 下次回来访问edges[i+1]
            if not visited[v]: # v未访问,访问并记录其可达顶点
                DFS_seq.append(v)
                visited[v] = 1
                st.push((0, graph.out_edges(v))) # 下次访问的边组
    return DFS_seq
生成树

7.4 最小生成树

7.5 最短路径

7.6 AOV/AEV网及其算法

AOV网、拓扑排序和拓扑序列

用图中的顶点表示某个有一定规模的“工程”里的不同活动,用图中的边表示各项活动之间的先后顺序关系。(activity on vertex network)

工程和工作安排

不同工作之间存在一些制约关系,完成一些工作之后才能开始另一些工作。做出满足制约关系的工作安排。

拓扑排序和拓扑序列

对于给定的AOV网python设置宽度为30右对齐_完全图_37,如果python设置宽度为30右对齐_完全图_37中的所有顶点能排成一个线性序列
python设置宽度为30右对齐_python设置宽度为30右对齐_39
满足:如果python设置宽度为30右对齐_完全图_37中存在从顶点python设置宽度为30右对齐_python设置宽度为30右对齐_16到顶点python设置宽度为30右对齐_邻接矩阵_25的路径,那么python设置宽度为30右对齐_邻接矩阵_43python设置宽度为30右对齐_python设置宽度为30右对齐_16就排在python设置宽度为30右对齐_邻接矩阵_25之前,则称python设置宽度为30右对齐_邻接矩阵_43python设置宽度为30右对齐_完全图_37的一个拓扑序列,而构造拓扑序列的操作称为拓扑排序。
1.不唯一
2.拓扑序列的逆网也是拓扑序列

拓扑排序算法

任何无回路的AOV网python设置宽度为30右对齐_完全图_37都可以做出拓扑序列:
1.从python设置宽度为30右对齐_完全图_37中选出一个入度为0的顶点作为序列的下一顶点
2.从python设置宽度为30右对齐_完全图_37网中删除所选顶点及其所有的出边。
3.反复执行上面两步操作,直到已经选出了图中的所有顶点,或者再也找不到入度非0的顶点时算法结束。

实现技术和函数定义

工作中需要反复找出入度为0的顶点,提出一种技巧:在indegree里维持一个“0度表”,表中记录当时已知的所有入度为0但还没有处理的顶点。用一个变量zerov记录“第一个”入度为0的顶点的下标;用表元素indegree[zerov]记录下一个入度为0的顶点的下标;如此类推。
1.确定所有顶点的入度存入indegree,用0度表记录其中入度为0的顶点;
2.反复选择入度为0的顶点并维护0度表;
3.最后返回拓扑序列,失败时返回False.

def toposort(graph):
    vnum = graph.vertex_num()        # 顶点数
    indegree, toposeq = [0]*vnum, []  # 入度表,拓扑排序表
    zerov = -1
    for vi in range(vnum):      
        for v, w in graph.out_edges(vi):  # 建立初始的入度表,vi的出边,下标和权重
            indegree[v] += 1
    for vi in range(vnum):   # 建立初始的0度表
        if indegree[vi] == 0:
            indegree[vi] = zerov
            zerov = vi
    for n in range(vnum):
        if zerov == -1:    # 没有入度为0的顶点,不存在拓扑序列
            return False
        vi = zerov             # 从0度表弹出顶点vi
        zerov = indegree[zerov]
        toposeq.append(vi)     # 把一个vi加入拓扑序列
        for v, w in graph.out_edges(vi):  # 检查vi的出边
            indegree[v] -= 1
            if indegree[v] == 0:
                indegree[v] = zerov
                zerov = v
    return toposeq
AOE网和关键路径

AOE网是一种无环的带权有向图:
1.顶点表示事件,有向边表示活动,边上的权值通常表示活动持续时间;
2.图中一个顶点表示的事件,也就是它的入边所表示的活动都已经完成,它的出边所表示的活动都可以开始的那个状态,把这一情况看做事件。
完成整个工程的最短事件,就是从开始顶点到完成顶点的最长路径的长度(即,路径上各条边的权值之和)。这种最长路径称为AOE网的关键路径。

关键路径算法
变量定义

python设置宽度为30右对齐_完全图_51:事件python设置宽度为30右对齐_邻接矩阵_25最早可能发生的时间,对每个python设置宽度为30右对齐_邻接矩阵_53只需要考虑以python设置宽度为30右对齐_邻接矩阵_25为终点的入边集。
python设置宽度为30右对齐_Graph_55:时间python设置宽度为30右对齐_python设置宽度为30右对齐_16最迟允许发生的时间,对每个python设置宽度为30右对齐_邻接矩阵_57只需要考虑以python设置宽度为30右对齐_python设置宽度为30右对齐_16为始点的出边集。
网络中活动python设置宽度为30右对齐_完全图_59的最早可能开始时间python设置宽度为30右对齐_完全图_60,以及它的最迟允许开始时间python设置宽度为30右对齐_邻接矩阵_61.
关键活动:活动集合python设置宽度为30右对齐_Graph_62中的所有活动成为这个AOE网里的关键活动。
关键路径:所有完全由关键活动构成的从初始点到终点的路径就是关键路径。

算法

1.生成AOE网的一个拓扑序列;
2.生成python设置宽度为30右对齐_完全图_63表的值,应该按拓扑序列的顺序计算;
3.生成python设置宽度为30右对齐_完全图_64数组的值,应该按照拓扑序列的逆序计算;
4.数据组python设置宽度为30右对齐_邻接矩阵_13python设置宽度为30右对齐_Graph_66可以一起计算,直接求出关键活动。

def critical_paths(graph):
    def events_earliest_time(vnum, graph, toposeq):
        ee = [0] * vnum
        for i in toposeq:
            for j, w in graph.out_edges(i):
                if ee[i] + w > ee[j]:  # 事件j更晚结束?
                    ee[j] = ee[i] + w 
        return ee

    def event_latest_time(vnum, graph, toposeq, eelast):
        le = [eelast] * vnum
        for k in range(vnum-2, -1, -1):   # 逆拓扑顺序
            i = toposeq[k]
            for j, w in graph.out_deges(i):
                if le[j] - w < le[i]:
                    le[i] = le[j] - w
        return le

    def crt_paths(vnum, graph, ee, le):
        crt_actions = []
        for i in range(vnum):
            for j, w in graph.out_deges(i):
                if ee[i] == le[j] - w:  # 关键活动
                    crt_actions.append((i, j, ee[i]))
        return crt_actions

    
    toposeq = toposort(graph)
    if not toposeq: # 不存在拓扑序列,失败结束
        return False
    vnum = graph.vertex_num()
    ee = events_earliest_time(vnum, graph, toposeq)
    le = event_latest_time(vnum, graph, toposeq, ee[vnum-1])
    return crt_paths(vnum, graph, ee, le)