zoukankan      html  css  js  c++  java
  • [图论入门]图的储存

    #0.0 引入

    我们这里有一张图

    现在,我们需要把它存储到电脑中

    输入格式:
    
    第一行:n,m  n表示点的数量,m表示边的数量
    
    第2~m+1行: x,y,z 表示x到y的边的权值为z
    

    本例输入为:

    6 8
    0 2 3
    0 4 4
    0 5 2
    1 4 9
    1 5 4
    2 3 1
    2 4 2
    4 5 6
    

    #1.0 邻接矩阵

    邻接矩阵正如其名,实际便是一个二维数组,用于存储两点之间是否有边
    我们定义一个数组M来存储

    int M[101][101];
    

    因为这个图有权值,我们需要先初始化这个数组(n为点的数量),当(M[i][j])的职为正无穷时,说明(i)(j)之间没有边

    void st(){
        for (int i = 0;i < n ;i ++)
          for (int j = 0;j < n;j ++)
            M[i][j] = 0x7ffffff;
    }
    

    之后,(M)数组中变成了这样:

    输入,不必多讲

    void init(){
        for (int i = 0;i < m;i ++){
            int x,y,z;
            cin >> x >> y >> z;
            M[x][y] = z;
        }
    }
    

    输入后,数组(M)变成了这个亚子:(标黄的为做出更改)

    此时,这图便存储了进去。
    邻接矩阵需要二维数组储存,数据过大时显然是无法使用的,因此,邻接矩阵并不常用


    #2.0 邻接表

    邻接矩阵在储存一个稀疏图时对于空间的浪费是极大的,那么我们可不可以只对边的数据进行储存?
    就可以用到我们的一大杀器——邻接表

    邻接表,存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。 --百度百科

    #2.1 邻接表的存储

    邻接表的实现方法有许多,这里只简单叙述常用的一种,其实下文的链式前向星也是邻接表的一种

    首先,我们按照边读入的数据对边进行编号,如:
    0 2 3这条边编号为(1)
    2 3 1这条边编号为(6)
    我们建立一个 Edge 类型的结构体,结构体定义如下:

    struct Edge{
        int u; //边的起点
        int v; //边的终点
        int w; //边的权值
    };
    Edge e[SIZE];
    

    这样,例图的储存便是这样的:

    我们现在已经将每一条边的数据存入了,但这样是不好遍历的,为了方便遍历,我们还需要将这些边连接起来
    这里就要引出邻接表的精髓: next 数组和 first 数组

    • first[i] 储存以 i 结点为起点最后一条边(在输入顺序中)的编号, 一定要注意,这里储存的是编号
    • next[j] 存储与编号为 j同起点的上一条边(在输入顺序中)的编号
      分析上面的叙述,我们可以得到以下代码:
    next[tot] = first[e[tot].u]; 
    //之前以e[tot].u为起点的最后一条边在此次存储后变成了与tot号边同起点的上一条边
    first[e[tot].u] = tot; //新的以e[tot].u为起点的最后一条边的编号为tot
    tot ++; //增加边的编号
    

    举个例子,例图中以 '0' 号结点为起点的边有

    为了以后的遍历,录入前,我们要先将 first数组全部置为 (-1)
    那么,当这些边全部录入后,first[0] 中储存的编号为 (3),而其中 next 数组存储情况则如下表:

    其他的边存储规则与之相同
    存储完整代码:

    inline void add(int u,int v,int w){
        e[tot].u = u;
        e[tot].v = v;
        e[tot].w = w;
        next[tot] = first[e[tot].u];
        first[e[tot].u] = tot;
        tot ++;
    }
    

    #2.2 邻接表的遍历

    由上面的存储,我们可以看出,当我们想要遍历以 i 为起点的所有边时,只需要从 first[i] 中储存的边开始,依次查找 next[fisrt[i]]next[next[first[]i]]...当为 (-1) 时,说明没有下一条边了,可以停止,即为下面的程序:

    inline void ergodic(){
        for (int i = 0;i < n;i ++){
    	for (int j = first[i];j != -1;j = next[j])
              ...Do something you want...
        }
    }
    

    通过观察可以发现,它遍历的顺序与输入的顺序恰好是相反的

    完整储存与输出

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define SIZE 100011
    using namespace std;
    
    struct Edge{
        int u;
        int v;
        int w;
    };
    Edge e[SIZE];
    
    int n,m,tot;
    int first[SIZE],next[SIZE];
    
    inline void add(int u,int v,int w){
        e[tot].u = u;
        e[tot].v = v;
        e[tot].w = w;
        next[tot] = first[e[tot].u];
        first[e[tot].u] = tot;
        tot ++;
    }
    
    inline void print(){
        for (int i = 0;i < n;i ++){
    	printf("
    %d:
    ",i);
    	for (int j = first[i];j != -1;j = next[j])
    	  printf("%d -> %d  w:%d
    ",i,e[j].v,e[j].w);
        }
    }
    
    int main(){
        memset(first,-1,sizeof(first));
        scanf("%d%d",&n,&m);
        for (int i = 0;i < m;i ++){
    	int u,v,w;
    	scanf("%d%d%d",&u,&v,&w);
    	add(u,v,w);
        }
        print();
        return 0;
    }
    

    #3.0 链式前向星

    还有一种常用的图的储存方式,链式前向星,(其实也是邻接表的一种

    #3.1 链式前向星的存储

    学过上面的邻接表后,在看这就没什么难度了,因为储存方式基本相同
    我们这样定义这个结构体:

    struct Edge{
        int w; \该边的权值
        int to; \该边的终点
        int next; \与这条边同起点的上一条边的编号
    };
    Edge e[SIZE];
    

    我们还需要一个 head 数组,head 数组的定义如下:

    • head[i] 储存以 i 结点为起点最后一条边(在输入顺序中)的编号

    这个 head 数组的定义是不是很眼熟?没错,它是我从上面粘贴过来的与上面邻接表 first 数组定义是相同的
    这样看来,链式前向星不过是把上文邻接表的实现中的 next 数组移到了结构体中,所以我们可以轻松写出以下代码:

    inline void add(int u,int v,int w){
        e[tot].to = v;
        e[tot].w = w;
        e[tot].next = head[u];
        head[u] = tot;
        tot ++;
    }
    

    #3.2 链式前向星的遍历

    与上文邻接表的遍历基本相同 =-= ,改动不大

    inline void ergodic(){
        for (int i = 0;i < n;i ++){
    	for (int j = head[i];j != -1;j = e[j].next)
              ...Do something you want...
        }
    }
    

    #3.3 完整储存与输出

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define SIZE 100011
    using namespace std;
    
    struct Edge{
        int w; \该边的权值
        int to; \该边的终点
        int next; \与这条边同起点的上一条边的编号
    };
    Edge e[SIZE];
    
    int n,m,tot;
    int head[SIZE];
    
    inline void add(int u,int v,int w){
        e[tot].to = v;
        e[tot].w = w;
        e[tot].next = head[u];
        head[u] = tot;
        tot ++;
    }
    
    inline void print(){
        for (int i = 0;i < n;i ++){
    	printf("
    %d:
    ",i);
    	for (int j = head[i];j != -1;j = e[j].next)
    	  printf("%d -> %d  w:%d
    ",i,e[j].v,e[j].w);
        }
    }
    
    int main(){
        memset(first,-1,sizeof(first));
        scanf("%d%d",&n,&m);
        for (int i = 0;i < m;i ++){
    	int u,v,w;
    	scanf("%d%d%d",&u,&v,&w);
    	add(u,v,w);
        }
        print();
        return 0;
    }
    

    更新日志及说明

    更新

    • 初次完成编辑 - (2020.10.16)
      本文若有更改或补充会持续更新

    个人主页

    欢迎到以下地址支持作者!
    Github戳这里
    Bilibili戳这里
    Luogu戳这里

  • 相关阅读:
    区分.net中的virtual new 与override
    DOM
    两个php函数
    中文字符,全角字符的正则表达式
    CSS实现居中代码大全
    xp纯净版
    A Visit to Two National Parks: Mount Rainier and Valley Forge
    8、检测浏览器和操作系统
    客户
    American History: The Reagan Years
  • 原文地址:https://www.cnblogs.com/Dfkuaid-210/p/13226987.html
Copyright © 2011-2022 走看看