19.图,图的两种存储结构

目录

一. 一些基本概念

二. 图的抽象数据类型定义

三. 图的存储结构

(1)数组表示法(邻接矩阵表示法)

(a)邻接矩阵

(b)存储表示

 (c)优缺点分析

(2)链式存储结构(邻接表表示法)

(a)邻接表

(b)存储表示

(c)优缺点

(d)邻接表的优化:十字链表

(e)邻接表的优化:邻接多重表


一. 一些基本概念

图:多对多的结构关系。G=(V,E),偶对;
其中:V:顶点(数据元素)的有穷非空集合;E:边的有穷集合。

无向图:每条边都是无方向的;有向图:每条边都是有方向的

完全图:任意两个点都有一条边相连。对于无向完全图,n个顶点应该有n(n-1)/2条边,对于有向完全图,两个顶点之间两个方向的边都存在才能算是完全图。n个顶点应该有n(n-1)条边。

稀疏图:有很少边或弧(有向边)的图(e<nlogn)。稠密图:有较多边或弧的图。

网:边/弧带权的图。
邻接:有边/弧相连的两个顶点之间的关系,相连时称为这两个点是邻接的。

存在\left ( v_i,v_j \right ),则称v_i,v_j互为邻接点;若存在<v_i,v_j>,表示这条弧从v_i指向v_j,则称v_i邻接到v_jv_j邻接于v_i

(在离散数学中,()表示无序对,<>表示有序对,也称序偶)

关联(依附):边/弧与顶点之间的关系。若存在\left ( v_i,v_j \right )/<v_i,v_j>,则称该边/弧关联于v_iv_j

顶点的度:与该顶点相关联的边的数目,记为TD(v)

在有向图中,顶点的度等于该顶点的入度与出度之和。顶点v的入度是以v为终点的有向边的条数,记作ID(v);顶点v的出度是以v为始点的有向边的条数,记作OD(v);

当有向图中仅1个顶点的入度为0,其余顶点的入度均为1,此时是树,并且是有向树。

路径:接续的边构成的顶点序列路径长度:路径上边或弧的数目/权值之和。

回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径。

简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。

连通图(强连通图):在无(有)向图G=(V,{E})中,若对任何两个顶点v、u都存在从v到u的路径,则称G是连通图(强连通图)。

权与网:图中边或弧所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或耗费。带权的图称为网。

子图:设有两个图G=(V,{E})、G1=(V1,{E1}),若V_1\subseteq VE_1\subseteq E,则称G1是G的子图。

连通分量(强连通分量):无(有)向图G的极大连通子图称为G的(强)连通分量。
极大连通子图:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。

极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通。
生成树:包含无向图G所有顶点的极小连通子图。
生成森林:对非连通图,由各个连通分量的生成树的集合。

二. 图的抽象数据类型定义

ADT Graph{
    数据对象V:具有相同特性的数据元素的集合,称为顶点集。
    数据关系R:R={VR}
        VR = {<v,w>|<v,w>|v,w∈V ^ p(v,w),
        <v,w>表示从v到w的弧,p(v,w)定义了弧<v,w>的信息(权)}
    基本操作P:
        Create_Graph()  //图的创建操作。
            初始条件:无。
            操作结果:生成一个没有顶点的空图G。
        GetVex(G,v)  //求图中的顶点v的值。
            初始条件:图G存在,v是图中的一个顶点。
            操作结果:生成一个没有顶点的空图G。
        CreateGraph(&G,V,VR)
            初始条件:V是图的顶点集,VR是图中弧的集合。
            操作结果:按V和VR的定义构造图G。
        DFSTraverse(G)
            初始条件:图G存在。
            操作结果:对图进行深度优先遍历。
        BFSTraverse(G)
            初始条件:图G存在。
            操作结果:对图进行广度优先遍历。
}ADT Graph

三. 图的存储结构

(1)数组表示法(邻接矩阵表示法)

(a)邻接矩阵

图没有顺序存储结构,但可以借助二维数组来表示元素间的关系。

