图的存储(邻接矩阵,边集数组,邻接表,链式前向星)

news2024/11/25 13:37:55

目录

🌼图的存储

(1)邻接矩阵

(2)边集数组

(3)邻接表

(4)链式前向星

😀刷题

🐍最大节点

🐍有向图 D 和 E

🐍奶牛排序


🌼图的存储

(1)邻接矩阵

adjacency matrix(邻接矩阵)

#define MaxVnum 100 // 节点数最大值
typedef char VexType; // 节点的数据类型, 根据需要定义
typedef int EdgeType; // 边上权值的数据类型; 不带权的图, 为0 / 1

结构体 

typedef struct {
    VexType Vex[MaxVnum]; // 节点类型 + 一维数组存储节点
    EdgeType Edge[MaxVnum][MaxVnum]; // 边上权值类型 + 邻接矩阵存储边
    int vexnum, edgenum; // 节点数, 边数
} AMGraph;

算法

void CreateAMGraph(AMGraph &G) { // 引用传递, 直接修改对象本身
    // AMGraph 包含一维节点数组, 二维邻接矩阵数组, 节点数, 边数
    int i, j;
    VexType u, v; // 节点类型
    cin >> G.vexnum;
    cin >> G.edgenum;

    for (int i = 0; i < G.vexnum; i++) 
        cin >> G.Vex[i]; // 输入节点
    
    for (int i = 0; i < G.vexnum; i++)
        for (int j = 0; j < G.vexnum; j++) 
            G.Edge[i][j] = 0; // 初始化邻接矩阵所有值为 0
    
    while(G.edgenum--) {
        cin >> u >> v; // 'a' 'b' ...
        i = locatevex(G, u); // 查找节点 u 对应下标
        j = locatevex(G, v); // 查找节点 v 对应下标
        if(i != -1 && j != -1)
            G.Edge[i][j] = G.Edge[j][i] = 1; // 无向图两点连通
    }
}

优点

(1)快速判断两点间,是否有边(Edge[i][j] == 1 有边)

(2)快速计算各节点的度

(无向图,邻接矩阵,第 i 行元素之和,就是节点 i 的 度)

(有向图,邻接矩阵,第 i 行的和为 出度,第 i 列的和为 入度)

缺点

(1)不利于增删节点(需要改变邻接矩阵大小,效率低)

(2)不利于访问邻接点

访问第 i 个点所有邻接点时,需要遍历第 i 行,时间复杂度 O(n) 

访问所有点邻接点,时间复杂度 O(n^2) 

(3)空间复杂度高,O(n^2)

初始可以这样定义👇(省去节点信息的查询步骤)

int M[m][n] = { {0,1,0,1}, {1,0,1,1}, {0,1,0,1}, {1,1,1,0} };

直接定义一个邻接矩阵

(2)边集数组

数组存储每条边的起点和终点,以下是网的结构体定义(增加了一个权值域)

适用于 最小生成树 kruskal算法 

struct Edge {
    int u;
    in v;
    int w;
} e[N*N];

(3)邻接表

邻接表,包括节点和邻接点

节点

typedef struct VexNode { // 定义节点类型    
    VexType data; // VexType为节点信息的数据类型, 根据需要定义
    AdjNode *first; // 指向第1个邻接点
}VexNode;

邻接点

typedef struct AdjNode { // 定义邻接点类型
    int v; // 下标v
    struct AdjNode *next; // 指向下一个邻接点
}AdjNode;

邻接表结构体

typedef struct {
    VexNode Vex[MaxVnum]; // 节点表
    int vexnum, edgenum; // 节点数, 边数
} ALGraph;

算法代码

