数据结构与算法----复习Part 17 (图(Graph)初步)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

 

目录

一,图(Graph)

图的分类

顶点的度

环形图和无环图

连通图和非连通图

强连通图和强连通分量

带权图

二,图的存储结构

1. 邻接矩阵(Adjacency Matrix)

2. 边集数组(Edgeset Array)

3. 邻接表(Adjacency List)

4. 链式前向星(Linked Forward Star)

5. 哈希表实现邻接表


 

一,图(Graph)

        由顶点的非空有限集合 V (由 n > 0 个顶点组成)与边的集合 E (顶点之间的关系)构成的结构。其形式化定义为 G = ( V, E )。

        顶点(Vertex):图中的数据元素通常称为顶点;

        边(Edge):图中两个数据元素之间的关联关系称为边,边的形式化定义为 e = <u, v>,表示从 u 到 v 的一条边,其中 u 称为起始点,v 称为终止点。

 子图(Sub Graph),边是子集并且顶点也是子集:

        根据定义,G 也是其自身的子图。

图的分类

        按照边是否右方向,可以分为 无向图 和 有向图。

        无向图(Undirected Graph):图中的每条边都没有指向性;

        有向图(Directed Graph):图中的每条边都具有指向性。

        无向图中,每条边都是由两个顶点组成的无序对,上图中记为(v1, v2) 或 (v2, v1)

        有向图中,有向边也被称为弧,是有序对,上图中记为 <v1, v2>。

        如果无向图中有 n 个顶点,则无向图中最多有 n * (n - 1) / 2 条边,称这样的无向图为【完全无向图(Completed Undirected Graph)】。

        如果有向图中有 n 个顶点,则有向图中最多有 n * (n - 1)条弧,称这样的有向图为 【完全有向图(Completed Directed Graph)】。

顶点的度

        与该顶点 vi 相关联的边的条数,记为 TD(vi)。

        例如上图左侧的无向完全图中,顶点 v3 的度为 3.

        而对于有向图,可以将顶点的度分为【顶点的出度】和【顶点的入度】

                顶点的出度:以该顶点 vi 为出发点的边的条数,记为 OD(vi);

                顶点的入度:以该顶点 vi 为终止点的边的条数,记为 ID(vi);

        有向图中某顶点的度:

                TD(vi) = OD(vi) + ID(vi)

环形图和无环图

        【路径】是图中的一个重要的概念,简单来说,如果顶点 v[n] 可以通过系列的顶点和边,到达顶点 v[m] ,则称两顶点之间有一条路径,其中经过的顶点序列,称为两个顶点之间的路径。

        环(Circle):如果一条路径的起始点和终止点相同,则称这条路径为【回路】或【环】;

        简单路径:顶点序列中,不重复出现某一顶点的路径;

        根据图中是否有环,我们可以将图分为【环形图】和【无环图】;

        环形图(Circular Graph):图中存在至少一条环路;

        无环图(Acyclic Graph):图中不存在环路。

        特别的,在有向图中,如果不存在环路,则将该图称为【有向无环图(Directed Acyclic Graph)】,缩写为DAG,因为有向无环图拥有特殊的拓扑结构,经常被用于处理动态规划、导航中最短路径、数据压缩等多中算法场景。

连通图和非连通图

        无向图中,如果从顶点 vi 到顶点 vj 有路径,则称顶点 vi 和 vj 是连通的。

        连通无向图:在无向图中,图中任意两个顶点之间都是连通的;

        非连通无向图:无向图中,至少存在一对顶点之间不存在任何路径;

连通分量:有些无向图可能不是连通图,但是其子图可能是连通的,这些子图称为原图的连通子图。而无向图的一个极大连通子图(不存在包含它的更大的连通子图)则称为该图的【连通分量】。

        连通子图:无向图的子图是连通无向图,则该子图为原图的连通子图;

        极大连通子图:无向图中一个连通子图,且不存在包含它的更大的连通子图;

        连通分量:无向图中的一个极大连通子图。

        上图中右侧的非连通无向图,其本身是非连通的,但 v1, v2, v3, v4 与其相连的边构成的子图是连通的,且不存在包含它的更大的连通子图了,所以该图是原图的一个连通分量。注意,顶点 v5, v6 与其相连的边构成的子图也符合定义,也是原图的一个连通分量。

强连通图和强连通分量

        有向图中,如果顶点 vi 到 vj 有路径,并且从顶点 vj 到 vi 也有路径,则称两顶点是连通的。

        强连通有向图:图中任意两个顶点都有路径;

        非强连通有向图:存在某顶点无法到达其他任何顶点;

