C++处理图存储的方式分享

目录
  • 一、邻接矩阵
  • 二、邻接表
  • 三、链式前向星
    • 1、AcWing方式(纯数组)
  • 三、Acwing图的存储方式
    • 2、复杂度
    • 2、应用
    • 3、邻接表
    • 4、代码实现
    • 5、插入边
  • 四、遍历
    • 1、深度优先遍历
    • 2、广度优先遍历
    • 3、复杂度
    • 4、应用
    • 5、实现案例
    • 6、 结构体+数组
    • 7、 结构体+数组(2)

一、邻接矩阵

适用:

稠密图,就是说点数的平方与边数接近的情况,换句话说就是边特别多。

不适用:

稀疏图,就是点数的平方与边数差的特别多,边数少,但点数多,就不行了,因为空间占用太大了。

实现代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 1010; //图的最大点数量
int n;
int v[N][N];        //邻接矩阵
/**
 * 测试数据
 4
 0 5 2 3
 5 0 0 1
 2 0 0 4
 3 1 4 0
 */
int main() {
    cin >> n;
    //读入到邻接矩阵
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> v[i][j];

    //下面的代码将找到与点i有直接连接的每一个点以及那条边的长度
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (v[i][j]) cout << "edge from point " 
                << i << " to point " << j << " with length " << v[i][j] << endl;
    return 0;
}

二、邻接表

#include <bits/stdc++.h>

using namespace std;

const int N = 1010; //图的最大点数量
struct Edge {       //记录边的终点,边权的结构体
    int to;         //终点
    int value;      //边权
};
int n, m; //表示图中有n个点,m条边
vector<Edge> p[N];  //使用vector的邻接表

/**
 * 测试数据
 4 6
 2 1 1
 1 3 2
 4 1 4
 2 4 6
 4 2 3
 3 4 5
 */
int main() {
    cin >> n >> m;
    //m条边
    for (int i = 1; i <= m; i++) {
        int u, v, l;                //点u到点v有一条权值为l的边
        cin >> u >> v >> l;
        p[u].push_back({v, l});
    }

    //输出
    for (int i = 1; i <= n; i++) {
        printf("出发点:%d ", i);
        for (int j = 0; j < p[i].size(); j++)
            printf(" 目标点:%d,权值:%d;", p[i][j].to, p[i][j].value);
        puts("");
    }

    return 0;
}

三、链式前向星

链式前向星是邻接表存图的第二种方法,它自己还有两种写法,比 用向量存图的那种邻接表要快 。

它是一种以边为主的存图方式,idxidx表示最后一条边的预存入的房间号,$head[i$]表示以$i$为起点第一条边的房间号。

每条边有三个属性:

  • $head[i]$出发到哪个结点的边?
  • 这条边的边权是多少?
  • 这条边的下一条边是谁?(下一条边的房间号)

链式前向星有三种变形,需要同学们都掌握,找一种自己最喜欢的背下来,其它两种要求能看懂,因为其它人写题解,可能使用了其它方式。

1、AcWing方式(纯数组)

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;     //点数最大值
int n, m;               //n个点,m条边

//idx是新结点加入的数据内索引号
//h[N]表示有N条单链表的头,e[M]代表每个节点的值,ne[M]代表每个节点的下一个节点号
int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;

//链式前向星
void add(int a, int b, int l) {
    e[idx] = b, ne[idx] = h[a], w[idx] = l, h[a] = idx++;
}

/**
 * 测试数据
 4 6
 2 1 1
 1 3 2
 4 1 4
 2 4 6
 4 2 3
 3 4 5
 */
int main() {
    cin >> n >> m;
    //初始化为-1,每个头节点写成-1
    memset(h, -1, sizeof h);

    //m条边
    for (int i = 1; i <= m; i++) {
        int u, v, l;                //点u到点v有一条权值为l的边
        cin >> u >> v >> l;
        //加入到链式前向星
        add(u, v, l);
    }

    //遍历每个结点
    for (int i = 1; i <= n; i++) {
        printf("出发点:%d ", i);
        for (int j = h[i]; j != -1; j = ne[j])
            printf(" 目标点:%d,权值:%d;", e[j], w[j]);
        puts("");
    }
    return 0;
}

三、Acwing图的存储方式

方法:使用一个二维数组 g 来存边,其中 g[u][v] 为 1 表示存在 到的边,为 0 表示不存在。如果是带边权的图,可以在 g[u][v] 中存储到的边的边权。