void CreateALGraph(ALGraph &G) { // 创建有向图的邻接表
    VexType u, v; // 节点类型

    cout << "请输入节点数和边数:" << endl;
    cin >> G.vexnum >> G.edgenum;

    cout << "请输入节点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++) // 节点数 vexnum
        cin >> G.Vex[i].data;
    for (int i = 0; i < G.vexnum; i++) 
        G.Vex[i].first = NULL; // 下一个邻接点置空

    cout << "请依次输入每条边的两个节点 u, v" << endl;
    while (G.edgenum--) {
        cin >> u >> v;
        int i = locatevex(G, u); // 查找节点 u 的下标
        int j = locatevex(G, v); // 查找节点 v 的下标
        if (i != -1 && j != -1)
            insertedge(G, i, j); // 插入边
        // 无向图多插入 1 次
    }
}

插入代码

void insertedge(ALGraph &G, int i, int j) // 头插法(插入一条边)
{
    AdjNode *s; // 新的邻接点
    s = new AdjNode; // 开辟内存
    // i 和 j相连, 所以是向 j 出边
    s->v = j; // 邻接点下标
    s->next = G.Vex[i].first; // 新的邻接点的下一个, 是原来节点 i 的下一个
    G.Vex[i].first = s; // 原来节点 i 的下一个, 变成邻接点
}

邻接表优点

(1)便于增删节点

(2)便于访问所有邻接点( 时间复杂度O(n + e) )

(3)空间复杂度低( 节点表 n 个空间 ,无向图邻接点表 n + 2e 空间,有向图临界点表 n + e 空间,所以空间复杂度O(n + e) ),而邻接矩阵空间复杂度 O(n^2)

存储图稀疏图 -> 邻接表稠密图 -> 邻接矩阵

缺点

(1)不利于判断两节点是否有边(需要遍历该节点后,整条链表)

(2)不利于计算各节点的度

无向图为该节点后单链表节点数

有向图(邻接表)的出度为 单链表节点数,但不易求入度

有向图(逆邻接表)的入度为 单链表节点数,但不易求出度

总体上,邻接表,访问同一节点所有关联边时,仅需访问该点后单链表,这是一大优势 

(4)链式前向星

链式前向星——最完美图解-腾讯云开发者社区-腾讯云 (tencent.com)

(👆算法训练营原文) 

链式前向星--最通俗易懂的讲解-CSDN博客

链式前向星,即静态链表,边集数组 + 邻接表

可快速访问一个节点的所有邻接点

(1)边集数组:edge[i],第 i 条边

(2)头节点数组:head[k],存储以 顶点 k 为起点的第 1 条边的下标(edge[i] 中的 i,即第几条边)

结构体 

struct node {
    int to, next, w;
    
}edge[maxe]; // 边集数组, 对边数的设置要比 maxn*maxn大

int head[maxn]; // 头节点数组

next

与edge[cnt](第 cnt 条边)起点相同的上一条边的编号(之所以是上一条,不是下一条,因为邻接表采取头插法 -- 逆序) 

添加边 u,v,w

void add(int u, int v, int w) 
{
    // 第 cnt 条边
    edge[cnt].to = v; // 终点
    edge[cnt].w = w; // 权值
    edge[cnt].next = head[u]; // -1时没有上一条边

    head[u] = cnt++; // 先赋值, 后自增
    // 起点 u 为顶点的, 第一条边是 cnt, 然后 cnt++, 进入下一条边
}

有向图:一次 add(u, v, w)

无向图:两次 add(u, v, w) 和 add(v, u, w)

访问一个节点 u 所有邻接点

当 edge[i].next != -1,说明还有邻接点 

for (int i = head[u]; i != -1; i = edge[i].next)
{
    int v = edge[i].to; // 顶点 u 的邻接点
    int w = edge[i].w; // u-v 这条边的权值
}

特点

(1)类似邻接表,头插法(倒序)进行链接 --> 边的输入顺序不同,创建的链式前向星也不同

(2)无向图的话,每输入一条边,需要添加 2 条边,又因为边从 0 开始,比如 edge[0]和edge[1],edge[2]和edge[3],4和5等等(即 0000和0001,0010和0011,0100和0101),此时,两条反向边,可以互相异或 1 得到另一条边,i 和 i^1

