图与存图
作为图论的基础,灵活运用各种方法十分重要
概论
点与边的集合
基本元素
点:必有
边:不一定有
图类型
按方向分
有向图
1. u,v间只存一边
2. 边数=n*(n-1)/ 2;
3.
无向图
1.u,v之间存两边(即u->v和v->u)
2.边数=n*(n-1);
3.
按边数分
完全图
1.所有 点与点之间均相连,边数取最大
稀疏图与稠密图
- 相对概念
- 稀疏图:与完全图边数相差较多
- 稠密图:接近完全图
程序实现
存储
邻接矩阵
时间复杂度 O(nn),n为点数
空间复杂度 (nn)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | … | |
---|---|---|---|---|---|---|---|---|---|
1 | |||||||||
2 | |||||||||
3 | |||||||||
4 | |||||||||
5 | |||||||||
6 | |||||||||
7 | |||||||||
8 | |||||||||
… |
a[i][j]表示点 i 与点 j 之间是否有路
代码实现
存储
for(int i=1;i<=n;++i) {
scanf("%d %d %d",&u,&v,&w); //u - v,边权为w
a[u][v]=w;
// a[v][u]=w; 无向图
}
遍历
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) {
if(a[i][j]) {
printf("%d->%d %d\n",i,j);
}
}
}
vector
时间复杂度 O(m)(有向图),O(2m) (无向图) m为边数
空间复杂度 有向图m,无向图为2m
vector,向量,STL库内容
可理解为 动态数组
动态存储实现:当前存满时,在内存中重新开辟 1.5(或2)倍大小(VS1.5 GCC 2),将原先的内容复制至新空间 (耗时,部分题会卡时间,但到底卡不卡,无法得知)
代码实现
struct Edge{
int w;
int to;
}e;
vector<Edge> a[10086]; //点数
int m,n; //边数 点数
int main() {
scanf("%d %d",&m,&n);
for(int i=1;i<=m;++i) {
scanf("%d %d %d",&u,&v,&w);
e.to=v;
e.w=w;
a[u].push_back(e);
// e.to=u; 无向图
// a[v].push_back(e);
}
// 遍历
for(int i=1;i<=n;++i) {
for(int j=0;j<a[i].size();j++) {
printf("%d->%d %d\n",&i,&a[i][j].to,&a[i][j].w);
}
}
}
链式前向星
时间复杂度 O(m)(有向图)
空间复杂度 有向图为m 无向图为2*m
节点
存储
注意,顺序为 ① ②,不能反,否则原先的节点就会丢失
(努力白费)
存储基本逻辑
将(u,v)边标号,待插入的边的后继赋值为head【i】,将head【u】更改为当前边的编号
代码实现
int u,v,w;
int n,m;
int tot;
struct Edge{
int dis; //边权
int to; //下一点
int next; //下一边
}e[10001]; //边数
int head[100005]; //点数
void add_edge (int uu,int vv,int ww) { //写成函数更 优雅
tot++;
e[tot].next=head[u];
e[tot].to=vv;
e[tot].dis=ww;
head[u]=tot;
}
int main() {
scanf("%d %d",&n,&m); //点数 边数
for(int i=1;i<=m;++i) {
scanf("%d %d %d",&u,&v,&w);
add_edge(u,v,w);
// add_edge(v,u,w); 无向图
}
for(int i=1;i<=n;++i) {
for(int j=head[i];j;j=e[j].next) {
printf("%d->%d %d\n",i,e[j].to,e[j].dis);
}
}
}
优缺点比较
邻接矩阵
优点
- 简单易懂
- (无)
缺点
- 空间复杂度 (nn),时间复杂度O(nn)
- 当图较稀疏时,浪费空间与时间,容易TLE,有时无法开这这这么大的数组
vector
优点
- 空间时间复杂度稍微友好
- 易懂
缺点
- 动态开辟时耗时
链式前向星
优点
- 空间时间复杂度友好
缺点
- 实现较复杂
(懂了就简单,不懂就难)