有向图的一个极大强连通子图称为该图的 强连通分量。

        上图中,右侧 v1, v2, v3, v4, v5, v6 与其相连的边构成的子图是原图的一个强连通分量,注意顶点 v7 构成的子图,也是原图的一个强连通分量。

带权图

        图不仅需要表示顶点之间是否存在某种关系,还需要表示这一关系的具体细节。则需要在边上带一些数据信息,这些信息称为 权 。在具体应用中,权值可以具有某种具体意义,如权值可以代表距离、时间及价格等不同属性。

        带权图:图的每条边都被赋予一个权值;

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

二,图的存储结构

        图的结构比较复杂,需要表示顶点和边。一个图可能有任意多个顶点,任何两个顶点之间都可能存在边。在实现图的存储时,重点关注边与顶点之间的关联关系,这是图存储的关键。

        图的存储可以通过【顺序存储结构】和【链式存储结构】来实现。其中顺序存储结构包括邻接矩阵和边集数组;链式存储结构包括邻接表,链式前向量等。

1. 邻接矩阵(Adjacency Matrix)

        使用一个二维数组 adjmatrix 来存储顶点之间的邻接关系。

        无权图:adjmatrix[i][j] = 1表示顶点 vi 到 vj 存在边。adjmatrix[i][j] = 0 表示顶点 vi 到 vj 不存在边;

        带权图:adjmatrix[i][j] = w 且 w != float('inf') ,表示顶点 vi 到 vj 的权值为 w,adjmatrix[i][j] = float('inf') 表示顶点 vi 到 vj 不存在边。

特点:

        优点:实现简单,且可以直接查询顶点 vi 与 vj 之间是否有边存在,可以直接查询边的权值;

        缺点:初始化效率和遍历效率低,空间利用率低,且不能存储重复边,也不便于增删节点。

class adjMatrix:
    def __init__(self, ver_count):
        self.ver_count = ver_count
        self.adj_matrix = [[None for _ in range(ver_count)] for _ in range(ver_count)]

    def __valid(self, v):
        return 0 <= v <= self.ver_count

    def creatGraph(self, edges = []):
        for vi, vj, val in edges:
            self.add_edge(vi, vj, val)

    def add_edge(self, vi, vj, val):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not i valid vertex.')

        self.adj_matrix[vi][vj] = val

    def get_edge(self, vi, vj):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError(str(vi) + 'or' + str(vj) + 'is not i valid vertex.')

        return self.adj_matrix[vi][vj]

    def printGraph(self):
        for vi in range(self.ver_count):
            for vj in range(self.ver_count):
                val = self.get_edge(vi, vj)
                if val:
                    print(str(vi) + ' - ' + str(vj) + ' : ' + str(val))

时间复杂度:

        初始化 O(n ^ 2)

        查询,添加或删除 O(1)

        获取某个节点所有边 O(n)

        图的遍历 O(n ^ 2)

空间复杂度 O(n ^ 2)

2. 边集数组(Edgeset Array)

        使用一个数组来存储顶点之间的邻接关系。数组中的每个元素都包含一条边的起点vi,终点vj和边的权值 val。

class EdgeNode:
    def __init__(self, vi, vj, val):
        self.vi = vi
        self.vj = vj
        self.val = val

class EdgesetGraph:
    def __init__(self):
        self.edges = []

    def creatGraph(self, edges = []):
        for vi, vj, val in edges:
            self.add_edge(vi, vj, val)

    def add_edge(self, vi, vj, val):
        edge = EdgeNode(vi, vj, val)
        self.edges.append(edge)

    def get_edge(self, vi, vj):
        for edge in self.edges:
            if vi == edge.vi and vj == edge.vj:
                val = edge.val
        return None

    def printGraph(self):
        for edge in self.edges:
            print(str(edge.vi) + ' - ' + str(edge.vj) + ' : ' + str(edge.val))

3. 邻接表(Adjacency List)

        使用顺序存储和链式存储相结合的存储结构在存储图的顶点和边。其数据结构其一是数组,主要用来存放顶点的数据信息,其二是链表,用来存放边信息。

        在邻接表的存储方法中,对于图中的每个节点建立一个线性链表,把所有邻接于 vi 的顶点链接到单链表上。

        在每个顶点的前面设置一个表头节点,称之为【顶点节点】。每个顶点节点由【顶点域】和【指针域】组成。其中顶点域用于存放某个顶点的数据信息,指针域用于指出该顶点的第 1 条边对应的链节点。

        为了方便随机访问任意顶点的链表,使用一组顺序存储的数组存储所有【顶点节点】部分,数组的下标表示顶点在图中的位置。

