图论

430 阅读7分钟

简介(来源于科大讯飞)

图论基本概念涉及图、节点与边的定义,有向图和无向图的区分,以及图的类型(如简单图、多重图和加权图)等。图论是离散数学的一个重要分支,主要研究节点及连接这些节点的边所组成的图形结构。这种结构在计算机科学中有着广泛的应用,如网络分析、社交网络、任务调度等问题都可以抽象为图的问题。

具体来说,一个图由节点集合V和边集合E组成,记作G = (V, E)。节点也称为顶点,代表图中的基本单位,而边则代表节点之间的连接关系。根据边是否有方向,图可以分为有向图和无向图。在无向图中,边(u, v)表示节点u和v之间互相连接;而在有向图中,边具有方向性,即边(u, v)不代表从v到u也有一条边。

图还可以根据其特点分为多种类型。例如,简单图是指没有自环(节点自己连接自己)和多重边(两个节点之间存在多条边)的图。多重图则允许自环和多重边的存在。加权图则为每条边分配了一个权重,通常表示节点之间的距离或连接的成本。

此外,路径和连通性也是图论中的重要概念。一条路径指的是从一个节点出发,通过一系列的边连接到其他节点,最终到达目标节点的路线。如果图中任意两点之间都存在至少一条路径,那么这个图被认为是连通的。对于有向图而言,如果每一对节点之间都能找到一条双向的路径,则称该图为强连通图。

最后,图的表示方法常见的有邻接矩阵和邻接表。邻接矩阵使用一个二维数组来表示节点之间的连接关系,而邻接表则为每个节点维护一个列表,记录与其直接相连的其他节点。这两种表示方法各有优劣,选择哪一种取决于具体的应用场景和图的稀疏性。

总结而言,掌握图论的基本概念和性质是理解和应用图论算法的基础。这些概念不仅能够帮助更好地描述和分析现实世界中的许多问题,还能为解决复杂问题提供有力的工具。

大白话理解图论

在探讨图论时,我们避免过于抽象的讨论,以免造成不必要的困惑。我们以算法中图的实际形态来理解它:简单来说,图是由不同点通过连线组成的图形。当然,图可以没有点,即所谓的“空图”,或者只包含一个单独节点的图。

举个现实生活中的例子,假设屏幕前的你是一个节点,现在你想去买瓶水,那么你和那瓶水之间就可以构成一个简单的图。再举一个更明显的例子,考虑城市地图,你可以将每个地标看作一个节点,那么整张城市地图就是一个典型的图表示。

图的种类

1、空图 image.png

2、无向图(图中节点没有方向指定) image.png

3、有向图(图中节点有方向)

image.png

4、加权无向图(图中节点没有方向但是有权值)(权值可以理解为两个节点之间的距离)

image.png

5、加权有向图(图中节点有方向并且拥有权值)

image.png

代码构造图

1、邻接表

要构造一个邻接表,首先需要确定图的顶点和边。假设我们有一个无向图,顶点用整数表示,边用元组表示。我们可以使用字典来表示领接表,其中键是顶点,值是一个列表,表示与该顶点相邻的顶点。

def create_adjacency_matrix(vertices, edges):
    # 初始化邻接矩阵,所有元素都为0
    matrix = [[0 for _ in range(vertices)] for _ in range(vertices)]

    # 遍历边信息,将对应的矩阵元素设为1
    for edge in edges:
        u, v = edge
        matrix[u][v] = 1
        matrix[v][u] = 1  # 如果是无向图,需要添加这一行

    return matrix

# 示例:构造一个包含4个顶点和5条边的无向图的邻接矩阵
vertices = 4
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
adjacency_matrix = create_adjacency_matrix(vertices, edges)

# 打印邻接矩阵
for row in adjacency_matrix:
    print(row)

这段代码首先定义了一个名为create_adjacency_list的函数,它接受一个边的列表作为输入。然后,它遍历边列表,并将每个顶点添加到邻接表中。最后,它将每个顶点的邻接顶点添加到相应的列表中。在这个例子中,我们使用了一个简单的无向图,所以每条边的两个顶点都被添加到彼此的邻接列表中。

2、邻接矩阵

要构造邻接矩阵,首先需要确定图的顶点数和边的信息。邻接矩阵是一个二维数组,其中行和列分别表示图中的顶点,数组元素表示两个顶点之间是否存在边。如果存在边,则对应的元素值为1,否则为0。

def create_adjacency_matrix(vertices, edges):
    # 初始化邻接矩阵,所有元素都为0
    matrix = [[0 for _ in range(vertices)] for _ in range(vertices)]

    # 遍历边信息,将对应的矩阵元素设为1
    for edge in edges:
        u, v = edge
        matrix[u][v] = 1
        matrix[v][u] = 1  # 如果是无向图,需要添加这一行

    return matrix

# 示例:构造一个包含4个顶点和5条边的无向图的邻接矩阵
vertices = 4
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
adjacency_matrix = create_adjacency_matrix(vertices, edges)

# 打印邻接矩阵
for row in adjacency_matrix:
    print(row)

3、邻接矩阵和邻接表的区别(来源于科大讯飞)

邻接矩阵和邻接表是两种用于表示图结构的常用方法,它们在存储方式、空间复杂度以及某些操作的效率上有所不同。以下是对这两种表示方法的详细对比:

1. 存储方式:

  • 邻接矩阵:使用一个二维数组来表示图中顶点之间的连接关系。如果顶点i和顶点j之间有边,则矩阵中的matrix[i][j]元素通常被设置为1(或边的权重,如果是加权图的话)。
  • 邻接表:为每个顶点维护一个列表,记录与该顶点直接相连的所有其他顶点。这通常通过一个字典或数组实现,其中键是顶点,值是一个包含所有邻接顶点的列表。

2. 空间复杂度:

  • 邻接矩阵:无论图中顶点的连接情况如何,都需要存储一个|V| x |V|大小的矩阵,其中|V|是图中顶点的数量。因此,邻接矩阵的空间复杂度为O(|V|^2),这在顶点数量较多但边相对较少的情况下,可能会造成较大的空间浪费。
  • 邻接表:只存储与每个顶点直接相连的顶点,因此其空间复杂度与边的数量成正比,即O(|E| + |V|),其中|E|是图中边的数量。对于稀疏图(边数远少于顶点数的平方),邻接表更为空间高效。

3. 时间复杂度:

  • 邻接矩阵:查找与某个顶点相连的所有顶点的时间复杂度为O(|V|),因为需要遍历矩阵的一行。然而,判断两个顶点之间是否存在边可以直接通过矩阵索引,时间复杂度为O(1)。
  • 邻接表:查找与某个顶点相连的所有顶点的时间复杂度取决于该顶点的度(即与其相连的顶点数),因此可能从O(1)到O(|V|)不等。判断两个顶点之间是否存在边需要遍历起点的邻接表,时间复杂度最坏为O(|V|),在邻接表中使用哈希表等数据结构可以优化这一操作。

4. 适用场景:

  • 邻接矩阵:更适合于顶点数量较少且边密集的图,例如完全图或几乎每个顶点都与其他顶点相连的情况。
  • 邻接表:对于大型稀疏图,即顶点数量多但边相对稀少的情况,邻接表是更理想的选择。

总结来说,邻接矩阵和邻接表各有优势,选择哪种表示形式主要取决于图的特点(如顶点和边的数量,图的稀疏程度)以及具体的应用需求(如需要频繁执行的操作类型)。