我们在学习图论的时候,图的存储是很必要的,毕竟你不存图就没法解决这种问题。
对于一些点数比较小的图,我们可以使用二维数组存图。
这种方法的优点是他的存图的 存储和插入 的时间复杂度都是 O ( 1 ) O(1) O(1)(非常高效)。
但是,事物都是具有两面性的,这种邻接矩阵的缺点就在于如果这张图的边数非常的少,那么他的空间将会浪费非常多。(如下图)
邻接矩阵的空间复杂度是 O ( n 2 ) O(n^2) O(n2)。
#include
using namespace std;
int edge[405][405];
int n,m,u,v;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>u>>v;
edge[u][v]=edge[v][u]=1;
//表示两个点可以连接一条边
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<edge[i][j]<<' ';
}
cout<<'\n';
}//输出邻接矩阵
return 0;
}
使用动态数组存图需要我们拥有一定的 STL 知识。
大概介绍一下 vector
的用法:
专用函数:(以上都是以 0 0 0 为准量,若为 1 1 1 ,需要做偏移)
(1) vt.size()
求动态数组的长度。
(2) vt.push _ back(a)
把 a a a 变量存入 v t vt vt
(3) vt.resize(a)
重设数组长度为 a a a ,多的退,少的增。
(4) vt.clear()
清空动态数组
(5) vt.insert(vt.begin()+x,y)
将第 x x x 个位置加入 y y y ,其他元素后移。
(6) vt.erase(vt.begin()+x-1,vt.begin()+y)
将区间 x x x 到 y y y 删掉(包括地址,内存)
(7) vt.erase(vt.begin()+x)
删除第 x x x 个元素(包括地址,内存)
(8) pop_back()
剔除队尾元素。
等等……
我们发现只要我们开启一个二维的动态数组就不会收到内存空间的限制了。
即 vector 存图的空间复杂度是 O ( m ) O(m) O(m)( m m m 为图的边数)。
但是 vector 比较难于判断一个点是否与另一个点保持联通,查找时间复杂度最坏为 O ( n ) O(n) O(n)。( n n n 为节点个数)
#include
using namespace std;
int n,m,u,v;
vector<int> edge[405];//开启一个二维的动态数组
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>u>>v;
edge[u].push_back(v);
edge[v].push_back(u);//STL 使用
}
for(int i=1;i<=n;i++){
for(int j=0;j<edge[j].size();j++)cout<<edge[i][j]<<' ';
cout<<'\n';
}//输出为所有连接节点 i 的点。
return 0;
}
我们也可以使用结构体 struct
存图。
好处是可以将所有边都统计起来,以便于实现排序等算法。
坏处和 vector
相同:都无法快速判断两个点是否联通,最坏时间复杂度 O ( m ) O(m) O(m)( m m m 为边的个数)。
#include
using namespace std;
int n,m;
struct node{
int u,v,value;
//起点,终点,边权
}edge[405];
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>edge[i].u>>edge[i].v>>edge[i].value;
}
for(int i=1;i<=m;i++){
cout<<edge[i].u<<" "<<edge[i].v<<" "<<edge[i].value<<'\n';
//输出每一条边的起点,终点,边权
}
return 0;
}
这个比较不好解释,就不说了。
存图的方法很多,每一种方法都有其独特的特点,我们针对不同的题目需要采取不同的存图方法。