图论(课堂笔记)
图的定义
一个 图(Graph) 是由顶点(Vertex) 和 边(Edge)
定义:
一个图G是有序二元数对\((V, E)\)的集合,满足:
\(V\)是所有顶点的集合
\(E\)是所有边的集合,即\(E \sube V \cdot V\)
简单图:
没有重边和自环的图
图的分类
无向图(Undirected Graph)
边没有方向,边\((u,v)\)和边\((v,u)\)表示同一条边。
有向图(Directed Graph/Digraph)
边有方向,边\((u,v)\)表示从\(u\)指向\(v\)的边。
加权图(Weighted Graph)
边带有权重或成本值。
无权图(Unweighted Graph)
边没有权重。
基本术语
度(Degree)
与顶点相连的边数(有向图中分为入度和出度)
路径(Path)
顶点序列,其中每对相邻顶点由边连接
环(Cycle):
起点和终点相同的路径
连通图(Connected Graph)
任意两个顶点间都有路径相连(无向图)
强连通图(Strongly Connected Graph)
任意两个顶点间都有双向路径(有向图)
图的存储
邻接表
用graph[u][]表示与u相连的点
常用vector实现
struct edge{
int from, to, w; // from:起点,to:终点,w:wealth,边权
edge(int a, int b, int c) : from(a), to(b), w(c){}
};
vector<edge> graph[N];
int main(){
// 初始化
for (int i = 1; i <= n; i++)
graph[i].clear();
// 存边
graph[a].push_back(a, b, c);
}
邻接矩阵
用一个矩阵\(graph_{ij}\)表示\(i\)和\(j\)之间是否有边,记录边权
int graph[N][N]
//如果边(i, j)存在
graph[i][j] = 1;
//有边权
graph[i][j] = w;
//不存在
graph[i][j] = 0;
// Or
graph[i][j] = -INF;
链式前向星
链式前向星结合了邻接表以及邻接矩阵的优点,空间复杂度仅为\(O(E)\)
具体的看代码
const int MAXN = 100010; // 最大顶点数
const int MAXM = 200010; // 最大边数(无向图要×2)
struct Edge {
int to; // 边的终点
int next; // 下一条边的索引
int weight; // 边权(可选)
} edges[MAXM]; // 边集数组
int head[MAXN]; // head[u]表示以u为起点的第一条边的索引
int edge_count = 0; // 当前边数
// 初始化
void init() {
edge_count = 0;
memset(head, -1, sizeof(head)); // -1表示没有下一条边
}
// 添加边
void add_edge(int u, int v, int w = 1) {
edges[edge_count].to = v;
edges[edge_count].weight = w;
edges[edge_count].next = head[u]; // 将新边插入链表头部
head[u] = edge_count++; // 更新头指针
}
// 遍历u的所有邻接点
void traverse(int u) {
cout << "Vertex " << u << "'s neighbors: ";
for (int i = head[u]; i != -1; i = edges[i].next) {
int v = edges[i].to;
int w = edges[i].weight;
cout /*<< "(" */<< v << /*", " <<*/ w /*<< ") "*/ << " ";
}
cout << "\n";
}
最短路
Dijkstra
Dijkstra算法是荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)在1956年提出的经典算法
本质上是“优先队列+BFS”
用于处理无负权的带权图
复杂度\(O((V+E)\log V)\)
vector<pair<int, int>> graph[Maxn]; // 存储邻接表和边权
pair<vector<int>, vector<int>> Dijkstra(int source, int n) {
vector<int> dist(n + 1, INF); //初始化为INF
vector<int> prev(n + 1, -1); //初始化为-1
dist[source] = 0; //距离自己是0
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minheap; //小根堆
minheap.push({0, source});
while (!minheap.empty()) {
auto curr = minheap.top();
minheap.pop();
int current_dist = curr.first;
int u = curr.second;
if (current_dist > dist[u]) continue;
for (auto &edge : graph[u]) { //遍历每一条边
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
prev[v] = u;
minheap.push({dist[v], v});
}
}
}
return {dist, prev}; //返回距离以及前驱,可用于找路径
}
Bellman-Ford
Bellman-Ford算法是一种用于计算带权图中单源最短路径的算法,由理查德·贝尔曼(Richard Bellman)和Lester Ford Jr.提出。它可以处理负权边(但不能处理负权环)的情况。
具体步骤
初始化:将所有节点的最短距离估计值设为\(\infty\),源点设为\(0\)
松弛操作:对每条边进行\(V-1\)次松弛操作(\(V\)为顶点数)
检查负权环:再进行一次松弛操作,如果还能松弛则说明存在负权环
复杂度\(O(VE)\)
vector<edge> graph;
pair<bool, pair<vector<int>, vector<int>>> BellmanFord(int source, int n) {
vector<int> dist(n + 1, INF);
vector<int> prev(n + 1, -1);
vector<int> steps(n + 1, 0);
dist[source] = 0;
for (int i = 1; i < n; i++) {
bool flag = false;
for (const auto& e : graph) {
if (dist[e.from] != INF) {
if (dist[e.to] > dist[e.from] + e.w ||
(dist[e.to] == dist[e.from] + e.w && steps[e.to] > steps[e.from] + 1)) {
dist[e.to] = dist[e.from] + e.w;
prev[e.to] = e.from;
steps[e.to] = steps[e.from] + 1;
flag = true;
} else if (dist[e.to] == dist[e.from] + e.w && steps[e.to] == steps[e.from] + 1) {
if (e.from < prev[e.to]) {
prev[e.to] = e.from;
flag = true;
}
}
}
}
if (!flag) break;
}
bool has_negative_cycle = false;
for (const auto& e : graph) {
if (dist[e.from] != INF && dist[e.to] > dist[e.from] + e.w) {
has_negative_cycle = true;
break;
}
}
return {has_negative_cycle, {dist, prev}};
}
Floyd-Warshall
本质是一种dp算法
复杂度\(O(V^3)\)
使用邻接表存储
直接看代码
void FloydWarshall(vector<vector<int>>& dist, vector<vector<int>>& next, int n) {
// 初始化next矩阵:记录i->j的路径的第一个中间节点
next.assign(n + 1, vector<int>(n + 1, -1));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; ++j)
if (i != j && dist[i][j] != INF)
next[i][j] = j;
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (dist[i][k] != INF && dist[k][j] != INF && dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j]; //最关键的地方
next[i][j] = next[i][k];
}
// else if (dist[i][j] == dist[i][k] + dist[k][j])
// if (next[i][j] > next[i][k])
// next[i][j] = next[i][k];
}

浙公网安备 33010602011771号