算法8:巧妙的邻接表(数组实现)

之前我们介绍过图的邻接矩阵存储法,它的空间和时间复杂度都是N2,如今我来介绍第二种存储图的方法:邻接表,这样空间和时间复杂度就都是M。

对于稀疏图来说,M要远远小于N2。

先上数据。例如以下。


1

2

3

4

5

6

4
5

1
4 9

4
3 8

1
2 5

2
4 6

1
3 7

第一行两个整数n m。

n表示顶点个数(顶点编号为1~n),m表示边的条数。接下来m行表示,每行有3个数x
y z。表示顶点x到顶点y的边的权值为z。下图就是一种使用链表来实现邻接表的方法。

上面这样的实现方法为图中的每一个顶点(左边部分)都建立了一个单链表(右边部分)。这样我们就能够通过遍历每一个顶点的链表。从而得到该顶点全部的边了。使用链表来实现邻接表对于痛恨指针的的朋友来说,这简直就是噩梦。

这里我将为大家介绍另一种使用数组来实现的邻接表,这是一种在实际应用中很easy实现的方法。这样的方法为每一个顶点i(i从1~n)也都保存了一个类似“链表”的东西。里面保存的是从顶点i出发的全部的边,详细例如以下。

首先我们依照读入的顺序为每一条边进行编号(1~m)。

比方第一条边“1
4 9”的编号就是1,“1 3 7”这条边的编号是5。

这里用u、v和w三个数组用来记录每条边的详细信息,即u[i]、v[i]和w[i]表示第i条边是从第u[i]号顶点到v[i]号顶点(u[i]àv[i]),且权值为w[i]。

再用一个first数组来存储每一个顶点当中一条边的编号。以便待会我们来枚举每个顶点全部的边(你可能会问:存储当中一条边的编号就能够了?不可能吧,每一个顶点都须要存储其全部边的编号才行吧!甭着急。继续往下看)。比方1号顶点有一条边是
“1 4 9”(该条边的编号是1),那么就将first[1]的值设为1。假设某个顶点i没有以该顶点为起始点的边,则将first[i]的值设为-1。如今我们来看看详细怎样操作,初始状态例如以下。

咦?上图中怎么多了一个next数组。有什么作用呢?不着急。待会再解释。如今先读入第一条边“1
4 9”。

读入第1条边(1
4 9),将这条边的信息存储到u[1]、v[1]和w[1]中。

同一时候为这条边赋予一个编号,由于这条边是最先读入的,存储在u、v和w数组下标为1的单元格中,因此编号就是1。这条边的起始点是1号顶点。因此将first[1]的值设为1。

另外这条“编号为1的边”是以1号顶点(即u[1])为起始点的第一条边,所以要将next[1]的值设为-1。也就是说,假设当前这条“编号为i的边”,是我们发现的以u[i]为起始点的第一条边。就将next[i]的值设为-1(貌似的这个next数组非常神奇啊⊙_⊙)。

读入第2条边(4
3 8),将这条边的信息存储到u[2]、v[2]和w[2]中。这条边的编号为2。

这条边的起始顶点是4号顶点,因此将first[4]的值设为2。另外这条“编号为2的边”是我们发现以4号顶点为起始点的第一条边,所以将next[2]的值设为-1。

读入第3条边(1
2 5)。将这条边的信息存储到u[3]、v[3]和w[3]中。这条边的编号为3,起始顶点是1号顶点。我们发现1号顶点已经有一条“编号为1 的边”了。假设此时将first[1]的值设为3,那“编号为1的边”岂不是就丢失了?我有办法,此时仅仅需将next[3]的值设为1就可以。如今你知道next数组是用来做什么的吧。

next[i]存储的是“编号为i的边”的“前一条边”的编号。

读入第4条边(2 4 6),将这条边的信息存储到u[4]、v[4]和w[4]中,这条边的编号为4,起始顶点是2号顶点,因此将first[2]的值设为4。

另外这条“编号为4的边”是我们发现以2号顶点为起始点的第一条边。所以将next[4]的值设为-1。

读入第5条边(1 3 7),将这条边的信息存储到u[5]、v[5]和w[5]中,这条边的编号为5,起始顶点又是1号顶点。此时须要将first[1]的值设为5。并将next[5]的值改为3。

此时,假设我们想遍历1号顶点的每一条边就非常easy了。1号顶点的当中一条边的编号存储在first[1]中。其余的边则能够通过next数组寻找到。请看下图。

算法8:巧妙的邻接表(数组实现)

细心的同学会发现,此时遍历边某个顶点边的时候的遍历顺序正好与读入时候的顺序相反。

由于在为每一个顶点插入边的时候都直接插入“链表”的首部而不是尾部。只是这并不会产生不论什么问题。这正是这样的方法的其妙之处。

创建邻接表的代码例如以下。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

int n,m,i;

//u、v和w的数组大小要依据实际情况来设置,要比m的最大值要大1

int u[6],v[6],w[6];

//first和next的数组大小要依据实际情况来设置,要比n的最大值要大1

int first[5],next[5];

scanf("%d
%d"
,&n,&m);

//初始化first数组下标1~n的值为-1,表示1~n顶点临时都没有边

for(i=1;i<=n;i++)

    first[i]=-1;

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;

}

接下来怎样遍历每一条边呢?我们之前说过事实上first数组存储的就是每一个顶点i(i从1~n)的第一条边。比方1号顶点的第一条边是编号为5的边(1
3 7)。2号顶点的第一条边是编号为4的边(2
4 6)。3号顶点没有出向边,4号顶点的第一条边是编号为2的边(2
4 6)。那么怎样遍历1号顶点的每一条边呢?也非常easy。

请看下图:

遍历1号顶点全部边的代码例如以下。


1

2

3

