学习笔记-数据结构-图的基础知识及邻接矩阵和邻接表的实现(Python)

数据结构 专栏收录该内容
22 篇文章 22 订阅

一、基本概念

是一个二元组G=(V,E)。V是非空有穷的顶点集合。E是图G中边的集合。

有向图:图中的每条边都有方向(即带有箭头)。

无向图:图中的每条边都没有方向。

有向边:用尖括号来表示为<a,b>。a是始点,b是终点。也被称为弧,a是弧尾,b是弧头。

无向边:用圆括号表示为(a,b)。

完全图:任意两个顶点之间都有边的图(有向图或无向图)。

            n个顶点的无向完全图有n*(n-1)/2条边;n个顶点的有向完全图有n*(n-1)条边。

顶点的度:与这个顶点邻接的边条数。

                对于有向图还有入度和出度的概念。

                入度:以此顶点为终点的边的数目。

                出度:以此顶点为始点的边的数目。

路径:两顶点之间所存在的通路。

        路径的长度:该路径上边的条数。

        回路(环):起点和终点相同(重合)的路径。

        简单路径:除了顶点和终点可能相同外,其他顶点均不相同的路径。

        简单回路:一个回路内,起点和终点相同,但其他的顶点都不相同。也可理解为起点和终点相同的简单路径是简单回路。

                        简单回路是简单路径的一个子集。  

连通:存在从顶点a到b的路径,则说明两顶点是连通的。

        连通无向图:无向图中,任意两顶点之间都相互连通。

        强连通有向图:有向图中任意两个顶点a、b,从a到b连通,并且从b到a也连通。(因为是有向边,所以从a到b,从b到a的路径都要求存在)

子图:对于图G=(V,E)和G1=(V1,E1),如果V1包含于V,并且E1包含于E,则称G1是G的一个子图。

        连通子图:原图可能不是连通图,但其一些子图是连通的,则这些子图称为原图的连通子图。(对于有向图,则称为强连通子图)

        极大连通子图(连通分量):G1是原图G的一个连通子图,G1的顶点和边集合都已经不能扩充,是极大的。(如果再增加  顶点,就会不连通,也没有其他的边可加)。如果G本身连通,则只有一个连通分量,就是G本身。

        极大强连通子图(强连通分量):这是对于有向图来说的,与无向图中的定义类似。


图4 是强连通有向图,只有一个强连通分量(其自身)。

图2中包含两个强连通分量。顶点a总是与其自身连通,所以其本身就是一个强连通分量。

图3包含3个强连通分量。

图1中任意两个顶点都不是相互连通的,所以有3个强连通分量(3个顶点)。


带权图:图中的每条边都带有一个权值。

网络:带有权值的连通无向图。

       

二、邻接矩阵及用其实现的图类

邻接矩阵是表示图中顶点间邻接关系的方阵。如果图有n个顶点,那么邻接矩阵就是一个n*n的方阵。

最简单的邻接矩阵是以0/1为元素的方阵。定义如下:


对于带权图,其定义为:


1、以下是有向图的邻接矩阵的一个例子,右图是左图的邻接矩阵。

每行中非零元素的个数对应于顶点i的出度;每列中非零元素的个数对应于顶点i的入度。


2、以下是无向图的邻接矩阵的例子。可以看出,无向图的邻接矩阵是对称矩阵。


邻接矩阵的优点:便于计算出各个顶点的度,以及判断两个顶点间是否有边。

                缺点:矩阵中的大部分元素只是为了说明某些边不存在,造成了空间的浪费。

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_edge(self, vi, vj, val=1):
        if self.invalid(vi) or self.invalid(vj):
            raise GraphError(str(vi) + 'or' + str(vj) + 'is not a valid vertex')
        self.mat[vi][vj] = val

    def get_edge(self, vi, vj):
        if self.invalid(vi) or self.invalid(vj):
            raise GraphError(str(vi) + 'or' + str(vj) + 'is not a valid vertex')
        return self.mat[vi][vj]

    # 返回每个顶点的出边的终点位置,和这条出边上的权值
    def out_edges(self, vi):
        if self.invalid(vi):
            raise GraphError(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:  # 当前行中不等于0的位置
                edges.append((i, row[i]))
        return edges

三、用邻接表及其实现的图类

邻接表就是为图中的每个顶点关联一个链表,其中记录这个顶点的所有邻边。

在无向图中,同一条边被邻接表存储两次。在有向图中,同一条边被邻接表存储一次。

在无向图中,顶点的度即为:对应链表中结点的个数。在有向图中,顶点出度即为:对应链表中结点的个数;入度则需要遍历其他顶点的链表。

邻接表的优点:只存储实际的边,节省空间。

             缺点:确定顶点的度时,可能需要遍历一个链表。

稠密图一般用邻接矩阵,稀疏图一般用邻接表。


class GraphAL(Graph):
    def __init__(self, mat1=[], unconn=0):
        vnum = len(mat1)
        for x in mat1:
            if len(x) != vnum:
                raise ValueError('Argument for Graph')
        self.mat = [Graph._out_edges(mat1[i], unconn) for i in range(vnum)]
        self.unconn = unconn
        self.vnum = vnum

    def add_vertex(self):
        self.mat.append([])
        self.vnum += 1
        return self.vnum - 1

    def add_edge(self, vi, vj, val=1):
        if self.vnum == 0:
            raise GraphError('can not add edge to empty graph')
        if self.invalid(vi) or self.invalid(vj):
            raise GraphError(str(vi) + 'or' + str(vj) + 'is not a valid vertex')
        row = self.mat[vi]  # rowmat中的某一行。例如[(0,4),(2,6)]
        i = 0
        while i < len(row):
            if row[i][0] == vj:  # 如果原来存在vivj的边,找出与终点vj相同的终点在第几个元组中
                self.mat[vi][i] = (vj, val)
                return
            if row[i][0] > vj:  # 原来不存在vivj的边。因为边表中是按递增的顺序添加的,
                break  # 假设vj=2,但是当前已经遍历到了(3,1),说明没有终点为2的这条边
            i += 1
        self.mat[vi].insert(i, (vj, val))

    def get_edge(self, vi, vj):
        if self.invalid(vi) or self.invalid(vj):
            raise GraphError(str(vi) + 'or' + str(vj) + 'is not a valid vertex')
        for i, val in self.mat[vi]:
            if i == vj:
                return val
        return self.unconn

    def out_edges(self, vi):
        if self.invalid(vi):
            raise GraphError(str(vi) + 'is not a valid vertex')
        return self.mat[vi]


if __name__ == "__main__":
    mat = [[0, 0, 3],
           [4, 0, 6],
           [0, 8, 9]]
    g = Graph(mat)
    print(g.out_edges(1))  # [(0, 4), (2, 6)]

    g1 = GraphAL(mat)
    g1.add_edge(1, 1, 16)
    print(g1.mat)  # [[(2, 3)], [(0, 4), (1, 16), (2, 6)], [(1, 8), (2, 9)]]


评论 5 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

bebr

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值