图的几种存储方式(邻接矩阵+邻接表+vector)

最近看到数据结构真的是头大,刚好想到之前自己因为不会存图被xxx怒怼,作为一个acmer来说,怎么能不会这种操作呢。然后现在来总结一下图的存储方式。
图的分类有很多,这里不再赘述。
来看一个一般的无向图:通俗地讲,一张图是由边、顶点集构成,每条边上可能还会有相应的边权(带权的),这里讲带权的。
然后想我们怎样存储它呢,下面介绍几种存储方式。
1、邻接矩阵
图的邻接矩阵存储方式是用两个数组来存图的,一个一维数组存储顶点集,一个二维数组(邻接矩阵)存储的是图中边的信息。
例如:上图就可以用一个一维数组head[4]={ v0,v1,v2,v3}存储顶点信息,一个二维数组edge[4][4]存储边的信息(比如edge[i][j]=2,可以将这个数组看作点i到点j的边权为2),上面这个图中,1表示图中存在点i到点j的边0表示不存在。但是细心的你也许注意到了,上面的图是一个无向图,也就是说i-j的同是j-i也是一样的,也就是说edge[i][j]==edge[j][i];这个关系利用对称矩阵可以证明,也就是说,用主对角线为轴的上三角形和下三角形相对应的元素是相等的。而在刚开始的时候将邻接矩阵初始化为0表示所有点都没有联通,时间复杂度O(n^2).好了,具体我们见代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
#define INF 0x3f3f3f3f
int head[maxn];//存储顶点,这里不需要
int edge[maxn][maxn];
int n,m;//n个点,m条边,点的编号为1-n
void init()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j) edge[i][j]=0;//自己到自己是0
            else     edge[i][j]=INF;//初始化为无穷大
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();//初始化邻接矩阵
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            //if(u!=v&&edge[u][v]>w)这里主要是处理重边和自环
            edge[u][v]=edge[v][u]=w;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i;j<=n;j++)
            {
                printf("%d-%d=%d\n",i,j,edge[i][j]);
            }
        }
    }
    return 0;
}

 2、邻接表

邻接表存图主要是使用链表存储,这里存储顶点信息依然使用动态的一维head[] 数组,而存储各个边信息则需要使用多重链表。具体我们见代码:
上图的邻接表如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;//点的数量
const int maxm=10005;//边的数量
int head[maxn];//存储顶点
int cnt;
struct node
{
    int u;//起点
    int v;//终点
    int w;//权值
    int next;//指向上一条边的编号
}edge[maxn*4];//一般都是要开到边的四倍
void add(int u,int v,int w)
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;//顶点编号
}
/*
两种方式都可以,下面的是用C++构造实现
struct node
{
    int v,w,next;
    node(){}
    node(int v,int w,int next):v(v),w(w),next(next){}
}E[4*maxn];
void add(int u,int v,int w)
{
    E[cnt]=node(v,w,head[u]);
    head[u]=cnt++;
}
*/
void init()
{
    cnt=0;
    memset(head,-1,sizeof(head));//表头数组初始化
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        for(int i=1;i<=m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);//双向边
        }
        int u;
        scanf("%d",&u);//输入一个起点
        for(int i=head[u];i!=-1;i=edge[i].next)//输出所有与起点为u相连的边的终点和权值
        {
             int v=edge[i].v;
             int w=edge[i].w;
             printf("%d %d\n",v,w);
        }
    }
    return 0;
}
二者区别:
先介绍一下稠密图和稀疏图:
对于一个含有n个点m条边的无向图,邻接表表示有n个邻接表结点和m个边表结点。边的数量接近于n*(n-1)的称作稠密图,反之为稀疏图。考虑到邻接表中要附加链域,这时候用邻接矩阵比较合适。
对于一个含有n个点m条边的有向图,邻接表表示有n个邻接表结点和2*m个边表结点。边的数目远小于n^2的为稀疏图,反之为稠密图。
还有一种存图方式为vector存图,有兴趣可以了解一下,
下面给出代码:https://paste.ubuntu.com/p/NK5vxQfZBT/

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
struct node
{
    int u;//起点
    int v;//终点
    int w;//权值
}E;
vector<node>edge[maxn];//edge[i]表示起点是i,vector里面存储的是边
int main()
{
    int n,m;//n个点m条边
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        //点的编号1-n
        for(int i=1;i<=m;i++)
        {
            int u;
            scanf("%d%d%d",&u,&E.v,&E.w);
            edge[u].push_back(E);
        }
        for(int i=1;i<=n;i++)//这里vector实际上相当于一个二维的动态数组,第二维大小不确定
        {
            for(int j=0;j<edge[i].size();j++)
            {
                node e=edge[i][j];
                printf("%d-%d=%d\n",i,e.v,e.w);
            }
        }
    }
    return 0;
}




猜你喜欢

转载自blog.csdn.net/dl962454/article/details/80188119