4

5

6

k=first[1];//
1号顶点当中的一条边的编号(事实上也是最后读入的边)

while(k!=-1) //其余的边都能够在next数组中依次找到

{

    printf("%d
%d %d\n"
,u[k],v[k],w[k]);

    k=next[k];

}

遍历每一个顶点的全部边的代码例如以下。


1

2

3

4

5

6

7

8

9

for(i=1;i<=n;i++)

{

    k=first[i];

    while(k!=-1)

    {

        printf("%d
%d %d\n"
,u[k],v[k],w[k]);

        k=next[k];

    }

}

能够发现使用邻接表来存储图的时间空间复杂度是O(M),遍历每一条边的时间复杂度是也是O(M)。假设一个图是稀疏图的话,M要远小于N2。

因此稀疏图选用邻接表来存储要比邻接矩阵来存储要好非常多。

欢迎转载,码字不easy啊,转载麻烦注明出处

【啊哈!算法】系列8:巧妙的邻接表(数组实现)  http://ahalei.blog.51cto.com/4767671/1391988

时间: 2024-08-07 23:22:03

算法8:巧妙的邻接表(数组实现)的相关文章

巧妙的邻接表(数组实现)

之前我们介绍过图的邻接矩阵存储法,它的空间和时间复杂度都是N2,现在我来介绍另外一种存储图的方法:邻接表,这样空间和时间复杂度就都是M.对于稀疏图来说,M要远远小于N2.先上数据,如下. 4 5 1 4 9 4 3 8 1 2 5 2 4 6 1 3 7 第一行两个整数n m.n表示顶点个数(顶点编号为1~n),m表示边的条数.接下来m行表示,每行有3个数x y z,表示顶点x到顶点y的边的权值为z.下图就是一种使用链表来实现邻接表的方法. 上面这种实现方法为图中的每一个顶点(左边部分)都建立了

最小生成树Kruskal算法(邻接矩阵和邻接表)

最小生成树,克鲁斯卡尔算法. 算法简述: 将每个顶点看成一个图. 在所有图中找权值最小的边.将这条边的两个图连成一个图, 重复上一步.直到只剩一个图. 注:将abcdef每个顶点看成一个图.将最小权值的边的两个图连接. 连接最小权值为1的两个图,这时a-c,b,d,e,f. 连接最小权值为2的两个图,这时a-c,b,d-f,e. 连接最小权值为3的两个图,这时a-c,b-e,d-f. 连接最小权值为4的两个图,这时a-c-f-d,b-e.(c-f) 连接最小权值为5的两个图,这时a-c-b-e-

邻接表 数组实现

#include<stdio.h> int main() { int n,m,i; int u[6],v[6],w[6]; int first[5],next[6]; //first记录每个点连接的第一边,e.g:first[u[i]]表示点u[i]能到达的第一个点 //next[i]表示编号为i的边的下一条边的编号 scanf("%d %d",&n,&m); for(i=1;i<=n;i++) first[i]=-1;//表示现在还没有边 for(i

网络流三大算法【邻接矩阵+邻接表】POJ1273

网络流的基本概念跟算法原理我是在以下两篇博客里看懂的,写的非常好. http://www.cnblogs.com/ZJUT-jiangnan/p/3632525.html http://www.cnblogs.com/zsboy/archive/2013/01/27/2878810.html 网络流有四种算法, 包括 Edmond-Karp(简称EK), Ford-Fulkerson(简称FF), dinic算法以及SAP算法. 下面我会写出前三种算法的矩阵跟邻接表的形式, 对于第四种以后有必要

邻接表怎么写

最近做图的题比较多,除了克鲁斯卡尔和floyd,像广搜,普里姆,Bellman-Ford,迪杰斯特拉,SPFA,拓扑排序等等,都用到图的邻接表形式. 数据结构书上表示邻接表比较复杂,一般形式如下: 1 typedef struct Node 2 { 3 int dest; //邻接边的弧头结点序号 4 int weight; //权值信息 5 struct Node *next; //指向下一条邻接边 6 }Edge; //单链表结点的结构体 7 8 typedef struct 9 { 10

图的邻接表的操作实现

<span style="font-size:18px;">#include<stdio.h> #include<malloc.h> #define MaxVertices 10 //定义顶点的最大值 typedef char DataType; typedef struct Node{ int dest;//邻接边的弧头顶点序号 struct Node *next;//单链表的下一个结点指针 }Edge;//邻接边单链表的结点结构体 typedef

【算法与数据结构】图 -- 邻接表

/************************************************************************ 边(弧)结点 -------------------------- |adjvex | info | nextarc | -------------------------- adjvex:邻接点域,该点在图中的位置 info :相关信息,比如可以保存从某顶点到该点的权值 nextac:指向下一个与该点有相同邻接点的顶点 顶点结点 ---------

数组模拟实现邻接表

图的边的表示方法,有很多.像邻接矩阵.边集数组.邻接表等.其中,第三者的时空复杂度应该是最优的.但是实现却需要比较麻烦的链表,但是我们也可以用数组来模拟链表,使编程的复杂度进一步降低. 这种算法:遍历所有的边的时间复杂度是O(M),M表示边的总数,空间复杂度也是O(M).在最坏情况下,查询i与j是否有边的时间复杂度是O(N). 这篇文章主要写给P(Pascal)党,在Cpp中,链表的实现方式较为简单(有现成的库),所以也没必要用这种办法.说是模拟实现邻接表,其实是模拟实现链表.接下来看类型定义:

Hihocoder 之 #1097 : 最小生成树一&#183;Prim算法 (用vector二维 模拟邻接表,进行prim()生成树算法, *【模板】)

#1097 : 最小生成树一·Prim算法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了! 但是,问题也接踵而来——小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A.B.C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过