从零开始的图的存储方法

图论是信息学竞赛中十分重要的一个部分,但许多算法都建立在如何存储图的结构上。

笔者就在这里总结一下图的几种存储方法。

 

1.邻接矩阵

邻接矩阵是表示图的数据结构中最简单的一种,对于一个有n个点的图,我们需要一个n*n的矩阵,

对于这个图,第i行第j列表示点ai到点aj的距离。

使用邻接矩阵的时候我们需要初始化,map1[i][i]=0,map1[i][j]=∞。每读入一组数据将map1[i][j]赋值为value即可。

 

对于邻接矩阵,初始化需要O(n^2)的时间,建图需要O(m)的时间,而且空间复杂度是O(n^2)。

虽然邻接矩阵非常简单,但很少有算法选择时间和空间都如此高的邻接矩阵来存储图。

但也有使用邻接矩阵较简单的 如:Floyd

 

2.前向星

前向星的构造方法很简单,每读入一条边,就将这条边存入数组中,把数组中的边按照起点的顺序排序,就构造完成了。

int head[MAXN];//存储起点为ai的第一条边的位置
struct node
{
    int begin;  //起点 
    int to;     //终点 
    int val;    //权值 
};
node map1[MAXM];
View Code

 

将所有信息读入后,按到起点为第一顺序,终点为第二顺序,权值为第三顺序排序。

前向星的复杂度与排序的算法有关,一般为O(m*logm)。

因为需要开两个数组,所以空间复杂度为O(n+m)。

 

3.邻接表

邻接表是图中最常用的存储结构,对于图中的每个顶点ai,把所有邻接于ai的边存入一个单链表,这个单链表就是顶点ai的邻接表。

邻接表实现的方式有很多

如 动态指针建标,vector数组建表,静态建表(与前向星有关)。

这里只介绍后两种。

 

1.

首先是vector数组建表

数据结构:

 

struct node
{
    int to;   //顶点序号
    int val;  //权值
};
vector <node> map1[maxn];
View Code

 

 

 

 遍历

node e;
cin>>x>>y>>z;
e.to=y;
e.val=z;
map1[i].push_back(e);

//遍历
for(i=1;i<=n;i++)
{
    for(vector <node> ::iterator k=map1[i].begin();k!=map1[i].end();k++)
    {
        node t=*k;
        cout<<i<<" "<<t.to<<" "<<t.val<<endl;
    }
} 
View Code

使用vector有一点好处,就是空间的申请和释放都不需要自己处理,不容易犯错

 

2.

静态建标

也有人叫做链式前向星

这种方法是采用用数组模拟链表的策略来建立邻接表

数据结构:

 

for(i=1;i<=n;i++)
{
    cin>>x>>y>>z;
    map1[i].to=y;
    map1[i].val=z;
    map1[i].next=head[x];
    head[x]=i;
}
View Code

 

 

 

建图与遍历

for(i=1;i<=m;i++)
{
    cin>>x>>y>>z;
    map1[i].to=j;
    map1[i].val=z;
    map1[i].next=head[x];
    head[x]=i;
}

//遍历

for(i=1;i<=n;i++)
{
    for(k=head[i];k!=-1;k=map1[k].next)
    {
        cout<<i<<" "<<map1[k].to<<" "<<map1[k].val<<endl; 
    }
} 
View Code

因为我们使用了两个数组,所以空间复杂度是O(n+m),每读入一条边就需要维护数组,所以时间复杂度为O(m)

静态建标的优点在于必要的空间较小,时间复杂度不是很高,代码和原理还有实现都十分明确,而且可以存重边,

除了不能直接用起点和终点确定是否有边以外,其他都是完美的。

 

 

 

posted @ 2017-07-04 09:50  ashon37w  阅读(490)  评论(0编辑  收藏  举报