邻接表储存图的实现方法

  • Post author:
  • Post category:其他


图(有向图和无向图)的储存方法有邻接矩阵和邻接表两种储存方式。

其中

邻接表有两种实现方式:1.指针。2.数组

后者使用比较方便简洁,这里介绍一下用

数组实现邻接表储存图

输入数据

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

4表示顶点个数 5表示输入的边数


用邻接表储存这个图,代码如下

(我觉得有点乱,hh,理解不太清楚就不要用这个方法,直接

跳过

这个,看后面的)

#include<stdio.h>
int main(void)
{
 int n,m;//n表示顶点个数,m表示输入的边数; 这里用n=4(点),m=5(边);举例 
 int i,k;
 int u[6],v[6],w[6];//第一,二个数组是储存顶点的,第三个是储存该边的权值的;
 //这三个数组的大小要根据m来定,比m大一就行了;
 int first[5],next[6];
 //这两个数组也要看实际,first数组比n(顶点)大一,next数组比m(边)大一 
 scanf("%d %d",&n,&m);
 for(i=1;i<=n;i++)
 first[i]=-1;//初始化first数组,表示1~n顶点暂时还没有连接的边;
 for(i=1;i<=m;i++) 
 { 
  scanf("%d %d %d",&u[i],&v[i],&w[i]);//依次输入顶点和所连接的边权值 
  //下面两句是关键
  next[i]=first[u[i]];
  first[u[i]]=i;//为边编号,保证遍历时u[],v[],w[]输出的准确 
 }
 printf("\n");
 /*其实就是 最终储存依次 从一个顶点出发的所有边(i其实就是这些边的编号) 
 下面是邻接表的遍历输*/
 for(i=1;i<=n;i++)
 {
  k=first[i];/*选出顶点 1 开始遍历  first可以理解为储存顶点编号,
  其实 也储存了最后一次输入边的编号*/ 
  while(k!=-1)//一直输出与该点连接的边直到没有(next【】==-1) 
  {
   printf("%d %d %d\n",u[k],v[k],w[k]);//其实k就是边的编号 
   k=next[k]; //变为该顶点连接的下一条边 的编号 
  }
 }
 return 0;
}


输入输出如下


在这里插入图片描述

first[5]= {5,4,-1,2}这里的值都是最后一次输入边的编号

next[6]={-1,-1,1,-1,3}


可以发现使用邻接表来储存图的时间空间复杂度是O(M),遍历每一条边的时间复杂度也是O(M)。如果是稀疏图的话,M要远小于N

2

我们可以使用邻接表来代替邻接矩阵( Q(n

2

) )降低时间复杂度,邻接表最坏的情况下才和邻接矩阵一样,但是一般情况下并不会有那么多边。


上面是啊哈算法中实现用数组实现链表储存图的方法


不过我觉得使用时不是很方便,而且容易出错,所以不习惯用这个



现给出两种简便快捷的实现方法:


一、数组实现方法

:**

我们先定义一个结构体数组:

struct node{//u->v 
	int v;//记录当前顶点
	int next;//与u相连的上一条出边 
}a[maxx*2];//储存无向图 

a就是实现链表的结构体数组:

下面先给出它的

输入

方法:

int c=0;//边的编号
int head[10000];//数组长度为顶点个数
void addage(int x,int y)
{
	a[++c].v=y; a[c].next =head[x]/*顶点*/;head[x]=c/*第几条边*/;//x->y
	a[++c].v =x;a[c].next =head[y];head[y]=c;//y->x;//如果是有向图的化省略这一行
	return;//无向图 
}
int main(void)
memset(head,-1,sizeof(head));//初始化为-1
	for(int i=1;i<=m;i++){//m为边的总数
		scanf("%d %d",&x,&y);//x->y
		addage(x,y);
	}

这里用数组实现链表,可以这样

理解

链表的储存可以想象成:储存编号为0~n-1的砖头

每个顶点可以看成一堆砖头,

在每一堆砖头中,

一层一层碟砖头,head记录该堆砖头最后一次出边的编号

遍历找某一顶点的出边的时候:用head找出顶点录入的最后一条出边,然后依次访问每一条边(因为C被记录在next里了)

还不太懂?那下面给出它的

遍历方法

,容易理解:

注意head的初值为-1,即遍历到顶点编号为-1停止

for(j=head[x];j!=-1;j=a[j].next )//x为你要的顶点,这一个循环就是遍历x的每一条出边
//a[j].v为x指向的顶点编号

你如果还想要储存每条边的权值的话可以直接在结构体里定义。


二、vector

看到这是不是觉得这个挺好用的,

下面再讲一个更有意思的

,学过C++ STL容器的都知道里面有个

vector

当我们初学图论的时候我们都会优先使用邻接矩阵,因为我们就会这个,也容易理解对于初学者来说,其实我们可以

用vector代替二维数组来实现邻接矩阵

我们储存图一般定义一个二维数组e[100][100],如果a,b之间有边,e[a][b]就设为1.

那我们用vector怎么

储存



遍历

呢?

1,定义

vector <int> e[100];

2,如果a,b之间有边,那么我们直接:

e[a].push_back(b);

3,遍历的时候我们,如果要遍历顶点a的所有出边,那么我们可以这样做:

for(int i=0;i<e[a].size();i++)

就好了,这样同样可以缩减遍历的时间,就和链表的速度差不多。我在做图论的题目都比较喜欢用这种方法而且我觉得比较好理解,通俗易懂吧,关键是和普通的邻接矩阵使用差不多,又省时。

【强推】

后面两种方法都挺好的,看自己了。



版权声明:本文为qq_43791377原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。