图论(课堂笔记)

图的定义

一个 图(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];
}
posted @ 2025-07-22 14:01  Yangyihao  阅读(15)  评论(0)    收藏  举报