这个特性在网络流中较为常用(异或运算,不需要额外的存储 / 操作,节省空间和时间)

(3)整合了边集数组和邻接表,属于静态链表,不需要频繁创建节点

😀刷题

🐍最大节点

P3916 图的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路 

本题,求,从节点 v 出发,能到达的最大节点 u

建立原图的反向图,即 add(u, v) 变成 add(v, u)

然后从节点 n 开始递归,一直到节点 1

对于当前最大节点 u,凡是能到达的节点 v,v能到达的最大节点就是 u

解释 

结合图理解👇

节点 1 的第 1 条边是 edge[0],边的编号为 0,0号边即 节点1 到 节点2,距离(权值)为 5 的这条边,这条边的终点是 2(to),权值是 5(w),没有上一条边(next == -1)

if (ans[v]) return;

两种可能的解释👆

(1)已经得到最远的点,避免后续多余的计算

(2)已经计算过的点,因为是反向的,此时再计算,可能会把无法到达的点算进去,导致结果错误

AC  代码

#include<iostream>
#include<cstdio> // scanf()
#include<cstring> // memset()
using namespace std;

const int MAX = 100010;
int n, m, cnt = 0, head[MAX], ans[MAX];

struct node
{
    int to, next;
}e[MAX];

// 添加 u 到 v 的边
void add(int u, int v)
{
    e[cnt].to = v; // 终点
    e[cnt].next = head[u]; // 节点 u 的第一条边, 就是它的上一条边
    head[u] = cnt++; // 先赋值, 再自增
}