案例:

最短距离Dijkstra

从s到t的最短距离算法流程:

b[]表示当前已经确定最短距离的点。

dis[s] = 0, dis[其他] = +∞

for (int i = 1; i <= n; i ++)

t:不在b中的最短距离的点

将t加入b[]

使用t更新其他未被确定的点的距离

代码实现:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 510;

int n, m;
int w[N][N];
int dis[N];
bool b[N];

int dijkstra() {
    memset(dis, 0x3f, sizeof dis);
    dis[1] = 0;

    for (int i = 0; i < n; i ++) {
        int k = -1;
        for (int j = 1; j <= n; j ++)
            if (!b[j] && (k == -1 || dis[k] > dis[j]))
                k = j;

        b[k] = true;

        for (int j = 1; j <= n; j ++) {
            dis[j] = min(dis[j], dis[k] + w[k][j]);
        }
    }

    if (dis[n] == 0x3f3f3f3f) return -1;
    else return dis[n];
}

int main() {
    scanf("%d %d", &n, &m);

    memset(w, 0x3f, sizeof w);

    while (m --) {
        int i, j, k;
        scanf("%d %d %d", &i, &j, &k);
        w[i][j] = min(w[i][j], k);
    }

    int t = dijkstra();

    printf("%d", t);
    return 0;
}

2、复杂度

2、应用

邻接矩阵只适用于没有重边(或重边可以忽略)的情况。

其最显著的优点是可以查询一条边是否存在。

由于邻接矩阵在稀疏图上效率很低(尤其是在点数较多的图上,空间无法承受),所以一般只会在稠密图上使用邻接矩阵。

3、邻接表

使用一个支持动态增加元素的数据结构构成的数组,如 vector g[n + 1] 来存边,其中 g[u] 存储的是点的所有出边的相关信息(终点、边权等)。

4、代码实现

数据定义:

h是n个链表的链表头, e存的是每一个节点的值, ne存的是 next指针是多少。

int h[N], e[M], ne[M], idx;
bool st[N];

5、插入边

插入一条a指向b的边

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

四、遍历

1、深度优先遍历

void dfs(int u) {
    st[u] = true;    // 标记已经被遍历过了
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

2、广度优先遍历

void bfs() {
    int q[N];    // 定义队列 
    int hh = 0, tt = 0;    // 头和尾指针 
    memset(st, 0, sizeof st);
    q[0] = 1;
    while (hh <= tt) {
        int t = q[hh ++];
        st[t] = true;
        cout << t << ' ';
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (!st[j]) {
                q[++ tt] = j;
            }
        }
    }
}

3、复杂度

4、应用

存各种图都很适合,除非有特殊需求(如需要快速查询一条边是否存在,且点数较少,可以使用邻接矩阵)。

尤其适用于需要对一个点的所有出边进行排序的场合。

5、实现案例

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1e5 + 10, M = N * 2;

// h是n个链表的链表头, e存的是每一个节点的值, ne存的是 next指针是多少。 
int h[N], e[M], ne[M], idx;
bool st[N];
int n;    // n条边 

// 插入一条a指向b的边 
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

// 深度优先遍历
void dfs(int u) {
    cout << u << ' ';
    st[u] = true;    // 标记已经被遍历过了
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

// 广度优先遍历 
void bfs() {
    int q[N];    // 定义队列 
    int hh = 0, tt = 0;    // 头和尾指针 
    memset(st, 0, sizeof st);
    q[0] = 1;
    while (hh <= tt) {
        int t = q[hh ++];
        st[t] = true;
        cout << t << ' ';
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (!st[j]) {
                q[++ tt] = j;
            }
        }
    }
}

int main () {
    memset(h, -1, sizeof h);
    cin >> n;

    for (int i = 1; i <= n; i ++) {
        int a, b;
        cin >> a >> b; 
        add(a, b);
        add(b, a);
    }

    cout << "深度优先遍历:";
    dfs(1);
    cout << endl;
    cout << "广度优先遍历:";
    bfs(); 
    return 0;
}

6、 结构体+数组

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;     //点数最大值
int n, m, idx;          //n个点,m条边,idx是新结点加入的数据内索引号

