邻接表和邻接矩阵

article/2025/8/27 22:33:07

进入图论的大门(深渊)之前,我们一定要掌握的两种必备手段——邻接表和邻接矩阵,此刻将成为我们的巨大帮手(其实做不来题还是做不来),下面让我们来学习一下,这两种存图方式在计算机中,该如何应用

1.邻接矩阵:就是一个二维数组,大小为dis[n][n](n为顶点数),其中dis[i][j]表示顶点i到顶点j的距离,可以看出,邻接表的空间复杂度为O(n^2),那怎么存边呢?如下:

for(int i = 0; i < m; i++)
{cin>>u>>v>>w;dis[u][v] = w;dis[v][u] = w;    //不是双向图可以把这句注释掉
}

以上表示在有m条边的图中,顶点u->v有一条权值为w的边,双向图加上v->u权值也为w

可以写一个邻接矩阵的初始化:

#define inf 0x3f3f3f3f
memset(dis,inf,sizeof dis);    //距离初始化为正无穷
for(int i = 1; i <= n; i++)    //边从1开始编号,1~ndis[i][i] = 0;            //自己到自己的距离为0

2.邻接表:邻接表是一部分人理解的难点,它的思想是,对每一个顶点,创建一个表,链接其所有出边,如下图: 

 假设存在这样的图:(u v w一组数表示顶点u到顶点v存在一条权值为w的边)

4 5        //4个顶点,5条边
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7

于是我们可以画一个这样的图(图片来源:啊哈算法):

 

这是个什么意思呢?这里我们用五个三个数组u[],v[],w[],first[],next[]来记录每一条边的信息,其实邻接表就是记录边的信息啊,所以我们来看看这些数组是什么意思:

首先我们对所有m条边进行编号,1~m,first[i]用来存顶点i的最后一条出边的编号next[i]用来存编号为i的边的上一条边的编号,u[],v[]是顶点集合,w[]是边权集合,u[i],v[i],w[i]表示第i条边是从顶点v[i] -> 顶点u[i],权值为w[i]的边,其实现在也说不明白,那我们就一步步模拟此过程:

根据输入的顺序,可以将边编号1~5:

4 5        //4个顶点,5条边
1 4 9        //1
4 3 8        //2
1 2 5        //3
2 4 6        //4
1 3 7        //5

 第一条边存入:u[1] = 1,v[1] = 4,w[1] = 9,next[1] = first[u[1]],(先保存上一条边编号),first[u[1]] = 1;

第二条边存入:u[2] = 4,v[2] = 3,w[2] = 8,next[2] = first[u[2]],first[u[2]] = 2;

第三条边存入:u[3] = 1,v[3] = 2,w[3] = 5,next[3] = first[u[3]],first[u[3]] = 3;

第四条边存入:u[4] = 2,v[4] = 4,w[4] = 6,next[4] = first[u[4]],first[u[4]] = 4;

第五条边存入:u[5] = 1,v[5] = 3,w[5] = 7,next[5] = first[u[5]],first[u[5]] = 5;

 

最后解释一下,这么存的妙处在什么地方:

首先我们说了,first[i]表示顶点u[i]的最后一条出边的编号,注意是编号!那么,对于每一条输入的边,first[u[i]]就是以顶点u[i]为起点的边的编号,那么next[i] = first[u[i]]则表示,对于第i条边,next[i]保存的是以u[i]为起点的上一条边的编号,为何是上一条边?因为注意语句顺序,在next[i] = first[u[i]]执行完立即执行first[u[i]] = i,也就是将上一条输入的边的编号信息从first[]转移到next[]数组上了,first[]就可以存当前边的信息了,这样,我们通过first[]和next[]就可以遍历整个图了,过程如下图:

比如我们要找顶点1的所有出边,首先找到first[1],然后根据next[first[1]]找到上一条边的编号,再通过next[next[first[1]]找到再上一条边的编号,一直继续搜索下去直到next[1] = -1为止,说明此时已遍历1号顶点所有出边(因为在没插入边的时候,head[1] = -1,而-1的值赋给了next[1],找到-1说明不存在上一条边了)

是不是很神奇!但是我们这里为了组织边的时候看起来更具有整体性,不用五个数组存边,我们用一个结构体+head[]的形式,其中head[]就是上文的first[],代码如下:

#define maxn 5250    //最大边数
#define vertex 505   //最大点数struct node
{int to,v,next;
}e[maxn];int head[vertex+1],cnt;void init()            //初始化操作
{cnt = 0;memset(head,-1,sizeof head);
}void addedge(int u,int v,int w)    //加边操作
{e[cnt].to = v;e[cnt].v = w;e[cnt].next = head[u];head[u] = cnt++;
}for(int i = 1; i <= vertex; i++)    //遍历所有顶点的所有出边;
{for(int j = head[i]; j != -1; j = e[j].next)    //从head[i],通过不断找next[j],来遍历顶点i的所有出边;{cout<<i<<" "<<e[j].to<<" "<<e[j].v<<endl;}
}

看起来很高大上?其实并没有,只是将v[]换成结构体的to成员,w[]换成结构体的v成员,next[]换成结构体的next成员,(结构体数组相当于成员数组的集合嘛),first[]换成head[]而已, 那么有人就要问了,为什么不用u[]呢?因为这样存边在大多情况下,不需要用到u[],具体原因可以具体考虑,当然加上u成员也无妨只是多一点空间开销嘛,比如在Bellman-Ford算法中u成员将会带来很大的便利,还是要看使用情况而定的

 

当然,还有更直接,更接近于链表形式的邻接表表示法,那就是利用STL中的vector存一个图,这样的邻接表更易理解和使用,但是传说中有些题目可能卡vector而必须用数组模拟的方式写过,目前我还没有遇见这样的题目,但是这里还是介绍一下vector存图的方法(并且建议使用该方法):

首先明确,vector实质上是一个动态数组的玩意儿,就是不定长数组,根据需要分配空间的意思,关于vector的使用方法,由于篇幅有限,只介绍几个常用的:

#include<vector>
using namespace std;vector<int> a;a[0];                //直接按下标访问元素(不会检查是否越界)
a.push_back(3);    //将元素3插入数组最后
a.pop_back();     //删除数组中最后一个元素
a.insert(a.begin(),5);        //两个参数,插入位置,插入元素值
a.erase(a.end());          //一个参数,要删除的位置
a.empty();        //判断数组是否为空
a.clear();        //清空数组
a.size();        //返回数组此刻长度
a.begin();        //迭代器指针
a.end();           //迭代器尾指针
a.cbegin();        //常量头指针
a.cend();            //常量尾指针

根据邻接表和vector的特性,我只需要将顶点i的出边,都存在一个数组里vector[i],就好了!那么数组vector是什么类型的呢?应该是“边”这种类型,我们可以设一个结构体类型,如下:

#define maxn 5250    //最大点数;struct node
{int to,v;    //to代表到哪个顶点,v代表权值
};vector<node> e[maxn];    //每个点都有自己的邻接表,vector是一个node类型的数组

那么怎么插入边呢?

void addedge(int u,int v,int w)    //u->v = w;
{node tmp;tmp.to = v;tmp.v = w;e[u].push_back(tmp);    //关于顶点u的邻接表
}

是不是特别好理解?这样邻接表就建好了,那要怎么遍历一个顶点边的信息呢?

for(int i = 0; i < e[1].size(); i++)    //遍历1号顶点所有出边
{cout<<1<<e[1][i].to<<e[1][i].v<<endl;
}
//输出应该会是(根据上图):
1 4 9
1 2 5
1 3 7

将完整的模块拼在一起就是:

#define n 5250    //最大点数;
#include<vector>
using namespace std;struct node
{int to,v;    //to代表到哪个顶点,v代表权值
};vector<node> e[n+1];    //每个点都有自己的邻接表,vector是一个node类型的数组void init()
{for(int i = 0; i <= n; i++)e[i].clear();
}void addedge(int u,int v,int w)    //u->v = w,加边
{node tmp;tmp.to = v;tmp.v = w;e[u].push_back(tmp);    //关于顶点u的邻接表
}for(int i = 1; i <= n; i++)
{for(int j = 0; j < e[i].size(); j++)    //遍历i号顶点所有出边{cout<<i<<e[i][j].to<<e[i][j].v<<endl;    //顶点i的第j条出边}
}//根据上图,输出应该是:
1 4 9
1 2 5
1 3 7
2 4 6
4 3 8

当然,只有两个成员的结构体,我们可以考虑用pair代替(万能的STL啊!),注意make_pair(a,b);的快捷操作:

#define n 5250    //最大点数;
#include<vector>
using namespace std;
typedef pair<int,int> pir;    //pir.first代表到哪个顶点,pir.second代表权值vector<pir> e[n+1];    //每个点都有自己的邻接表,vector是一个pair类型的数组void init()
{for(int i = 0; i <= n; i++)e[i].clear();
}void addedge(int u,int v,int w)    //u->v = w,加边
{e[u].push_back(make_pair(v,w));    //关于顶点u的邻接表
}for(int i = 1; i <= n; i++)
{for(int j = 0; j < e[i].size(); j++)    //遍历i号顶点所有出边{cout<<i<<e[i][j].to<<e[i][j].v<<endl;    //顶点i的第j条出边}
}//根据上图,输出应该是:
1 4 9
1 2 5
1 3 7
2 4 6
4 3 8

对了,也许有人要问,为什么是e[i][j]呢?显然e[n]是一个二维数组啊,因为每一个点的出边本身是一个数组啊,不止有一条出边,所以相当于往动态数组里存数组,当然是二维数组了,可以访问e[i][j],表示,顶点i的第j条出边,关于pair,本身没什么好讲的,很简单就不介绍了.

今天关于存图的方法,就写到这里,相信该是够用了吧


http://chatgpt.dhexx.cn/article/o0AyYJ0q.shtml

相关文章

图的邻接矩阵和邻接表的比较

欢迎关注“软件开发理论”公众号获取干货 图的存储结构主要分两种&#xff0c;一种是邻接矩阵&#xff0c;一种是邻接表。 1.邻接矩阵 图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息&#xff0c;一个二维数组&#xff08;邻接矩阵&#xff09;存储图…

无向图邻接表实现

无向图邻接表实现 顶点&#xff1a;按照编号顺序将顶点数据存储在一维数组当中 关联同一个顶点的边&#xff08;以顶点为尾的弧&#xff09;&#xff1a;用线性链表存储 头结点&#xff1a;datafirstarc 表结点&#xff1a;adjvex&#xff08;邻接点的序号&#xff0c;存放…

邻接矩阵和邻接表

图的存储结构主要分两种&#xff0c;一种是邻接矩阵&#xff0c;一种是邻接表。 1.邻接矩阵 邻接矩阵的存储方式是用两个数组来表示图。一个一维数组储存图中顶点信息&#xff0c;一个二维数组储存图中的边或弧的信息。 无向图 这里右图是把顶点作为矩阵内行和列的标头罗列出…

图--邻接表

邻接表是图结构中的一种存储结构&#xff0c;适用于存储无向图和有向图。 邻接表存储图的实现方式是&#xff0c;给图中的各个顶点独自建立一个链表&#xff0c;用节点存储该顶点&#xff0c;用链表中其他节点存储各自的临界点。 与此同时&#xff0c;为了便于管理这些链表&a…

邻接表的实现(有向邻接表)、(无向邻接表),基于C++

邻接表 邻接矩阵的实现请看这里 是不错的一种图存储结构&#xff0c;但是&#xff0c;对于边数相对顶点较少的图&#xff0c;这种结构存在对存储空间的极大浪费。因此&#xff0c;找到一种数组与链表相结合的存储方法称为邻接表。 邻接表的处理方法是这样的&#xff1a; &a…

邻接表图文详解

在与链表相关的诸多结构中&#xff0c;邻接表是相当重要的一个。它是树与图结构的一般化存储方式&#xff0c; 邻接表可以看成“带有索引数组的多个数据链表”构成的结构集合。在这样的结构中存储的数据被分成若干类&#xff0c;每一类的数据构成一个链表。每一类还有一个代表元…

数据结构 图的邻接表

呃&#xff0c;下面该写邻接表了....... 邻接表的出现是因为图若是稀疏图&#xff0c;用邻接矩阵会造成空间的浪费&#xff0c;毕竟你要开辟一个一维数组和一个二维数组嘛&#xff0c;而且还是大开小用的那种。 邻接表为了避免内存的浪费引入了链式存储&#xff0c;它的处理办…

怎么画邻接表?不用邻接矩阵也能画?

目录 一、有向图的邻接表 二、无向图的邻接表 一、有向图的邻接表 最简单粗暴的方式就是把某个顶点发出的箭头指向的顶点全作为单个结点连接到此顶点的后面。结点数等于边数。 按正常思路的话&#xff0c;是一种递归遍历。 1.选一个点作为出发点。比如选一个v0。 2.从第一出…

数据结构之图(三)——邻接表

邻接表表示法(链式) 顶点&#xff1a; 按编号顺序将顶点数据存储在一维数组中。关联同一顶点的边&#xff1a; 用线性链表存储。如果有边\弧的信息&#xff0c;还可以在表结点中增加一项&#xff0c; 无向图的邻接表 例子&#xff1a; 特点&#xff1a; 邻接表不唯一若无向…

邻接表

邻接表&#xff1a;所谓邻接表&#xff08;adjacency list&#xff09;&#xff0c;就是把从同一个顶点发出的边链接在同一个称为边链表的单链表中。边链表的每个结点代表一条边&#xff0c;称为边结点。每个边结点有2 个域&#xff1a;该边终点的序号&#xff0c;以及指向下一…

图的邻接表

邻接表 图的邻接表存储方法跟树的孩子链表示法相类似&#xff0c;是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点&#xff0c;则把相邻顶点依次存放于表头结点所指向的单向链表中。如词条概念图所示&#xff0c;表结点存放的是邻接顶点在…

【数据结构】图的存储结构—邻接表

目录 什么是邻接表&#xff1f; 邻接表&#xff1a;定义 邻接表&#xff1a;相关类 邻接表&#xff1a;基本操作 1&#xff09;创建无向网 2&#xff09;创建有向网 3&#xff09;顶点定位 4&#xff09;插入边 5&#xff09;第一个邻接点 6&#xff09;查询下一个邻…

图的存储结构---邻接表

1. 邻接表&#xff08;无向图&#xff09; 对于边数相对顶点较少的图&#xff0c;可采取邻接表。把数组与链表结合在一起来存储&#xff0c;这种方式在图结构也适用&#xff0c;称为邻接表&#xff08;AdjacencyList&#xff09;。 2. 邻接表&#xff08;有向图&#xff09…

图的存储结构——邻接表

图的存储结构之邻接表 一、邻接表表示法无向图的邻接表有向图的邻接表有向图的逆邻接表 二、图的邻接表存储表示三、采用邻接表表示法创建无向网 一、邻接表表示法 回忆在线性表时&#xff0c;顺序存储结构就存在预先分配内存可能造成存储空间浪费的问题&#xff0c;于是引出了…

vivado实现FFT和IFFT信号处理

一&#xff0c;FFT的物理意义 FFT是离散傅立叶变换的快速算法&#xff0c;可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的&#xff0c;但是如果变换到频域之后&#xff0c;就很容易看出特征了。这就是很多信号分析采用FFT变换的原因。另外在频谱分析方面&a…

ifft java_OpenCV DFT_INVERSE与Matlab的ifft不同

我尝试使用opencv的dft函数过滤信号 . 我试图这样做的方式是在时域中获取信号&#xff1a; x [0.0201920000000000 -0.0514940000000000 0.0222140000000000 0.0142460000000000 -0.00313500000000000 0.00270600000000000 0.0111770000000000 0.0233470000000000 -0.00162700…

4.8 IFFT/FFT

4.8.1 IFFT/FFT原理 1. IFFT、FFT在OFDM系统中的应用 2、IFFT/FFT原理 3、DIT的基2FFT算法 4、DIF的基2FFT运算 5、基于的频率抽选FFT算法 4.8.2 基于 DIF FFT的硬件 结构 1、各级的蝶形运算硬件实现 2、transfer12、 transfer34、transfer56硬件实现 3、transfer23、 transfe…

Python实现FFT及IFFT

运行环境及编译工具 WindowsVS Code 编程语言及库版本 库版本Python3.7.0copy无numpy1.19.2opencv3.4.2PIL8.1.0matplotlib3.4.3 可执行文件 HW_2.pyHW_2.ipynb在HW_2.ipynb中执行&#xff0c;详细程序信息在HW_2.py中 问题 1 通过计算一维傅里叶变换实现图像二维快速傅里…

matlab FFT 和IFFT

代码&#xff1a; fs100;N128; n0:N-1;tn/fs; xsin(2*pi*40*t)sin(2*pi*15*t); subplot(221);plot(n,x,b); xlabel(时间/s);ylabel(x);title(原始信号); grid on;yfft(x,N); magabs(y); fn*fs/N; subplot(222);plot(f(1:N/2),mag(1:N/2)*2/N,b); xlabel(频率/Hz);ylabel(振幅)…

基于vivado实现FFT/IFFT

文章目录 前言一、基本过程二、vivado配置1.新建工程2.调用DDS的IP核2.调用FFT的IP核 三、编写Verilog程序1.顶层文件fft.v2.仿真文件fft_tb.v 四、运行仿真1. 运行仿真设置2. 仿真波形设置3. 结果分析 前言 使用vivado2018.3实现FFT&#xff0f;IFFT&#xff0c;过程比较详细…