// 递归遍历所有邻接边
// 注意是倒序反向遍历, 所以 u 是最远可到达的点
void dfs(int u, int v)
{
    if (ans[v]) return; // 已经得到最远点
    ans[v] = u; // 最远的点
    // 递归遍历所有邻接边
    for (int i = head[v]; i != -1; i = e[i].next) { // head[v] 节点 v 的第1条边
        int v1 = e[i].to; // 邻接点
        dfs(u, v1); // 最大节点 u 所能到达的点 v1
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    // 初始化 头节点 数组
    memset(head, -1, sizeof(head));

    // 添加反向边
    int u, v;
    while(m--) {
        scanf("%d%d", &u, &v);
        add(v, u);
    }

    // 倒序 dfs 递归
    for (int i = n; i >= 1; --i)
        dfs(i, i);

    // 输出
    for (int i = 1; i <= n; ++i)
        cout << ans[i] << " ";

    return 0;
}

🐍有向图 D 和 E

From D to E and Back - UVA 11175 - Virtual Judge (vjudge.net)

思路 

把 D 的边缩成点,D 的边对应 E 的点

如果D存在边 i (u, v),j (v, w),那么 E 存在点 i, j,以及一条 i 到 j 的边

因为 D 和 E,都是有向图(关键)

所以, 若 D 中,边 i, j 有公共端点,那么 i 连接的边,j 一定也连接

换言之,E 中,点 i, j 有公共连接点 k,那么,此时如果 i 有邻接点 k2,j 必定也邻接 k2(因为 E 是有向图)

即,E中,点 i 和 点 k1 有边,点 j 和 k2 也有边,此时若点 i 和 k2 有边,j 必定也和 k2 有边

解释

第一个

宏定义

第二个

REP(i,b,e)

REP后要紧跟着 (i,b,e),不要加个空格,否则报错:i was not defined in this scope

第三个

flag1, flag2 的声明,应该放在 i, j 两重循环内,而不是放在 check() 里

因为 return false,是针对相同的 i, j 去遍历每一个 k 的

第四个

每组测试,读入的是 m,边数

第五个

注意Yes,No之前有空格,直接复制题目会错

第六个

为什么用邻接矩阵存储呢,需要不断判断两个两个节点之间是否有边

AC  代码

远程 OJ 崩了,样例过了,就当过了吧

#include<bits/stdc++.h>
#define REP(i,b,e) for(int i=(b); i<(e); ++i)
using namespace std;

const int N = 310;
int g[N][N], n, m; // g 邻接矩阵

bool check()
{
    REP(i, 0, n) // 0~n-1
        REP(j, 0, n) { //0~n-1
            int flag1 = 0, flag2 = 0;
            REP(k, 0, n) { // 0~n-1
                if (g[i][k] && g[j][k]) flag1 = 1; // 共同邻接点
                if (g[i][k] ^ g[j][k]) flag2 = 1; // 一方有, 一方没有
            }
            if (flag1 && flag2) return false;
        }
    return true;
}

int main()
{
    int T, cnt = 1;
    cin >> T;

    int x, y;
    while (T--) {
        memset(g, 0, sizeof(g)); // 初始化邻接矩阵
        cin >> n >> m;
        REP(i, 0, m) { // 读取 m 条边
            cin >> x >> y;
            g[x][y] = 1;
        }
        if (check())
            printf("Case #%d: Yes\n", cnt++);
        else
            printf("Case #%d: No\n", cnt++);
    }

    return 0;
}

🐍奶牛排序

Ranking the Cows - POJ 3275 - Virtual Judge (vjudge.net)

思路 

样例 

奶牛理解为有向图的节点,关系理解为边

对于 n 个节点的图,两两之间的关系有, 1 + ... + n-1 种,即 n(n-1) / 2

样例中 5 个节点,就有 10 种关系

然后根据给出的 5 条边,又能间接得到另外的 2 条边

所以10 - (5 + 3)= 3

还需要知道 3 种关系

位运算 

那么,如何得到已知的关系是 7 种呢

每个节点用一个 bitset 表示👇目录中 2.4.7 bitset

STL入门 + 刷题(下)_千帐灯无此声的博客-CSDN博客

bitset<maxn>p[maxn]; // maxn表示位数, p[] 二进制数组

初始化:p[i][i] = 1,表示自身可达(即自己和自己有关系)

输入 

输入 2 1  (其他输入同理)

即 2 -> 1 的有向边,2 可达 1,那么 p[2][1] = 1,p[2] = 000110👇

右边第 0 位开始,第 2 位为 1,表示自身可达,第 1 位为 1,即节点 2 可达 节点 1

根据已知点找其他

if (p[i][k])
    p[i] |= p[k];

// 等价于

if (p[i][k])
    p[i] = p[i] | p[k];

按位或:同时为 0 才为 0

可以间接找到每个点与其他点的关系👇

(比如节点 a 可达节点 b,b 可达 c,那么间接得到 a 也可达 c)

计算结果

两两间,总的关系数:n(n-1) / 2

用 ans 累计每个 bitset 数组 p[i] 中 1 的个数

由于 ans 中包括 n 种自己到自己的关系

所以已知关系为 ans - n

输出答案为 总数 - 已知数 =

n(n-1) / 2 - ans + n

解释

看懂代码后,有个疑问,如果最后还差 5 个关系,是否存在,只需要再调查 2 个关系,就能间接得到剩下 3 个关系的可能呢(可能是出题者结果导向,或者我的理解有偏差)

👆如果按这个意思,那么题目会复杂很多👆

编号从 1 开始,所以如果你所有 for 循环都是 0 ~ n-1,就会 Wrong Answer

AC  代码

#include<iostream> // 编号 1 开始
#include<bitset>
using namespace std;

const int N = 1010;
// N 表示长度为 N 的位集合
bitset<N> p[N]; // 数组 p 的每个元素都是 bitset<N> 类型, 即一个二进制数

int main()
{
    int n, m, x, y, ans = 0;
    cin >> n >> m;

    // 初始化 bitset 数组
    for (int i = 1; i <= n; ++i)
        p[i][i] = 1; // 自己和自己有联系
    while (m--) {
        cin >> x >> y;
        p[x][y] = 1; // 节点 x 可达 y
    }
    // 间接求其他关系
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j) 
            if (p[j][i])
                p[j] = p[j] | p[i];
    // 输出
    for (int i = 1; i <= n; ++i)
        ans += p[i].count(); // 统计每个 bitset 中 1 的个数
    cout << n*(n-1)/2 - ans + n << endl;

    return 0;
}