//链式前向星
struct Edge {
    int to;     //到哪个结点
    int value;  //边权
    int next;   //同起点的下一条边的编号
} edge[N << 1]; //同起点的边的集合 N<<1就是2*N,一般的题目,边的数量通常是小于2*N的,这个看具体的题目要求

int head[N];    //以i为起点的边的集合入口处

//加入一条边,x起点,y终点,value边权
void add_edge(int x, int y, int value) {
    edge[++idx].to = y;         //终点
    edge[idx].value = value;    //权值
    edge[idx].next = head[x];   //以x为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[x] = idx;              //更新以x为起点上一条边的编号
}

/**
 * 测试数据
 4 6
 2 1 1
 1 3 2
 4 1 4
 2 4 6
 4 2 3
 3 4 5
 */
int main() {
    cin >> n >> m;

    //m条边
    for (int i = 1; i <= m; i++) {
        int u, v, l;                //点u到点v有一条权值为l的边
        cin >> u >> v >> l;
        //加入到链式前向星
        add_edge(u, v, l);
    }

    //遍历每个结点
    for (int i = 1; i <= n; i++) {
        printf("出发点:%d ", i);
        for (int j = head[i]; j; j = edge[j].next)  //遍历每个结点的每一条边
            printf(" 目标点:%d,权值:%d;", edge[j].to, edge[j].value);
        puts("");
    }
    return 0;
}

7、 结构体+数组(2)

为什么链式前向星有两种实现方法呢?这其实是看用不用的问题,如果它用了,那么就是在加边的最后需要++,如果不用,进来就++。

第二个变化就是如果用了,那么就不能用做默认值了,所以需要初始化memset(head,-1 ,sizeof head);

第三个变化就是遍历时的条件变了,成了j!=-1,而不用的就是j就行了,我个人还是喜欢用不带的那个,就是上面的。是因为网上好多网友喜欢这种方式,如果我们看其它人的题解时,可能看不懂,所以也要了解一下。

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;     //点数最大值
int n, m, idx;          //n个点,m条边,idx是新结点加入的数据内索引号

//链式前向星
struct Edge {
    int to;     //到哪个结点
    int value;  //边权
    int next;   //同起点的下一条边的编号
} edge[N << 1]; //同起点的边的集合 N<<1就是2*N,一般的题目,边的数量通常是小于2*N的,这个看具体的题目要求

int head[N];    //以i为起点的边的集合入口处

//加入一条边,x起点,y终点,value边权
void add_edge(int x, int y, int value) {
    edge[idx].to = y;           //终点
    edge[idx].value = value;    //权值
    edge[idx].next = head[x];   //以x为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[x] = idx++;            //更新以x为起点上一条边的编号
}

/**
 * 测试数据
 4 6
 2 1 1
 1 3 2
 4 1 4
 2 4 6
 4 2 3
 3 4 5
 */
int main() {
    cin >> n >> m;

    //初始化head数组
    memset(head, -1, sizeof head);

    //m条边
    for (int i = 1; i <= m; i++) {
        int u, v, l;                //点u到点v有一条权值为l的边
        cin >> u >> v >> l;
        //加入到链式前向星
        add_edge(u, v, l);
    }

    //遍历每个结点
    for (int i = 1; i <= n; i++) {
        printf("出发点:%d ", i);
        for (int j = head[i]; j != -1; j = edge[j].next)  //遍历每个结点的每一条边
            printf(" 目标点:%d,权值:%d;", edge[j].to, edge[j].value);
        puts("");
    }
    return 0;
}