建立一个顶点表vexs(记录各个顶点信息)和一个邻接矩阵arcs(表示各个顶点之间关系)。设图A= (V,E)有n个顶点,则

 图的邻接矩阵是一个二维数组A. arcs[n][n],定义为:

A.arcs[i][j]=\left\{\begin{matrix} 1,if <i,j>\in E\, or \left ( i,j \right)\in E\\ 0,else \end{matrix}\right.

无向图的邻接矩阵

分析1:无向图的邻接矩阵是对称的;
分析2:顶点i的度=第i行(列)中1的个数;
特别:完全图的邻接矩阵中,对角元素为0,其余1。

有向图的邻接矩阵

在有向图的邻接矩阵中,第i行表示以结点v_i为箭尾的弧(即出度边),顶点的出度=行元素之和;第i列表示以结点v_i为箭头的弧(即入度边),顶点的入度=列元素之和。则顶点的度=第i行元素之和+第i列元素之和,有向图的邻接矩阵不一定是对称矩阵。

对于网,网是带权的图,它的邻接矩阵就不是简单的0和1,定义如下:

A.arcs[i][j]=\left\{\begin{matrix} W_{ij},if <i,j>\in E\, or \left ( i,j \right )\in E\\ \infty ,else \end{matrix}\right.

(b)存储表示

邻接矩阵的存储表示:用两个数组分别存储顶点表和邻接矩阵。

#define Maxlnt 32767  //表示极大值,即无穷大
#define MVNum 100  //最大顶点数
typedef char VerTexType;  //设顶点的数据类型为字符型,可以根据实际情况修改
typedef int ArcType;  //假设边的权值类型为整型,可以根据实际情况修改

typedef struct{
    VerTexType vexs[MVNum];  //顶点表
    ArcType arcs[MVNum][MVNum];  //邻接矩阵
    int vexnum, arcnum;  //图的当前点数和边数
}AMGraph; //Adjacency Matrix Graph

下面介绍一下邻接矩阵创建无向网的算法,有向图,有向网,无向图是类似的操作:

  • 输入总顶点数和总边数。
  • 依次输入点的信息存入顶点表中。
  • 初始化邻接矩阵,使每个权值初始化为极大值。
  • 构造邻接矩阵。
Status CreateUDN(AMGraph &G){  //采用邻接矩阵表示法,创建无向网G
    cin>>G.vexnum>>G.arcnum;  //输入总顶点数,总边数
    for(i = O; i<G.vexnum; ++i)
        cin>>G.vexs[i];  //依次输入点的信息
    for(i = 0; i<G.vexnum; ++i)  //初始化邻接矩阵
        for(j = 0; j<G.vexnum;++j)
            G.arcs[i][j] = Maxlnt;  //边的权值均置为极大值(无穷大)
    for(k = O; k<G.arcnum; ++k){  //构造邻接矩阵
        cin>>v1>>v2>>w;  //输入一条边所依附的顶点及边的权值
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);  //确定v1和v2在G.vexs中的位置
        G.arcs[i][j] = w;  //边<v1, v2>的权值置为w
        G.arcs[j][ti]= w;  //置<v1, v2>的对称边<v2,v1>的权值为w
    }//for
    return OK;
}  //CreateUDN

int LocateVex(AMGraph G, VertexType u){
//图G中查找顶点u,存在则返回顶点表中的下标;否则返回-1
    int i;
    for(i=O;i<G.vexnum;++i)
        if(u == G.vexs[i]) return i;
    return -1;
}

 (c)优缺点分析

优点:

  • 直观、简单、好理解;
  • 方便检查任意一对顶点间是否存在边;
  • 方便找任一顶点的所有“邻接点”(有边直接相连的顶点);
  • 方便计算任一顶点的“度”(从该点发出的边数为“出度”,指向该点的边数为“入度”);

缺点:

  • 不便于增加和删除顶点;
  • 浪费空间——存稀疏图(点很多而边很少)有大量无效元素(对稠密图(特别是完全图)还是很合算的);
  • 浪费时间——统计稀疏图中一共有多少条边,时间复杂度是O(n^2)

(2)链式存储结构(邻接表表示法)

(a)邻接表

顶点:按编号顺序将顶点数据存储在一维数组中;

关联同一顶点的边(以顶点为尾的弧):用线性链表存储;

其中表结点的info指边的权值,观察上面的无向图的邻接表,有以下特点:

  • 邻接表不唯一(只要是关联的同一个结点,这些边的顺序可以互换)
  • 若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图。
  • 无向图中顶点v_i的度为第i个单链表中的结点数。

对于有向图:邻接表存的是结点向外指出的结点。逆邻接表存的是指向这个结点的结点。

对有向图的(逆)邻接表,顶点v_i的出(入)度为第i个单链表中的结点个数;
对有向图的(逆)邻接表,顶点v_i的入(出)度为整个单链表中邻接点域值是i-1的结点个数;

(b)存储表示
//头结点
typedef struct VNode{  
    VerTexType data;  //顶点信息
    ArcNode *firstarc;  //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum]; //AdjList表示邻接表类型
//说明:例如,AdjList v;相当于: VNode v[MVNum];

//边结点
#define MVNum 100  //最大顶点数
typedef struct ArcNode{  
    int adjvex;  //该边所指向的顶点在头结点数组的位置
    struct ArcNode *nextarc;  //指向下一条边的指针
    OtherInfo info;  //和边相关的信息
}ArcNode;

//图的结构定义
typedef struct{
    AdjList vertices; //vertices是vertex的复数
    int vexnum,arcnum;  //图的当前顶点数和弧数
}ALGraph;

有了结构定义之后,下面就可以进行一些操作:

ALGragh G;  //定义了邻接表表示的图G
G.vexum=5; G.arcnum=5;  //图G包含5个顶点,5条边
G.vertices[1].data= 'b';  //图G中第2个顶点是b
p = G.vertices[1].firtarc;  //指针p指向顶点b的第一条边结点
p->adjvex = 4;  //p指针所指边结点是到下标为4的结点的边

下面介绍一下用邻接表建立无向网的步骤:

  • 输入总顶点数和总边数。
  • 建立顶点表:依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL
  • 创建邻接表:依次输入每条边依附的两个顶点,根据查找函数确定两个顶点在顶点表的序号i和j,建立边结点,将此边结点分别插入到v_i和v_j对应的两个边链表的头部。
Status CreateUDG(ALGraph &G){ //采用邻接表表示法,创建无向图G
    cin>>G.vexnum>>G.arcnum;  //输入总顶点数,总边数
    for(i = O; i<G.vexnum; ++i){  //输入各点,构造表头结点表
        cin>> G.vertices[i].data;  //输入顶点值
        G.vertices[i].firstarc = NULL;  //初始化表头结点的指针域为NULL
    }  //for
    for(k = O; k<G.arcnum;++k){  //输入各边,构造邻接表
        cin>>v1>>v2;  //输入一条边依附的两个顶点
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);  //在顶点表的数组中执行查找,返回下标

        p1 = new ArcNode;  //生成一个新的边结点*p1
        p1->adjvex = j;  //邻接点序号为j
        p1->nextarc = G.vertices[i].firstarc;
        G.vertices[i].firstarc = p1;  //将新结点*p1插入顶点vi的边表头部

        p2 = new ArcNode;  //生成另一个对称的新的边结点*p2
        p2->adjvex = i;  //邻接点序号为i
        p2->nextarc = G.vertices[j].firstarc;
        G.vertices[j].firstarc = p2;  //将新结点*p2插入顶点vj的边表头部
    }  //for
    return OK;
}  //CreateUDG

(c)优缺点
  • 方便找任一顶点的所有“邻接点”;
  • 节约稀疏图的空间:需要N个头指针+2E个结点(每个结点至少2个域);
  • 方便计算无向图任一顶点的“度”,但是对有向图来说只能计算“出度”;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”;
  • 不方便检查任一对顶点是否存在边;

与邻接矩阵的联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。

区别:(1)对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。(2)邻接矩阵的空间复杂度为O(n^2),而邻接表的空间复杂度为O(n+e)。(3)用途:邻接矩阵多用于稠密图;而邻接表多用于稀疏图。

(d)邻接表的优化:十字链表

后面两部分介绍一下对于邻接表的优化表。

十字链表(Orthogonal List)是有向图的另一种链式存储结构。我们也可以把它看成是将有向图的邻接表和逆邻接表结合起来形成的一种链表。有向图中的每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点。它们的结构如下:

顶点结点data域放置图结点信息,不解释;firstin存放第一条入弧,指向该结点的【有向边结点】的指针;firstout存放第一条出弧,指出该结点的【有向边结点】的指针;

弧结点放置有向边结点信息,tailvex域是方向尾(指出)结点在邻接表的下标,headvex域是方向头(指入)结点在邻接表的下标。例如一条有向边从5->2,那么tailvex=5,headvex=2。hlink和tlink都是指针域,hlink存放弧头相同的下一条弧,tlink存放弧尾相同的下一条弧。

有了十字链表之后,顺着头结点的firstin和firstout就可以很方便的看出出度和入度,从而计算结点的度。

(e)邻接表的优化:邻接多重表

对于无向图,邻接表每条边要存两遍。如何降低空间复杂度?

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

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

相关文章

前端工程化概述

软件工程定义&#xff1a;将工程方法系统化地应用到软件开发中 前端发展历史 前端工程化的发展历史可以追溯到互联网的早期阶段&#xff0c;随着前端技术的不断演进和互联网应用的复杂化&#xff0c;前端工程化也逐渐成为了前端开发的重要领域。以下是前端工程化的主要发展里程…

【了解一下常见的设计模式】

文章目录 了解一下常用的设计模式(工厂、包装、关系)导语设计模式辨析系列 工厂篇工厂什么是工厂简单工厂「模式」&#xff08;Simple Factory「Pattern」&#xff09;简单工厂代码示例&#xff1a;简单计算器优点&#xff1a;缺点&#xff1a; 静态工厂模式特点&#xff1a; 工…

手搭手入门MyBatis-Plus

MyBatis-Plus Mybatis-Plus介绍 为简化开发而生 MyBatis-Plus(opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis(opens new window) 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性 无侵入&#…

protobuf+netty自定义编码解码

protobufnetty自定义编 项目背景 protobufnetty自定义编码解码 比如心跳协议&#xff0c;客户端请求的协议是10001&#xff0c;在java端如何解码&#xff0c;心跳返回协议如何编码&#xff0c;将协议号带过去 // 心跳包 //10001 message c2s_heartbeat { }//10002 message …

LeetCode--HOT100题(38)

目录 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;226. 翻转二叉树&#xff08;简单&#xff09; 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 LeetCode做题链…

lvs-DR模式:

lvs-DR数据包流向分析 客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 Director Server 和 Real Server 在同一个网络中&#xff0c;数据通过二层数据链路…

7-42 整型关键字的散列映射

题目链接&#xff1a;这里 题目大意&#xff1a;就是写一个线性探测的散列 然鹅&#xff0c;我不会写(?)我一共错了两个地方 有冲突的情况下&#xff0c;就是线性探查然后往后找&#xff0c;但是我之前写的是t&#xff0c;应该是t (t1)%p;…在有重复关键字的时候&#xff0c…

大学生创业出路【第二弹】科创训练营

目录 &#x1f680;一、我从哪里了解到的训练营 &#x1f680;二、训练营里学习和日常 &#x1f50e;学习 &#x1f50e;环境和设备 &#x1f50e;遇到的人 &#x1f50e;团队记录视频 &#x1f680;三、感悟 ​​​​个人主页&#xff1a;一天三顿-不喝奶茶&#x1f39…

UE4/5Niagara粒子特效之Niagara_Particles官方案例:1.5->2.3

目录 之前的文章&#xff1a; 1.5 Blend Attributes by Value 发射器更新 粒子生成 粒子更新 2.1 Static Beams ​编辑 发射器更新&#xff1a; 粒子生成 粒子更新 2.2 Dynamic Beams 没有开始模拟前的效果是&#xff1a; 开始模拟后的效果是&#xff1a; 发射器更新 …

数据结构入门 — 顺序表详解

前言 数据结构入门 — 顺序表详解 博客主页链接&#xff1a;https://blog.csdn.net/m0_74014525 关注博主&#xff0c;后期持续更新系列文章 文章末尾有源码 *****感谢观看&#xff0c;希望对你有所帮助***** 文章目录 前言一、顺序表1. 顺序表是什么2. 优缺点 二、概念及结构…

java-IONIO

一、JAVA IO 1.1. 阻塞 IO 模型 最传统的一种 IO 模型&#xff0c;即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后&#xff0c;内 核会去查看数据是否就绪&#xff0c;如果没有就绪就会等待数据就绪&#xff0c;而用户线程就会处于阻塞状态&#xff0c;用户线…

java八股文面试[数据结构]——ArrayList和LinkedList区别

ArrayList和LinkedList的异同 二者的线程都不安全&#xff0c;相对线程安全的Vector,执行效率高。此外&#xff0c;ArrayList时实现了基于动态数组的数据结构&#xff0c;LinkedList基于链表的数据结构&#xff0c;对于随机访问get和set&#xff0c;ArrayList觉得优于LinkedLis…

线性回归的正则化改进(岭回归、Lasso、弹性网络),最小二乘法和最大似然估计之间关系,正则化

目录 最小二乘法 极大似然估计的思想 概率&#xff1a;已知分布参数-对分布参数进行估计 概率描述的是结果;似然描述的是假设/模型​编辑 似然&#xff1a;已知观测结果-对分布参数进行估计​编辑 对数函数消灭连乘-连乘导致算法参数消失 极大似然估计公式&#xff1a;将乘…

LeetCode:Hot100python版本之回溯

回溯算法其实是纯暴力搜索。for循环嵌套是写不出的 组合&#xff1a;没有顺序 排列&#xff1a;有顺序 回溯法可以抽象为树形结构。只有在回溯算法中递归才会有返回值。 46. 全排列 排列是有顺序的。 组合类问题用startindex&#xff0c;排序类问题用used&#xff0c;来标…

【网络】DNS | ICMP | NAT | 代理服务器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 前面几篇文章虽然讲介绍了整个网络通信的协议栈&#xff0c;我们也知道了完整的网络通信过程&#xff…

【图像去噪】基于混合自适应(EM 自适应)实现自适应图像去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

如何拉取Gitee / GitHub上的Unity项目并成功运行

前言 由于目前大部分人使用的仓库都是Gitee或者是GitHub&#xff0c;包括小编的公司所使用的项目仓库也包括了Gitee&#xff1b;我们需要学习技术栈时都会去百度或者是去GitHub上看看别人的项目观摩学习&#xff0c;可能很多小白在遇到拉取代码时出现各种问题&#xff0c;或者…

Server2016安装SQL server数据库遇到异常解决

首先看几个会出现的异常&#xff0c;下边看解决办法&#xff1a; 第一步: 先修改安装包x86\setup目录下的setupsql.exe,以Xp&#xff0c;SP3兼容模式运行&#xff0c; 这个右键&#xff0c;属性&#xff0c;兼容性&#xff0c;修改就行&#xff0c;类似这样 第二步: 修改c:…

【Rust】Rust学习 第十六章无畏并发

安全且高效的处理并发编程是 Rust 的另一个主要目标。并发编程&#xff08;Concurrent programming&#xff09;&#xff0c;代表程序的不同部分相互独立的执行&#xff0c;而 并行编程&#xff08;parallel programming&#xff09;代表程序不同部分于同时执行&#xff0c;这两…

【优选算法】—— 字符串匹配算法

在本期的字符串匹配算法中&#xff0c;我将给大家带来常见的两种经典的示例&#xff1a; 1、暴力匹配&#xff08;BF&#xff09;算法 2、KMP算法 目录 &#xff08;一&#xff09;暴力匹配&#xff08;BF&#xff09;算法 1、思想 2、演示 3、代码展示 &#xff08;二&…