除了常规意义的 邻接矩阵,边集数组,邻接表,链式前向星(存储图)外

我们还可以考虑 bitset,用二进制来存图

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3 文本分类入门finetune:bert-base-chinese

项目实战&#xff1a; 数据准备工作 bert-base-chinese 是一种预训练的语言模型&#xff0c;基于 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;架构&#xff0c;专门用于中文自然语言处理任务。BERT 是由 Google 在 2018 年提出的一…

Leetcode—1466.重新规划路线【中等】

2023每日刷题&#xff08;五十二&#xff09; Leetcode—1466.重新规划路线 算法思想 实现代码 class Solution { public:int minReorder(int n, vector<vector<int>>& connections) {vector<pair<int, int>> g[n];for(auto e: connections) {in…

“掌握高效视频分割技巧,降低误差,提高精度“

如果你是一名视频编辑爱好者或者专业人士&#xff0c;那么你一定会在视频剪辑的过程中遇到各种挑战。其中&#xff0c;如何准确高效地进行视频分割是一个至关重要的问题。现在&#xff0c;我们将向你展示一种全新的解决方案&#xff0c;帮助你轻松解决这些问题。 首先第一步&a…

Vue使用百度地图以及实现轨迹回放 附完整代码

百度地图开放平台 https://lbs.baidu.com/index.php?title%E9%A6%96%E9%A1%B5 javaScript API https://lbs.baidu.com/index.php?titlejspopularGL 百度地图实例 https://lbsyun.baidu.com/index.php?titleopen/jsdemoVue Baidu Map文档 https://dafrok.github.io/vue-baidu…

数据结构与算法:红黑树

目录 什么是红黑树 ​编辑 红黑树的性质 红黑树的辨析 红黑树实现 红黑树的结构 红黑树的插入 红黑树的调试 红黑树平衡判断 什么是红黑树 这里引入一下NIL节点的概念&#xff1a; NIL节点也称为空节点或外部节点&#xff0c;是红黑树中一种特殊的节点类型。NIL节点不…

DeepIn,UOS统信专业版安装运行Java,JavaFx程序

因为要适配国产统信UOS系统&#xff0c;要求JavaFx程序能简便双击运行&#xff0c;由于网上UOS开发相关文章少&#xff0c;多数文章没用&#xff0c;因此花了不少时间&#xff0c;踩了不少坑&#xff0c;下面记录一些遇到的问题&#xff0c;我的程序环境是jdk1.8&#xff0c;为…

应用程序中实现用户隐私合规和数据保护合规的处理方案及建议

随着移动互联网的发展&#xff0c;用户隐私合规和数据保护合规已经成为应用开发过程中不可忽视的重要环节。为了帮助开发者实现隐私和数据保护合规&#xff0c;本文将介绍一些处理方案和建议。 图片来源&#xff1a;应用程序中实现用户隐私合规和数据保护合规的处理方案及建议 …

720度vr虚拟家居展厅提升客户的参观兴致

VR虚拟展厅线上3D交互展示的优势有以下几点&#xff1a; 打破了场馆的展示限制&#xff0c;可展示危险性制品、珍贵稀有物品、超大型设备等&#xff0c;同时提供了更大的展示空间和更丰富的展示内容。 可提供企业真实环境的实时VR全景参观&#xff0c;提升潜在客户信任度。 提供…

【附源码】完整版,Python+Selenium+Pytest+POM自动化测试框架封装

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试框架简介 …

