图论
以下只考虑简单图:两个节点之间同方向至多有一条边。
最短路径
单源最短路径
T
(
n
)
=
Θ
(
∣
E
∣
⋅
T
d
k
+
∣
V
∣
⋅
T
e
m
)
T(n)=\Theta(|E| \cdot T_{dk}+|V| \cdot T_{em})
T(n)=Θ(∣E∣⋅Tdk+∣V∣⋅Tem)
LeetCode743
朴素dijkstra算法
OSPF协议(链路状态算法)使用dijkstra算法。
松弛在一个可随机访问的数组里,
T
d
k
=
O
(
1
)
T_{dk}=O(1)
Tdk=O(1)。线性搜索并弹出最小节点所需时间复杂度为
T
e
m
=
O
(
∣
V
∣
)
T_{em}=O(|V|)
Tem=O(∣V∣)。
因此整个算法时间复杂度为
Θ
(
∣
V
∣
2
)
\Theta(|V|^2)
Θ(∣V∣2)。
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
int res = -1;
unordered_map<int, int> unvisited;
vector<int> dist(n+1, INT_MAX);
dist[k]=0;
vector<vector<pair<int,int>>> adjacent_link(n+1, vector<pair<int,int>>());
for(int i = 1; i <= n; i++){
if (i==k) continue;
unvisited.insert({i, INT_MAX});
}
for(const auto& edge:times){
adjacent_link[edge[0]].push_back({edge[1], edge[2]});
if(edge[0]==k){
unvisited[edge[1]]=edge[2];
dist[edge[1]]=edge[2];
}
}
while(!unvisited.empty()){
pair<int,int> cur = {-1, INT_MAX};
for(const auto& vertex:unvisited){
if(vertex.second<cur.second){
cur = vertex;
}
}
if(cur.second==INT_MAX){
return -1;
}
unvisited.erase(cur.first);
for(auto&adjacent:adjacent_link[cur.first]){
if(!unvisited.count(adjacent.first)) continue;
if(adjacent.second!=INT_MAX&&unvisited[adjacent.first]>adjacent.second+cur.second){
unvisited[adjacent.first]=adjacent.second+cur.second;
dist[adjacent.first] = unvisited[adjacent.first];
}
}
}
for(int i = 1; i <= n; i++){
res = dist[i]>res?dist[i]:res;
}
return res;
}
};
堆优化dijkstra算法
松弛需要在一个优先级队列里进行入队,弹出需要恢复队列结构,两者都是
O
(
log
(
∣
V
∣
)
)
O(\log(|V|))
O(log(∣V∣))
因此整个算法时间复杂度为
Θ
(
(
∣
V
∣
+
∣
E
∣
)
⋅
log
(
∣
V
∣
)
)
\Theta((|V|+|E|) \cdot \log(|V|))
Θ((∣V∣+∣E∣)⋅log(∣V∣))。
适合稀疏图、稠密图复杂性更高
Θ
(
∣
V
∣
2
⋅
log
(
∣
V
∣
)
)
\Theta(|V|^2 \cdot \log(|V|))
Θ(∣V∣2⋅log(∣V∣))。
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
int res = -1;
unordered_set<int> visited;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>> pq;
vector<int> dist(n+1, INT_MAX);
dist[k]=0;
vector<vector<pair<int,int>>> adjacent_link(n+1, vector<pair<int,int>>());
for(const auto& edge:times){
adjacent_link[edge[0]].push_back({edge[1], edge[2]});
}
pq.push({0,k});
while(!pq.empty()){
pair<int,int> cur = {-1, INT_MAX};
cur = pq.top(); pq.pop();
if(cur.second==INT_MAX){
return -1;
}
if(visited.count(cur.second)) continue;
for(auto&adjacent:adjacent_link[cur.second]){
if(adjacent.second!=INT_MAX&&dist[adjacent.first]>adjacent.second+cur.first){
dist[adjacent.first] = adjacent.second+cur.first;
pq.push({dist[adjacent.first], adjacent.first});
}
}
visited.insert(cur.second);
}
for(int i = 1; i <= n; i++){
res = dist[i]>res?dist[i]:res;
}
if(res==INT_MAX){
return -1;
}else{
return res;
}
}
};
有负环的最短路径 Bellman-Ford算法
Bellman-Ford方程:
D
i
s
(
s
,
t
)
=
min
v
∈
a
d
j
a
(
s
)
{
W
(
s
,
v
)
+
D
i
s
(
v
,
t
)
}
Dis(s,t)=\min_{v \in adja(s)}\{W(s,v)+Dis(v,t)\}
Dis(s,t)=minv∈adja(s){W(s,v)+Dis(v,t)}。
RIP协议(距离向量算法)使用Bellman-Ford方程。
未优化DP表维度版本:
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int n, int k) {
int n_edges = times.size();
vector<vector<int>> memo(n + 1, vector<int>(n + 1, INT_MAX));
memo[k][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++){
memo[j][i]=memo[j][i-1];
}
for (int j = 1; j <= n_edges; j++) {
const auto& edge = times[j - 1];
int u = edge[0], v = edge[1], w = edge[2];
if (memo[u][i - 1] != INT_MAX) {
memo[v][i] = memo[v][i] <= (memo[u][i - 1] + w)
? memo[v][i]
: (memo[u][i - 1] + w);
}
}
}
int res = 0;
for (int i = 1; i <= n; i++) {
res = res > memo[i][n] ? res : memo[i][n];
}
return res == INT_MAX ? -1 : res;
}
};
优化DP表维度版本:
多数直觉上
2
2
2维的DP,如果不需要回溯给出具体方案,可以改为滚动DP。例如背包问题。
多源最短路径与有负权边 Floyd算法
可以处理负边但不能处理负环,复杂度为
O
(
∣
V
∣
3
)
O(|V|^3)
O(∣V∣3)。每次考虑以前
k
k
k个为中介点,对所有
i
,
j
i,j
i,j节点对进行松弛。矩阵上一次的值表示前
k
−
1
k-1
k−1个为中介点的结果,第k次只需比较是否使用节点
k
k
k的情况。使用DP思想。
对角线在松弛中非负即代表出现了负环。
用prev[][]存储最短路径树即可进行path的重构。prev[u][v]存储u~>v路径上的倒数第二个节点。
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_set>
#include <climits>
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> grid;
int main(){
string line;
std::getline(cin, line);
istringstream iss(line);
int m, n;
vector<int>stations;
vector<int>communities;
iss >> m >> n;
grid = vector<vector<int>>(m, vector<int>(n, 0));
for(int i = 0; i < m; i++){
std::getline(cin, line);
istringstream iss(line);
for(int j = 0; j < n; j++){
iss >> grid[i][j];
if(grid[i][j]==0){
stations.push_back(i*n+j);
}
if(grid[i][j]==1){
communities.push_back(i*n+j);
}
}
}
int num_vertices=m*n;
vector<vector<int>> min_dist(num_vertices, vector<int>(num_vertices, INT_MAX));
for(int i = 0; i < num_vertices; i++){
for(int j = 0; j < num_vertices; j++){
int row_i=i/n, col_i=i%n, row_j=j/n, col_j=j%n;
if (i==j) {
min_dist[i][j]=0;
}else{
if(grid[row_i][col_i]!=-1&&grid[row_j][col_j]!=-1){
if((row_i==row_j&&abs(col_i-col_j)==1)||(abs(row_i-row_j)==1&&col_i==col_j)){
min_dist[i][j]=1;
min_dist[j][i]=1;
}
}
}
}
}
for(int k = 0; k < num_vertices; k++){
for(int i = 0; i < num_vertices; i++){
for(int j = 0; j < num_vertices; j++){
if(min_dist[i][k]==INT_MAX || min_dist[k][j]==INT_MAX){
continue;
}
int tmp = min_dist[i][k]+min_dist[k][j];
if(min_dist[i][j]>tmp){
min_dist[i][j]=tmp;
}
}
}
}
int res = 0;
for(const int &community:communities){
int length_path=INT_MAX;
for(const int &station:stations){
if(min_dist[community][station]<length_path){
length_path=min_dist[community][station];
}
}
if (length_path<INT_MAX){
res+=length_path;
}
}
cout << res;
return 0;
}
环检测算法
最小生成树
prim算法
小树长大
kruskal算法
合并森林