class AdjListNode:
    def __init__(self, vj, val):
        self.vj = vj
        self.val = val
        self.next = None

class VertexNode:
    def __init__(self, vi):
        self.vi = vi
        self.head = None

class AdjList:
    def __init__(self, ver_count):
        self.ver_count = ver_count
        self.vertices = []
        for vi  in range(ver_count):
            vertex = VertexNode(vi)
            self.vertices.append(vertex)

    def __valid(self, v):
        return 0 <= v <= self.ver_count

    def creatGraph(self, edges = []):
        for vi, vj, val in edges:
            self.add_edge(vi, vj, val)

    def add_edge(self, vi, vj, val):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError('It is not a valid vertex')

        vertex = self.vertices[vi]
        edge = AdjListNode(vj, val)
        edge.next = vertex.head
        vertex.head = edge

    def get_edge(self, vi, vj):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError('It is not a valid vertex')

        vertex = self.vertices[vi]
        cur_edge = vertex.head
        while cur_edge:
            if cur_edge.vj == vj:
                return cur_edge.val
            cur_edge = cur_edge.next
        return None

    def printGraph(self):
        for vertex in self.vertices:
            cur_edge = vertex.head
            while cur_edge:
                print(str(vertex.vi) + ' - ' + str(cur_edge.vj) + ' : ' + str(cur_edge.val))
                cur_edge = cur_edge.next

时间复杂度:

        初始化和创建 O(m + n)

        查询存在 vi 到 vj 的边 O(TD(vi))

        遍历某个点的所有边 O(TD(vi))

        遍历整张图 O(m + n)

空间复杂度 O(m + n)

4. 链式前向星(Linked Forward Star)

        实质上是使用静态链表实现的邻接表,也叫做静态邻接表,将边集数组和邻接表相结合,可以快速访问一个节点所有的邻接点,并且使用很少的额外空间。

        特殊的边集数组 edges:其中 edges[ i ] 表示第 i 条边,edges[i].vj 表示第 i 条边的终止点,edges[i].val 表示第 i 条边的权值,edges[i].next 表示与第 i 条边同起始点的下一条边的存储位置。

        头节点数组 head:其中 head[ i ] 存储以顶点 i 为起始点的第 1 条边在数组 edges 中的下标。

class forwardStarNode:
    def __init__(self, vj, val):
        self.vj = vj
        self.val = val
        self.next = None

class forwardStar:
    def __init__(self, ver_count, edge_count):
        self.ver_count = ver_count
        self.edge_count = edge_count
        self.head = [-1 for _ in range(ver_count)]
        self.edges = []

    def __valid(self, v):
        return 0 <= v <= self.ver_count

    def creatGraph(self, edges = []):
        for i in range(len(edges)):
            vi, vj, val = edges[i]
            self.add_edge(i, vi, vj, val)

    def add_edge(self, index, vi, vj, val):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError('It is not a valid vertex.')

        edge = forwardStarNode(vj, val)
        edge.next = self.head[vi]
        self.edges.append(edge)
        self.head[vi] = index

    def get_edge(self, vi, vj):
        if not self.__valid(vi) or not self.__valid(vj):
            raise ValueError('It is not a valid vertex.')

        index = self.head[vi]
        while index != -1:
            if vj == self.edges[index].vj:
                return self.edges[index].val
            index = self.edges[index].next
        return None

    def printGraph(self):
        for vi in range(self.ver_count):
            index = self.head[vi]
            while index != -1:
                print(str(vi) + ' - ' + str(self.edges[index].vj) + ' : ' + str(self.edges[index].val))
                index = self.edges[index].next

时间复杂度:

        初始化和创建 O(m + n)

        查询存在 vi 到 vj 的边 O(TD(vi))

        遍历某个点的所有边 O(TD(vi))

        遍历整张图 O(m + n)

空间复杂度 O(m + n)

5. 哈希表实现邻接表

        在Python中,可以通过字典(哈希表)实现邻接表:

                第一个字典主要用来存放顶点的数据信息,字典的键是顶点,值是该点所有邻边构成的

        另一个字典;

                另一个字典是用来存放顶点相连的边信息,地点的键是边的终点,值是边的权重。

class HashNode:
    def __init__(self, vi):
        self.vi = vi
        self.adj_edges = dict()