到此这篇关于C++处理图存储的方式分享的文章就介绍到这了,更多相关C++处理图存储内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++浮点型的存储方式详解

    目录 浮点型及其存储方式 一.IEEE浮点标准 二.存储方式 IEEE 754对有效数字M和指数E的规定. 重点: 根据指数域不同取值分为一下三种情况: 总结 浮点型及其存储方式 有些时候需要变量能存储带小数点的数,或者能存储极大数或极小数.这类数可以用浮点(因小数点是"浮动的"而得名)格式进行存储.C语言提供了3种浮点类型,对应三种不同的浮点格式. 当精度要求不严格时(小数点后少于六位),float类型是很适合的类型.double提供更高的精度, 对绝大多数程序来说够用了.longd

  • EasyC++自动存储持续性

    转自微信公众号:Coder梁(ID:Coder_LT) 自动存储持续性: 这个概念乍一看有些拗口,其实它很简单,指的是在函数定义中声明的变量的存储持续性是自动的:它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放. 在默认情况下,我们在函数中声明的变量和函数的参数都是自动存储持续性,它的作用于为局部,没有链接性. 这里的链接性描述了名称如何在不同的单元之间共享,链接性为外部的名称可以在文件之间共享,链接性为内部的名称只能由一个车文件中的函数共享.自动变量

  • C++存储方案和动态分配

    目录 存储方案和动态分配 初始化 存储方案和动态分配 在之前的文章当中,我们讨论了C++用来为变量分配内存的5种方案,但是这些方案并不适用于使用new运算符分配的内存,这种内存被称为动态内存. 我们在之前的文章当中也曾介绍过,动态内存由new和delete控制,而不是由作用域和链接性规则控制.所以我们可以在一个函数当中分配动态内存,在另外一个函数中释放. 通常C++编译器当中有三块独立的内存,一块用于静态变量,一块用于自动变量,还有一块用于动态存储. 虽然存储方案的概念不适用于动态内存,但是适用

  • C++ 自由存储区是否等价于堆你知道吗

    目录 free store" VS "heap" 问题的来源 结论 free store" VS "heap" 当我问你C++的内存布局时,你大概会回答: "在C++中,内存区分为5个区,分别是堆.栈.自由存储区.全局/静态存储区.常量存储区". 如果我接着问你自由存储区与堆有什么区别,你或许这样回答: "malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来

  • C++实现数据文件存储与加载

    本文实例为大家分享了C++实现数据文件存储与加载的具体代码,供大家参考,具体内容如下 首先请先确认已经安装好了opencv3及以上版本. #include <opencv2/opencv.hpp> #include <iostream> #include <string> using namespace cv; using namespace std; 存储 then int main() { //创造一些要存的数据先 string words = "hell

  • C++浮点数在内存中的存储详解

    目录 前言: 浮点数的表示形式 浮点数存储模型 有效数字M 指数E 例题讲解 总结 前言: 我们在码代码的时候,经常遇到过以整数形式存入,浮点数形式输出:或者浮点数形式存入整数形式输出.输出的结果往往让人意想不到,那么,为什么会发生这样的变化,又是什么导致发生变化,接下来,就让我们从存储内部结构出发,带你深度解刨! 我们以一个例子来说明一切 #include<stdio.h> int main() { int n = 9; float *pFloat = (float *)&n; pr

  • C++ 实现稀疏矩阵的压缩存储的实例

    C++ 实现稀疏矩阵的压缩存储的实例 稀疏矩阵:M*N的矩阵,矩阵中有效值的个数远小于无效值的个数,且这些数据的分布没有规律. 稀疏矩阵的压缩存储:压缩存储值存储极少数的有效数据.使用{row,col,value}三元组存储每一个有效数据,三元组按原矩阵中的位置,以行优先级先后顺序依次存放. 实现代码: #include <iostream> #include <vector> using namespace std; template<class T> struct

  • C++ 数据结构之对称矩阵及稀疏矩阵的压缩存储

    对称矩阵及稀疏矩阵的压缩存储 1.稀疏矩阵 对于那些零元素数目远远多于非零元素数目,并且非零元素的分布没有规律的矩阵称为稀疏矩阵(sparse). 人们无法给出稀疏矩阵的确切定义,一般都只是凭个人的直觉来理解这个概念,即矩阵中非零元素的个数远远小于矩阵元素的总数,并且非零元素没有分布规律. 实现代码: //稀疏矩阵及其压缩存储 #pragma once #include <vector> #include <iostream> using namespace std; templa

  • C++处理图存储的方式分享

    目录 一.邻接矩阵 二.邻接表 三.链式前向星 1.AcWing方式(纯数组) 三.Acwing图的存储方式 2.复杂度 2.应用 3.邻接表 4.代码实现 5.插入边 四.遍历 1.深度优先遍历 2.广度优先遍历 3.复杂度 4.应用 5.实现案例 6. 结构体+数组 7. 结构体+数组(2) 一.邻接矩阵 适用: 稠密图,就是说点数的平方与边数接近的情况,换句话说就是边特别多. 不适用: 稀疏图,就是点数的平方与边数差的特别多,边数少,但点数多,就不行了,因为空间占用太大了. 实现代码: #

  • js淡入淡出焦点图幻灯片效果代码分享

    本文实例讲述了javascript淡入淡出焦点图幻灯片效果.分享给大家供大家参考.具体如下: 这是一款基于javascript实现的淡入淡出焦点图幻灯片效果代码,可以自定义标题,实现过程很简单. 运行效果图:-------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换浏览模式. 为大家分享的js淡入淡出焦点图幻灯片效果代码如下 <head> <meta http-equiv="Content-Ty

  • jQuery实现百叶窗焦点图动画效果代码分享(附源码下载)

    这是一款基于jQuery的百叶窗焦点图动画,和之前介绍的CSS3百叶窗焦点图动画不同的是,它的兼容性更好,实用性更强,因为它是基于纯jQuery的,基本上所有浏览器都能够支持.焦点图的图片切换动画是百叶窗的动画方式,但也有几种不同的百叶窗动画,因此也不会觉得单调. 在线演示     源码下载 HTML代码 <div id="slider"> <img src="images/1.jpg" alt="我们1" title=&quo

  • Android 谷歌推荐的VR实现方式(分享)

    谷歌有专门的SDK来完成VR,我这次以一个全景图片的例子来说一下这个SDK实现VR的基本过程,首先全景图片就是百度地图里的那样,能够看到周围环境360的图片. 添加依赖 compile 'com.google.vr:sdk-panowidget:1.80.0' 添加权限 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="an

  • Android加载长图的多种方案分享

    背景介绍 在某些特定场景下,我们需要考虑加载长图的需求,比如加载一幅<清明上河图>,这个好像有点过分了,那就加载1/2的<清明上河图>吧... 那TMD还不是一样道理. 言归正传说一下我这边遇到的情况,之前有图片或大图的模块是划分为H5来实现的,现在需求变更划分为原生开发,那么问题就来了. 图片尺寸为 图片大小为 这一刻我是懵逼的,哪个端图片上传的时候没限制尺寸和压缩?mdzz, 吐槽归吐槽,还是要撸起袖子解决加载长图大图的问题. 先提供几个技术方案来对比一下: 方案1:WebVi

  • SpringBoot实现文件下载功能的方式分享

    1. 将文件以流的形式一次性读取到内存,通过响应输出流输出到前端 /** * @param path 想要下载的文件的路径 * @param response * @功能描述 下载文件: */ @RequestMapping("/download") public void download(String path, HttpServletResponse response) { try { // path是指想要下载的文件的路径 File file = new File(path);

  • jquery左右全屏大尺寸多图滑动效果代码分享

    本文实例讲述了jquery左右全屏大尺寸多图滑动效果.分享给大家供大家参考.具体如下: 这是一款基于jquery实现的banner焦点图播放效果的插件,图片播放时处于满屏的状态,很具有画面感,呈现的效果更佳充实,用户的视觉体验更加具体,是一款很时尚大方的特效代码. 运行效果图:                                        -------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换

  • jQuery焦点图切换特效代码分享

    本文实例讲述了jQuery焦点图切换特效.分享给大家供大家参考.具体如下: 这是一款网易保健品商城网站的jQuery焦点图代码,界面简洁.时尚.大方,通用性比较强,实现过程很简单. 运行效果图:      -------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换浏览模式. 在head区域引入CSS样式: <link rel="stylesheet" href="css/zzsc.css

  • jquery京东商城双11焦点图多图广告特效代码分享

    本文实例讲述了jquery京东商城双11焦点图多图广告特效.分享给大家供大家参考.具体如下: jquery实现的京东商城双11焦点图多图广告滑动及自动切换动画效果源码,是一段模仿京东商城双11的焦点图代码,专业应用于网站的图片展示及重点展示的区域,该段代码实现了鼠标滑过切换图片及自动切换图片两种效果. 运行效果图:     -------------------查看效果 下载源码------------------- 小提示:浏览器中如果不能正常运行,可以尝试切换浏览模式. 为大家分享的jque

  • 实例详解Android文件存储数据方式

    总体的来讲,数据存储方式有三种:一个是文件,一个是数据库,另一个则是网络.下面通过本文给大家介绍Android文件存储数据方式. 1.文件存储数据使用了Java中的IO操作来进行文件的保存和读取,只不过Android在Context类中封装好了输入流和输出流的获取方法. 创建的存储文件保存在/data/data/<package name>/files文件夹下. 2.操作. 保存文件内容:通过Context.openFileOutput获取输出流,参数分别为文件名和存储模式. 读取文件内容:通

随机推荐