邻接表(vector<node> e[100]; e[x].push_back( (node){y,z} ); node(int a,int b):y(a),z(b){};)

 

struct node
{
    int y,z;
    node(int a,int b):y(a),z(b){};
};

vector<node> e[100];//邻接表
 for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
       e[x].push_back( (node){y,z} );
       //e[y].push_back((node){x,z});
    //存入无向图
    }

对第一种方法有种优化就是对每个点所对应的边的向量进行预估。例如有m条有向边,n个点,那么每个向量需要reserve(6*(m/n)/5);一般这样存储效率会有显著提高。

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    vector<vector<size_t>> graph(5);
    graph[0].push_back(1);//V0->V1.
    graph[1].push_back(4);//V1->V4.
    graph[1].push_back(0);//V1->V0.
    graph[2].push_back(1);//V2->V1.
    graph[2].push_back(3);//V2->V3.
    graph[3].push_back(0);//V3->V0.
    graph[4].push_back(3);//V4->V3.
    //假定要访问点1.
    for(const auto &ele:graph[1])//对于全部属于graph[1]这个容器的元素
    std::cout<<ele<<'';
    std::cout<<std::endl;
    //程序运行后输出40,表示点1连接了4点和0点。
    return 0;
}

第二种(使用map,set):

方法太多,不再举例了。

然而我们这样存图是不够的,对于无向图而言,可能存在一条边需要知道自己的反向边的位置。例如欧拉回路,例如网络流都是需要的。

第二种方法由于std::map<std::size_t,std::set<std::size_t>> graph;只是离散化后的邻接矩阵。对于这种存图方法,访问反向边则非常简单,例如我访问的是a->b这样一条边。那么只需要graph[b].count(a);就可以访问其反向边b->a。

然而对于第一种方法,我们没有办法解决反向边的互相访问题。

 

所以我们对于这种图需要存图修正。代码如下:

对于list容器可以直接存迭代器的,但是存图时也得考虑a是否等于b.forward_list存反向边的图就不好,因为用链表存图就是需要存完图后插入删除,假定一个元素前面的元素被删除了,那么根本无法访问反向边!!!!

感觉存图没问题了?NO!!!!还有一种图更奇葩,那就是对于每个点中的边得排序又得知道反向边的那种图。USACO上有个题目叫做骑马修栅栏,那个题要求字典序输出。数据量很小,以至于可以直接矩阵存图,但是我们考虑如何数据量大,这种方法就不行了。如果用第二种方法(std::map<std::size_t,std::set<std::size_t>>)存图,绝对可以,但是相对常数较大。如果我们考虑顺序容器去存储则比较快一些。

方法就是先用边集表存图,然后每条边a,b得优先以std::min(a,b)为第一关键字再按std::max(a,b)为第二关键字排序,再按照修正后的存图方法存图即可。具体代码见nocow上骑马修栅栏那题lgeecn发的题解和代码。

如果使用list存图可以先存图再用list.sort()函数进行排序,不过似乎效率会差一些,毕竟相对于vector,list常数太大了,达到6倍以上。

存图真心不简单,因为真正用的时候你可能会遇到各种问题,但是你可以加以思考,进行容器搭配使用,即可解决。

#include<map>
#include<set>
#include<iostream>
#include<cstddef>
#include<map>
#include<set>
intmain()
{
std::map<std::size_t,std::set<std::size_t>>graph;
graph[0].insert(1);//V0->V1.
graph[1].insert(4);//V1->V4.
graph[1].insert(0);//V1->V0.
graph[2].insert(1);//V2->V1.
graph[2].insert(3);//V2->V3.
graph[3].insert(0);//V3->V0.
graph[4].insert(3);//V4->V3.
//假定要访问点1.
for(constauto&ele:graph[1])//对于全部属于graph[1]这个容器的元素
std::cout<<ele<<'';
std::cout<<std::endl;
//程序运行后输出04,表示点1连接了0点和4点。对map,set里的元素进行遍历是有序的
return0;
}

 

#include<vector>
#include<iostream>
#include<utility>
#include<cstddef>
intmain()
{
std::vector<std::vector<std::pair<std::size_t,std::size_t>>>graph(5);//每条边的第二个元素不能是迭代器!!会失效!!!!必须是下标!!!
//比如有一条a-b的边,我们得让它实现互访。我们这里假定a=2,b=4;
std::size_ta(2),b(4);
graph[a].push_back(std::make_pair(b,graph[b].size()+(a==b)));//Va->Vb.需要判定a是否等于b.
graph[b].push_back(std::make_pair(a,graph[a].size()-1));//Vb->Va,这个不需要判定a是否等于b.
//访问反向边的方法.
for(constauto&ele:graph[a])//访问点a
graph[ele.first][ele.second];//这就是边ele的反向边了
return0;
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值