class HashGraph:
    def __init__(self):
        self.vertices = dict()

    def creatGraph(self, edges = []):
        for vi, vj, val in edges:
            self.add_edge(vi, vj, val)

    def add_vertex(self, vi):
        vertex = HashNode(vi)
        self.vertices[vi] = vertex

    def add_edge(self, vi, vj, val):
        if vi not in self.vertices:
            self.add_vertex(vi)
        if vj not in self.vertices:
            self.add_vertex(vj)

        self.vertices[vi].adj_edges[vj] = val


    def get_edge(self, vi, vj):
        if vi in self.vertices and vj in self.vertices[vi].adj_edges:
            return self.vertices[vi].adj_edges[vj]
        return None

    def printGraph(self):
        for vi in self.vertices:
            for vj in self.vertices[vi].adj_edges:
                print(str(vi) + ' - ' + str(vj) + ' : ' + str(self.vertices[vi].adj_edges[vj]))

时间复杂度:

        初始化和创建 O(m + n)

        查询存在 vi 到 vj 的边 O(1)

        遍历某个点的所有边 O(TD(vi))

        遍历整张图 O(m + n)

空间复杂度 O(m + n)

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/472641.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

远程服务器安装portainer —— docker的可视化工具

可视化工具&#xff08;了解即可&#xff09; 最常用的工具是 portainer portainer 是一个开源的容器管理平台&#xff0c;它提供了一个简单易用的用户界面&#xff0c;用于管理和监控 docker 容器集群。通过 portainer&#xff0c;用户可以轻松地进行容器的部署、启动、停止…

C++_回文串

目录 回文子串 最长回文子串 分割回文串 IV 分割回文串 II 最长回文子序列 让字符串成为回文串的最少插入次数 回文子串 647. 回文子串 思路&#xff0c;i j表示改范围内是否为回文串&#xff0c; ②倒着遍历是为了取出dp[i 1][j - 1] ③i j 只有一对&#xff0c;不会重复…

算法沉淀——贪心算法五(leetcode真题剖析)

算法沉淀——贪心算法五 01.跳跃游戏 II02.跳跃游戏03.加油站04.单调递增的数字 01.跳跃游戏 II 题目链接&#xff1a;https://leetcode.cn/problems/jump-game-ii/ 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转…

一共有哪 3 类线程安全问题

一共有哪 3 类线程安全问题 运行结果错误发布和初始化导致线程安全问题活跃性问题死锁活锁饥饿 要想弄清楚有哪 3 类线程安全问题&#xff0c;首先需要了解什么是线程安全&#xff0c;线程安全经常在工作中被提到&#xff0c;比如&#xff1a;你的对象不是线程安全的&#xff0…

2024新版计算器:腾讯云服务器价格计算器,报价不求人

腾讯云服务器价格计算器可以一键计算出云服务器的精准报价&#xff0c;包括CVM实例规格价格、CPU内存费用、公网带宽收费、存储系统盘和数据盘详细费用&#xff0c;腾讯云百科txybk.com分享腾讯云价格计算器链接入口、使用方法说明&#xff0c;在腾讯云百科 txy.wiki 查看当前最…

全球盲盒火热下,海外盲盒APP助力我国盲盒出海

盲盒具有不确定性&#xff0c;与各类热门影视动漫合作推出的专属盲盒商品&#xff0c;吸引了无数年轻人&#xff0c;成为了年轻人的娱乐消费首选方式。 在互联网电商的推动下&#xff0c;盲盒在全球内的市场规模迅速扩大。受到市场增长的影响&#xff0c;各类资本公司也纷纷进…

【Python】import无法导入某一目录下的文件

问题&#xff1a; 如图所示&#xff0c;我在mains文件夹下面有一个main_VAE.py的程序&#xff0c;在与mains同级目录的models文件夹下面有一个variational_autoencoder.py&#xff08;可能上图无法显示完全models文件夹&#xff09;&#xff0c;此时我想要在main_VAE.py程序中导…

数据结构从入门到精通——直接选择排序

直接选择排序 前言一、选择排序的基本思想&#xff1a;二、直接选择排序三、直接选择排序的特性总结&#xff1a;四、直接选择排序的动画展示五、直接选择排序的代码展示test.c 六、直接选择排序的优化test.c 前言 直接选择排序是一种简单的排序算法。它的工作原理是每一次从未…

Linux-docker安装数据库mysql

1、拉去mysql镜像&#xff1a; docker pull mysql2、创建容器挂载路径 mkdir -p /usr/local/jiuxiang/mysql/data # 数据存储位置 mkdir -p /usr/local/jiuxiang/mysql/logs # 日志存储位置 mkdir -p /usr/local/jiuxiang/mysql/conf # 配置文件3、启动容器 docker run -…

