一、拓扑排序
// 将入度为0的点写入myQueue
vector<int> ans;
queue<int> myQueue;
vector<int> G[N]; // 边
int d[N]; //入度
...
...
int n, m; // 节点个人、边的个数
void init() {
for(int i = 1; i <= m; ++i) {
cin >> x >> y;
G[x].emplace_back(y);
++d[y];
}
}
bool topSort(){
//ans中存储的是拓扑排序的结果
for(int i = 1; i <= n; ++i) {
if(d[i] == 0) {
myQueue.push(i);
ans.push_back(i);
}
}
while(myQueue.size()){
auto t = myQueue.front();
myQueue.pop();
for(auto next : G[t]) {
if(--d[next] == 0) {
myQueue.push(next);
ans.push_back(next);
}
}
}
return ans.size() == n;
}
二、多源最短路
1. floyd O(n3)
// 节点 1 ~ n
void floyd(){
for(int k = 1; k <= n; ++k)
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++ j)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
三、单源最短路
1. Dijkstra朴素版 O(n2) —— 权值为正
贪心的思想,每次都从集合中选取距离 1 即源节点距离最短的点,通过这个点来更新所有点到源点距离,同时会有vis来判重。
void dijkstra(){
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for(int i = 0; i < n; ++i){
int t = -1;
for(int j = 1; j <= n; ++j)
if(!vis[j] && (t == -1 || dis[t] > dis[j]))
t = j;
vis[t] = true;
for(int j = 1; j <= n; ++j)
dis[j] = min(dis[j], dis[t] + g[t][j]);
}
}
2. Dijkstra堆优化版 O(mlogn) —— 权值为正
基于上面朴素版,利用优先队列来替代遍历过程
// typedef pair<int, int> PII;
void dijkstra(){
memset(dis, 0x3f, sizeof dis);
priority_queue<PII, vector<PII>, greater<PII>> heap;
dis[1] = 0;
heap.push({0, 1});
while(heap.size()){
auto t = heap.top();
heap.pop();
int curId = t.second, curDis = t.first;
if(vis[curId]) continue;
vis[curId] = true;
for(auto next : G[curId]){
int nextId = next.second, distance = next.x;
if(dis[nextId] > dis[curId] + distance){
dis[nextId] = dis[curId] + distance;
heap.push({dis[nextId, nextId});
}
}
}
}
3. Bellman-ford O(nm)—— 权值可为负
struct Edge {
int from, to, w;
bool operator < (const Edge &t) const {
return w < t.w;
}
}edges[N];
void bellmanFord() {
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for(int i = 0; i < k; ++i) {//可通过这个k控制选择的边的条数
memcpy(backup, dis, sizeof dis);
//需要用备份数组,因为在更新时如果不用备份则会用到当前i这一轮的更新结果
//而实际上应该用i-1轮的dis结果去更新
for(int i = 1; i <= m; ++i) {// m为边数
int from = edges[i].from, to = edges[i].to, w = edges[i].w;
dis[to] = min(dis[to], backup[from] + w);
}
}
}
4. SPFA O(m) 最坏O(nm)—— 权值可为负
只要没有负环就可以用,也可以用来判断有没有负环
一、思想:更新过谁,就拿谁更新别人
1. 对bellman-ford算法的优化:用队列、BFS将变小了的dis[from]压入队列中,因为只有dis[from]变小了,dis[to]才有机会更新。所以将变小了的点压入其中。
(d[to] = min(d[to], backup[from] + w);
2. 需要借助用st[]数组判断该点是否在队列中,避免重复加入
3. 一般情况下,dijkstra也可以用 SPFA 如果不行的话就用回dijkstra
4. 当需要用于判断是否存在负环时,加一个cnt数组:在更新dis中一旦cnt[j] > n 则有负环
二、SPFA算法的优化:
1. SLF优化 如果dis[nextId]在更新后
< deque.front() 则从push_front队头加入
> deque.front() 则从push_back队尾加入
[SLF优化题目](https://www.acwing.com/file_system/file/content/whole/index/content/4428753/)
void spfa(){
memset(dis, 0x3f, sizeof dis);
dis[1] = 0, st[1] = true;
queue<int> myQueue;
/*如果需要判断负环的话,还需要将所有节点都写入myQueue
如果不写入所有节点,则只能判断从1开始的负环
for(int i = 1; i <= n; ++i){
myQueue.push(i), st[i] = true;
}
*/
myQueue.push(1);
while(myQueue.size()){
auto t = myQueue.front();
myQueue.pop();
st[t] = false;
for(auto next : G[t]) {
int nextId = next.y, distance = next.x;
if(dis[nextId] > dis[t] + distance) {
dis[nextId] = dis[t] + distance;
/*如果需要判断负环
cnt[nextId] = cnt[t] + 1;
if(cnt[nextId] > n) return false;
*/
if(!st[nextId]) {
st[nextId] = true;
myQueue.push({nextId, dis[nextId]});
}
}
}
}
}