邻接矩阵
给如下一个有向图:
该图有四个节点,五条边。右侧表示了有向图的起点,终点以及边权
根据该图建立邻接矩阵:
可以发现,邻接矩阵中许多位置并没有使用,故浪费了空间与时间
邻接表
让起点与终点直接相连,而不考虑先后顺序
将终点和边权作为一个节点,按头插法插入每个起点位置,就构建了一个邻接表
链表实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
using namespace std;
struct node{
int x; // 节点编号
int w; // 某个顶点到x的权值
node* next;
node(int x_, int w_){
this->x = x_;
this->w = w_;
next = nullptr;
}
};
vector<node*> head;
void add_edge(int u, int v, int w){
node* a = head[u]; // 现在的头
node* b = new node(v,w); // 建立新的节点
// 头插法
b->next = a->next;
a->next = b;
}
int main(){
int n, m;
cin >> n >> m;
head.resize(n+1);
for(int i = 0;i<=n;i++){
head[i] = new node(i,0);
}
for (int i = 0; i<=m;i++){
int u,v,w;
cin >> u >> v >> w;
add_edge(u,v,w);
}
// 遍历
for (int i = 1;i<=n;i++){
node* p = head[i]->next; // 从某个节点开始,指向其相邻节点并输出
while(p){
// 起点总是i,而i连接的所有节点均为其兄弟
printf("(%d, %d, %d)",i,p->x,p->w);
p = p->next;
}
cout << endl;
}
return 0;
}数组实现
建立四个数组
edge[i]:表示索引为i的边的终点,大小为mnext[i]:表示索引为i的边的下一条兄 弟边的索引,大小为mhead[i]:表示i的第一条边的索引,大小为nweigh[i]:表示索引为i的权重,大小为m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>
using namespace std;
vector<int> head, edge, Next, weight;
int idx = 0; // 表示边的索引
void add_edge(int u, int v, int w){
edge[idx] = v;
weight[idx]= w;
Next[idx] = head[u];
head[u]=idx++;
}
int main(){
int n, m;
cin >> n >> m;
head.resize(n+1,-1);
edge.resize(m+1);
Next.resize(m+1);
weight.resize(m+1);
for (int i = 0; i<=m;i++){
int u,v,w;
cin >> u >> v >> w;
add_edge(u,v,w);
}
// 遍历
for (int i = 1;i<=n;i++){
for (int j = head[i]; ~j; j=Next[j]){
int u = i;
int v = edge[j];
int w = weight[j];
printf("(%d, %d, %d)", u, v, w);
}
cout << endl;
}
return 0;
}链式前向星
图的多种表示
- 输入数据模式
1
2
3
4
5
6
4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7- 平面图
- 邻接矩阵
- 邻接表
数组实现
用一个数组与一个结构体:
- head数组,大小为顶点个数,保存边的下标,初始化为-1,表示从i出发没有边
- edge结构体,大小为边个数,元素表示终点、距离与下一条边的位置
如输入1 4 9,表示从1出发,终点为4,距离为9
检查是否有从1出发的边,没有(-1),next改为-1,此时第一条边从1出发,将head[1]=1
再输入4 3 8
检查是否有从4出发的边,没有(-1),next改为-1,此时第二条边从4出发,将head[4]=2
再输入1 2 5
检查是否有从1出发的边,head[1]=1,next改为1,此时第三条边也从1出发,将head[1]=3
再输入2 4 6
检查是否有从2出发的边,没有(-1),next改为-1,此时第四条边从2出发,将head[2]=4
再输入1 3 7
检查是否有从1出发的边,head[1]=3,next改为3,此时第五条边也从1出发,将head[1]=5
最终为
即实现了头插法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int n, m, s, u, v, w, cnt;
int head[100005];
struct edge{
int to, dis, nxt;
}edge[200005];
void Add_edge(int from, int to, int w){
edge[++cnt].to = to;
edge[cnt].dis = w;
edge[cnt].nxt = head[from];
head[from]=cnt;
}
void solve() {
cin >> n >> m;
fill(head, head+n+1,-1);
for (int i = 1;i<=n;i++){
cin >> u >> v >> w;
Add_edge(u,v,w);
}
// 遍历
for (int i = 1;i<=n;i++){
for (int k = head[i];~k;k = edge[k].nxt){
/*具体处理*/
}
}
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int tomorin=1;
cin >> tomorin;
while (tomorin--) solve();
return 0;
}十字链表
十字链表分为两部分:
顶点节点存储权值、入度(firstIn)与出度(firstOut);弧节点存储弧尾顶点在顶点数组中的位置(tailVex),弧头顶点在顶点数组中的位置(headVex),指向弧头相同的下一条弧(hLink),指向弧尾相同的下一条弧(tLink)与边权(Info)
每个节点组成了两个链表:以当前节点为弧头的弧组成的链表、以当前节点为弧尾的弧组成的链表
在寻找时自己找自己,即若为弧头组成链表,则依次寻找弧头相同的组成链表(firstIn->headVex);若为弧尾组成链表,则依次寻找弧尾相同的组成链表(firstOut->tailVex)
邻接多重表
邻接多重表分为两部分:
顶点节点存储权值与指向的第一条跟该顶点有关系的边
表节点:
- mark:标志域,标记某节点是否已经被操作过,例如遍历各节点时,为0表示还未遍历,为1表示遍历过
- ivex与jvex:表示节点表示的边两端的顶点在数组中的位置下标
- ilink与jlink:表示指向下一条与ivex,jvex相关的边
- info:边的权值
邻接多重表也遵循自己找自己,但比起十字链表更加灵活(两节点位置可互换),思路与十字链表类似