【S2ST】Direct Speech-to-Speech Translation With Discrete Units

【S2ST】Direct Speech-to-Speech Translation With Discrete Units AbstractIntroductionRelated workModelSpeech-to-unit translation (S2UT) modelMultitask learningUnit-based vocoder ExperimentsDataSystem setupBaselineASRMTTTSS2TTransformer Translatotron Evaluat…

【广州华锐互动】VR沉浸式体验铝厂安全事故让伤害教育更加深刻

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为我们的生活带来了前所未有的便捷和体验。在安全生产领域&#xff0c;VR技术的应用也日益受到重视。 VR公司广州华锐互动就开发了多款VR安全事故体验系统&#xff0c…

大数据技术2:大数据处理流程

前言&#xff1a;下图是一个简化的大数据处理流程图&#xff0c;大数据处理的主要流程包括数据收集、数据存储、数据处理、数据应用等主要环节。 1.1 数据收集 大数据处理的第一步是数据的收集。现在的中大型项目通常采用微服务架构进行分布式部署&#xff0c;所以数据的采集需…

如何使用Docker本地搭建开源CMF Drupal并结合内网穿透公网访问

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它的学习…

【目标检测算法】IOU、GIOU、DIOU、CIOU

目录 参考链接 前言 IOU(Intersection over Union) 优点 缺点 代码 存在的问题 GIOU(Generalized Intersection over Union) 来源 GIOU公式 实现代码 存在的问题 DIoU(Distance-IoU) 来源 DIOU公式 优点 实现代码 总结 参考链接 IoU系列&#xff08;IoU, GIoU…

Java程序员,你掌握了多线程吗?(文末送书)

目录 01、多线程对于Java的意义02、为什么Java工程师必须掌握多线程03、Java多线程使用方式04、如何学好Java多线程送书规则 摘要&#xff1a;互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流…

Java API接口强势对接:构建高效稳定的系统集成方案

文章目录 1. Java API接口简介2. Java API接口的优势2.1 高度可移植性2.2 强大的网络通信能力2.3 多样化的数据处理能力 3. 实战&#xff1a;Java API接口强势对接示例3.1 场景描述3.2 用户管理系统3.3 订单处理系统3.4 系统集成 4. 拓展&#xff1a;Java API接口在微服务架构中…

麒麟信安系统下的硬盘分区情况说明

目前飞腾平台上面麒麟信安系统分区情况如下&#xff1a; Tmpfs为内存文件系统&#xff0c;可以不考虑&#xff0c;真正使用的是两个分区 两个分区加起来为51G 查看cat /etc/fstab可以看到/data这个分区下包含了home opt root等常用文件夹 再加上这个分区容量只有17G&#xff0c…

基于Browscap对浏览器工具类优化

项目背景 原有的启动平台公共组件库comm-util的浏览器工具类BrowserUtils是基于UserAgentUtils的&#xff0c;但是该项目最后一个版本发布于 2018/01/24&#xff0c;之至今日23年底&#xff0c;已有5年没有维护更新&#xff0c;会造成最新版本的部分浏览器不能正确获取到浏览器…

嵌入式工程师校招经验与学习路线总结

前言&#xff1a;不知不觉2023年秋招已经结束&#xff0c;作者本人侥幸于秋招中斩获数十份大差不差的OFFER&#xff0c;包含&#xff1a;Top级的AIGC&#xff0c;工控龙头&#xff0c;国产MCU原厂&#xff0c;医疗器械&#xff0c;新能源车企等。总而言之&#xff0c;秋招总体情…

NR重写console.log 增加时间格式

如题&#xff0c;默认console.log输出的日志是13位的时间戳&#xff0c;然后不方便查查看与对比代码运行点的耗时&#xff0c;我们可以简单的重写 console.log方法&#xff0c;增加自定义时间戳格式&#xff0c;如下是增加时间&#xff08;时&#xff0c;分&#xff0c;秒&…