一、图的存储
在我们存储图的时候,主要使用邻接矩阵、邻接表两种方式来存储。通常邻接矩阵存储稠密图(边多),临界矩阵存储稀疏图(边少)。
1.1 邻接矩阵存储
邻接矩阵听起来比较高大上,其实就是用二维数组来表示 \(a\) 点与 \(b\) 点之间有一条边。例如在上述无向图中 \(1\) 与 \(4\) 之间有一条无向边。用邻接矩阵来存储的话就是
表示点 \(1\) 到点 \(4\) 之间的权重是 \(19\) ,因为是无向边所以 \(4\) 也可以到 \(9\) 权重是 \(19\) 。如果是有向图,建边的时候仅能根据方向建边。
可见邻接矩阵存储非常 \(ez\) 但是由于内存的限制,点也肯定不会太多。如果点很多 \(( 10^4 )\) 就仅能用邻接表存储了。
根据以上原理,我们上述无向图的存储结果便是:
1.2 邻接表存储
邻接表也一样,听起来非常高大上,其实相当简单。他就是用链表的思想。如下图所示
例如:点 \(1\) 可以到达的点有 \(\{2, 3, 4\}\) ,点 \(4\) 可以到达的点有 \(\{1,2,3,5,7,9\}\) 以此不断类推。关于边权,我们也仅需再开一个数组同时来维护即可。
二、图的存储代码实现
如果我们有如下读入数据
5 6
1 2 2
2 3 1
1 3 4
2 5 3
3 5 1
4 2 4
第一行包含整数 \(n\) 和 \(m\)。代表给定一个 \(n\) 个点 \(m\) 条边的有向图。
接下来 \(m\) 行每行包含三个整数 \(x,y,z\) ,表示存在一条从点 \(x\) 到点 \(y\) 的有向边,边长为 \(z\)。
2.1 邻接矩阵存储实现
const int N = 1010;
int g[N][N];
int main()
{int n, m;std::cin >> n >> m;memset(g, 0x3f, sizeof g);for(int i = 1; i <= m; i++){int x, y, z;std::cin >> x >> y >> z;g[x][y] = z; // 如果有重边则根据题意保留小边或大边// g[y][x] = z; // 如果是无向图,点 y 到点 x 也需要建边}
}
N = int(1e3 + 10)
INT_MAX = int(2e10)
g = [[INT_MAX for i in range(N)] for j in range(N)]
n, m = map(int, input().split())
for t in range(m):x, y, z = map(int, input().split())g[x][y] = z # 如果有重边则根据题意保留小边或大边# g[y][x] = z; 如果是无向图,点 y 到点 x 也需要建边
2.2 邻接表利用 vector
实现
int main()
{int n, m;std::cin >> n >> m;std::vector<int> e[n + 1], w[n + 1]; // 注意这里是中括号for(int i = 1; i <= m; i++){int x, y, z;std::cin >> x >> y >> z;e[x].push_back(y); // 邻接表无法处理重边,直接存就好,后面有最短路的问题,再在后面做处理即可w[x].push_back(z);// 如果是无向图也需要添加, e[y].push_back(x); w[y].push_back(z);}
}
n, m = map(int, input().split())
e = [[] for i in range(n + 1)]
w = [[] for i in range(n + 1)] # 或 w = e[:] 这里一定要切片
for i in range(m):x, y, z = map(int, input().split())e[x].append(y)w[x].append(z)# 如果是无向图也需要添加, e[y].append(x); w[y].append(z);
2.3 邻接表用数组实现(静态的)【链式前向星】
const int N = 1e5 + 10, M = 5e5 + 10;
int h[N], ne[M], e[M], w[M], idx;
int d[N];
int n, m;void add(int a, int b, int c){ne[idx] = h[a], e[idx] = b, w[idx] = c, h[a] = idx++;
}int main()
{memset(h, -1, sizeof h); // 切记这里的头链表一定要初始化成 -1std::cin >> n >> m;for(int i = 1; i <= m; i++){int a, b, c;std::cin >> a >> b >> c;add(a, b, c); // 同理,如果无向图需要 add(b, a, c);}
}
n, m = map(int, input().split())
INF = 1e12
N = 1000000 + 10
h, e, w, ne = [-1] * N, [0] * N, [0] * N, [0] * N
st = [False] * Nfor i in range(m):a, b, c = map(int, input().split())add(a, b, c)# 同理无向图需要 add(b, a, c)
三、图的遍历代码实现
关于图和树的遍历都是用 \(dfs\) 或 \(bfs\) 进行遍历的。
3.1 邻接矩阵遍历
bool st[N];
void dfs(int u)
{st[u] = true;for(int i = 1; i <= n; i++){if(g[u][i] != 0 && !st[i]) dfs(i); // 只要点 u 可以达到点 i ,且点 i 没有被访问过,进行遍历即可。// 具体的遍历法制需要根据题意进行修改}
}
st = [False] * (n + 1)
def dfs(u):st[u] = Truefor i in range(1, n + 1):if g[u][i] != 0 and not st[i]: dfs(i)
3.2 邻接表遍历 (vector)
bool st[N];
void dfs(int u)
{st[u] = true;for(int i = 0; i < e[u].size(); i++){if(!st[e[u][i]]) dfs(e[u][i]); // 只要点 u 可以达到点 i ,且点 i 没有被访问过,进行遍历即可。}
}
st = [False] * (n + 1)
def dfs(u):st[u] = Truefor i in range(len(e[u])):if st[e[u][i]]: dfs(e[u][i])
3.3 邻接表遍历(链式前向星)
bool st[N];
void dfs(int u) {if(st[u]) return;st[u] = true;for (int i = h[u]; i != -1; i = ne[i]){int j = e[i]; // 这个点去哪里dfs(j);}
}
st = [False] * (n + 1)
def dfs(u):st[u] = Truei = h[u]while i != -1:j = e[i]dfs(j)i = ne[i]