• 【学术篇】浅谈各种邻接表


    首先注:下面出现的代码完全没经编译,不保证编译成功,大家当伪代码看较好~

    在OIer的世界里,有一种奇妙的东西,叫图论。。
    而对于计算机,我们并不能将一张图输入,而需要一些存图方式
    比如下面这张图(画得不好看勿喷~):
    这里写图片描述
    最简单的存图方式是邻接矩阵
    *在一个n*n的矩阵中,第(i,j)个格子的值表示从i到j这条边的边权。。
    上图用邻接矩阵表示就是:

    -0123456
    0 0 3 0 5 0 0 0
    1 3 0 2 0 0 0 0
    2 0 2 0 2 0 0 0
    3 5 0 2 0 3 5 0
    4 0 0 0 3 0 2 0
    5 0 0 0 5 2 0 8
    6 0 0 0 0 0 8 0

    图的储存结构:

    int tu[N][N]; 

    建边:

    inline void build(int x,int y,int z){ //建从x到y边权为z的边
        tu[x][y]=z; //单向边
    #ifdef DOUBLE_EDGE
        tu[y][x]=z; //双向边
    #endif
    }

    枚举边:

    int x=start;
    for(int i=0;i<n;i++)
        if(tu[x][i]){ //若不为0表示相连
            int y=i; //跑到i的点
        }

    这个东西有个显而易见的缺点,O(n^2)的效率就决定了无法处理任何大规模的数据。。
    而O(nlogn)的算法中的n基本都在10^5级别。。数组都开不开啊_ (:з」∠) _

    所以我们需要邻接表,这样时间、空间效率都被控制在O(m)级别(m为边数)
    正统的邻接表是用指针……
    邻接表对于每个点主要保存了以下东西:
    - 这个点出发的第一条边
    - 每条边(和下面的都指该点出发的边)所指向的点
    - 每条边的边权(data域)
    - 每条边指向的下一条边
    储存的结构基本上是这样的:

    struct edge{
        int to,data;
        etype *next,*pair; //pair为反向边,视情况而建
        edge(){}
        edge(int t,int d,edge* nxt):to(t),data(d),next(nxt){}
        //dalao们发现C++的new实在是太慢了,所以要自己写一个orz
        void* operator new(unsigned,void* p){return p;}
    }*e[N];
    /*还有一个*/int Pe=0; //记录了边的总数

    建边:

    void build(int x,int y,int z){
        e[x]=new(Pe++)edge(y,z,e[x]); //指向y,边权z,上一条边是上一个e[x]..
    #ifdef DOUBLE_EDGE
        e[y]=new(Pe++)edge(x,z,e[y]);
        e[x]->pair=e[y];
        e[y]->pair=e[x];
    #endif
    }

    枚举边:

    int x=start;
    for(etype *i=e[no];i;i=i->next)
        int y=i->to;

    嗯,很好,我们现在有了存图的利器
    但是,指针的特性决定了对我们精确的要求。。
    不禁想起了指针调试的恐惧
    所以,我们应该是有更简(ju)便(ruo)的方法的:
    比如,数组模拟……(这应该也是应用最广泛的吧……)
    于是存储结构变成了:

    struct edge{
        int to,next,data;
    }e[M]; int v[N],tot=0; //v数组就是很多很多dalao的first数组,存点i出发的第一条边备查
    //网络流图中tot为了异或找反向边容易可以置为1..

    建边:

    void build(int x,int y,int z){
        e[++tot].to=y; e[tot].next=v[x]; e[tot].data=z; v[x]=tot;
    #ifdef DOUBLE_EDGE
        e[++tot].to=x; e[tot].next=v[y]; e[tot].data=z; v[y]=tot;
    #endif
    }

    枚举边:

    int x=start;
    for(int i=v[x];i;i=e[i].edge)
        int y=e[i].to;

    对就是这样。。
    其实似乎写到这里就可以结束了。。
    但是其实还是有一种写法的。。
    比如,C++的盆友们,还记得你们的STL么?
    vector也可以用来存邻接表哦(下面我给出没有边权的)~
    (而且似乎省选开O2什么的STL会格外跑得快)
    存储结构?

    vector<int> e[N];

    建边:

    void build(int x,int y){
        e[x].push_back(y); //媲美邻接矩阵的简便..
    #ifdef DOUBLE_EDGE
        e[y].push_back(x);
    #endif
    }

    枚举边:

    int x=start;
    for(/*unsigned*/int i=0;i<e[x].size();i++){ //不想要warning的可以加unsigned..
        int y=e[x][i];
    }

    行了,就介绍这么多。。。
    希望对大家有帮助,希望大家用的开心~~

  • 相关阅读:
    第二个Sprint
    第一个Sprint
    “尸体解剖” 报告
    软工水平自我评价表
    小学四则运算APP 第一个冲刺阶段 第五天
    小学四则运算APP 第一个冲刺阶段 第四天
    小学四则运算APP 第一个冲刺阶段 第三天
    小学四则运算APP 第一阶段冲刺 第二天-补
    小学四则运算APP 第一个冲刺 第二天
    小学四则运算APP 第一个冲刺阶段 第一天
  • 原文地址:https://www.cnblogs.com/enzymii/p/8412153.html
Copyright © 2020-2023  润新知