详细分析Python模块中的雪花算法(附模板)

目录 前言1. 基本知识2. 模板3. Demo 前言 分布式ID的生成推荐阅读&#xff1a;分布式ID生成方法的超详细分析&#xff08;全&#xff09; 1. 基本知识 Snowflake 算法是一种用于生成全局唯一 ID 的分布式算法&#xff0c;最初由 Twitter 设计并开源 它被设计用于解决分布式…

sentinel整合openFeign实现fall服务降级

服务提供方: 导入依赖&#xff1a; <!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--alibaba-sentinel--><depend…

猫猫编号

解法&#xff1a; 暴力 #include<iostream> #include<algorithm> #include<vector> using namespace std; #define endl \nint main() {int n, m, sum 1;cin >> n >> m;string s;cin >> s;int pre s[0] - 0;int t 0;for (int i 1; i…

【DAY13 软考中级备考笔记】操作系统

操作系统 3月17日 – 天气&#xff1a;晴 凑着周末&#xff0c;赶紧把操作系统完结一下。 1. 管程 管程也属于操作系统中的一种同步机制&#xff0c;为了解决多线程环境中的并发控制问题。它提供了一系列的高级同步原语。 作用于信号量一样&#xff0c;但是管程便携程序更加简单…

腾讯云优惠券介绍、领取入口及使用教程

腾讯云作为国内领先的云服务提供商&#xff0c;一直以其稳定、高效、安全的服务赢得了广大用户的信赖。为了回馈广大用户&#xff0c;腾讯云经常会推出各种优惠活动&#xff0c;其中优惠券就是最为常见和受欢迎的一种。 一、腾讯云优惠券介绍 腾讯云优惠券是腾讯云官方推出的一…

Json Web Token(JWT) 快速入门

推荐视频&#xff1a;【从零开始掌握JWT】 目录 第一章 会话跟踪 01 使用Cookie和Session&#xff0c;jsessionid 02 使用token 例子一&#xff1a;自定义token 例子二&#xff1a;使用redis存储token 第二章 会用JWT 01 TOKEN的特点 02 什么时候使用JWT 03 JWS-JWE…

Linux学习:git补充与调试工具gdb

目录 1. git版本控制器&#xff08;续&#xff09;1.1 git本地仓库结构1.2 git实现版本控制与多人协作的方式1.3 git相关指令&#xff0c;多分支模型与.gitignore文件 2. gdb调试工具2.1 企业项目开发流程简述与调试的必要性2.2 bug的调试思路方法与调式工具的使用 1. git版本控…

目标检测---IOU计算详细解读(IoU、GIoU、DIoU、CIoU、EIOU、Focal-EIOU、SIOU、WIOU)

常见IoU解读与代码实现 一、✒️IoU&#xff08;Intersection over Union&#xff09;1.1 &#x1f525;IoU原理☀️ 优点⚡️缺点 1.2 &#x1f525;IoU计算1.3 &#x1f4cc;IoU代码实现 二、✒️GIoU&#xff08;Generalized IoU&#xff09;2.1 GIoU原理☀️优点⚡️缺点 2…

深入理解Java中的TCP连接:三次握手和四次挥手

欢迎来到我的博客&#xff01;今天我们将一起探索网络通信的奥秘。在Java编程中&#xff0c;我们经常会涉及到网络通信&#xff0c;而TCP协议是实现可靠数据传输的重要协议之一。在建立TCP连接和断开连接的过程中&#xff0c;三次握手和四次挥手是至关重要的步骤。本文将深入探…

rt-thread(5.0版本)之sfud组件的使用问题记录(w25q128存储模块)

前言 记录一下5.0版本时使用官方推荐的函数与底层驱动存在的不兼容问题 相关宏定义 // -----------------------------SPI 组件 #define RT_USING_SPI #define RT_USING_SFUD #define RT_SFUD_USING_SFDP #define RT_SFUD_USING_FLASH_INFO_TABLE #define RT_SFUD_SPI_MAX_HZ…

生骨肉冻干喂养有哪些优点?对猫身体好的生骨肉冻干分享

随着科学养猫知识的普及&#xff0c;生骨肉冻干喂养越来越受到养猫人的青睐。生骨肉冻干不仅符合猫咪的饮食天性&#xff0c;还能提供均衡的营养&#xff0c;有助于维护猫咪的口腔和消化系统健康。很多铲屎官看到了生骨肉冻干喂养的好处&#xff0c;打算开始生骨肉冻干喂养&…