以下我用比较通俗易懂的话来讲,所以可能不是很“专业”。
通俗来讲,邻接矩阵就是用一个二维数组来存储一个图的信息。 我们用a[i][j]来表示 点i---->j有一条边权值为a[i][j];如果i--->j无边则a[i][j]=0;
//输入u,v,w 表示有一条u指向v权值为w的单向边。
for (int i = 1; i <= m; ++i) {
int u, v ,w;
cin >> u >> v >> w;
a[u][v] = w;
}
//注意是单向边
这样就可以暴力储存全部信息了
复杂度
查询是否存在某条边:O(1)
遍历一个点的所有出边:O(n)
遍历整张图:O(N^2)
空间复杂度:O(N^2)
应用
邻接矩阵只适用于没有重边(或重边可以忽略)的情况。
其最显著的优点是可以 O(1) 查询一条边是否存在。
由于邻接矩阵在稀疏图上效率很低(尤其是在点数较多的图上,空间无法承受),所以一般只会在稠密图上使用邻接矩阵。
遍历一个点的所有出边也很简单:
for(i=1;i<=n;i++)
if(a[u][i]!=0) {........}
但邻接矩阵会浪费很多空间。对于一些点数很大的图我们通常无法将图储存下来(比如一个点只连两条边 我们却要为它开长度为N的数组)
于是我们便可以用邻接表
对于邻接矩阵我们知道如果一个点只有几条边,我们便没必要为它浪费空间,只需要记录有用的边的信息再结合我们学过的 vector(可以动态开数组)的优点 便诞生了邻接表
使用一个支持动态增加元素的数据结构构成的数组,如 vector<int> adj[n + 1]
来存边,其中 adj[u]
存储的是点u的所有出边的相关信息(终点、边权等)。
vector<vector<int> > adj;
int main()
{
adj.resize(n + 1);//先为每个点预留空间
for (int i = 1; i <= m; ++i)
{
int u, v;
cin >> u >> v;
adj[u].push_back(v);//u这个点加一条边指向v
}
}
这样我们就可以解决问题了。
接下来我们来介绍链式前向星
其实邻接表已经可以解决问题了但我们还是推荐用链式前向星:
内存: vector 是 c++STL 中的 动态连续存储线性表, vector在每次扩充容量时默认都会多申请2倍的空间
时间: STL的调用, 内部实现都导致 vector 是一个非常慢的结构
这样容易爆,所以来看链式前向星:
关于链式前向星,其实就是用一个链表将每条边的信息储存下来
我们定义h[N]数组表示 点 i最后一次加入的边的编号
用数组e[n]来储存第i条边的信息
struct node{
int to,ne,value;
}e[N];
//当我们新加入一条边时 (u, v, w)u--->v 权值为w;
void add(int u,int v,int w)
{
cnt++;//边的个数;
e[cnt].to=v;
e[cnt].ne=h[u];//e[cnt].ne=点u最后一次加边时边的编号,这样方便我们遍历一个点的所有出边
e[cnt].value=w;
h[u]=cnt;//更新点u
}
// 遍历 u 的出边
for (int i = h[u]; i!=0; i = e[i].ne)
v=e[i].to;