今天学习了《算法笔记》最短路径 最小生成树 拓扑排序部分 这里做简单总结
最短路径使用较多的是dijstra
算法 基于贪心的思想 在无负边权的情况下,可以有效地求出单源最短路径,它维护d[]
表示结点i
到起点的距离 每次选择距离起点最近地结点访问,也就是d[i]
最小的结点i
进行访问,之后,使用结点i
对未访问过的结点进行松弛,可以使用优先队列进行优化。在许多题目中,不会只考最短距离,一般会有第二,第三标尺,比如边权之和,点权之和,最短路径的条数等,以上这三个标尺可以在松弛过程中一并更新优化,关键是建立num[]
,它的含义是由起点到结点i
的这条最短路径的边权和,点权和,最短路径条数,注意初值起点的值即可。这样,当结点被松弛,即d[to] > d[from] + cost
,参数进行覆盖更新,当d[to] == d[from] + cost
时,根据参数大小关系或相关逻辑进行更新。
但有时标尺多了之后,在函数内判断就导致不满足最优子结构,这时应该使用逻辑更为简单的算法dijstra + DFS
这个的思路就是使用dijstra
算法求出所有的由终点到固定单源起点最短路,思想就是定义vector pre[]
它的定义是从起点到结点i
的最短路径中结点i
的前驱,它的更新也是在松弛过程中,当松弛成功,就清空原pre,压入松弛结点,当距离相同,则直接压入松弛结点。那我们就能想到只要从终点一直沿着前驱走,最终一定能够走到起点,由此便产生了一条完整的从终点到固定起点的最短路径。我们可以使用DFS
对路径进行搜索,同时维护全局最优变量,当搜索到起点时,就判断条件,更新res
,同时将最优变量更新为先路径的参数,注意,一套参数只表示一条路径,当第一标尺更新时,第二标尺一定要同时更新。
模板代码
dijstra
最短路径
void dijstra(gg st){
priority_queue<arr2,vector<arr2>,greater<arr2>>q;
fill(d.begin(),d.end(),INT_MAX);
q.push({0,st});
d[st] = 0;
while(not q.empty()){
auto n = q.top();
q.pop();
if(n[0] != d[n[1]]) continue; // 防止旧部松弛
for(auto& i : adj[n[1]]){
if(d[n[1]] + i.len < d[i.u]){
d[i.u] = d[n[1]] + i.len;
num[i.u] = num[n[1]];
w[i.u] = w[n[1]] + weight[i.u];
q.push({d[i.u],i.u});
}else if(d[n[1]] + i.len == d[i.u]){ // 这里这个被松弛的结点无需入队 因为距离相同 结点相同
if(w[n[1]] + weight[i.u] > w[i.u]) {
w[i.u] = w[n[1]] + weight[i.u];
}
num[i.u] += num[n[1]];
}
}
}
}
spfa
最短路径
void spfa(gg st){
queue<gg>q;
q.push(st);
fill(d.begin(),d.end(),INT_MAX);
d[st] = 0;
inq[st] = true;
while(not q.empty()){
gg p = q.front();
q.pop();
inq[p] = false;
for(auto& e : adj[p]){
if(d[e.u] > d[p] + e.len){
d[e.u] = d[p] + e.len;
num[e.u] = num[p];
w[e.u] = w[p] + weight[e.u];
if(not inq[e.u]){
q.push(e.u);
inq[e.u] = true;
}
pre[e.u].clear();
pre[e.u].insert(p);
}else if(d[e.u] == d[p] + e.len){
if(w[e.u] < w[p] + weight[e.u]){
w[e.u] = w[p] + weight[e.u];
}
num[e.u] = 0;
pre[e.u].insert(p);
for(auto& i : pre[e.u]) num[e.u] += num[i]; // 这里需要重新计算 记前驱
if(not inq[e.u]){ // 这里和dij不同 这里只是结点 没有距离 如果这个被松弛的点不在队中 也一定要入队
q.push(e.u);
inq[e.u] = true;
}
}
}
}
}
回溯求最短路径代码
void DFS(string s, gg happiness, gg num){
if(s == st){
if(happiness > maxhappy or (happiness == maxhappy and happiness * 1.0 / num > maxavg)){
res = path;
maxhappy = happiness;
maxavg = happiness * 1.0 / num;
}
return;
}
happiness += happy[s];
num++;
for(auto i : pre[s]) {
path.push_back(i);
DFS(i,happiness,num);
path.pop_back();
}
}
Kruskal
算法 求最小生成树
struct Edge{
gg u, v,cost;
};
vector<Edge>edges;
vector<gg>ufs(maxn);
gg findFather(gg x){
return x == ufs[x] ? x : ufs[x] = findFather(ufs[x]);
}
void unionSets(gg x, gg y){
ufs[findFather(x)] = findFather(y);
}
void init(){
iota(ufs.begin(),ufs.end(),0);
}
gg Kruskal(){
gg res = 0;
sort(edges.begin(),edges.end(),[](const Edge& a, const Edge& b){
return a.cost < b.cost;
});
for(auto& e : edges){
if(findFather(e.u) != findFather(e.v)){
res += e.cost;
unionSets(e.u,e.v);
}
}
return res;
}
拓扑排序
bool topologicalSort(){
vector<gg>res;
queue<gg>q;
for(gg i=0;i<n;i++){ // 压入入度为0的结点
if(degree[i] == 0) q.push(i);
}
while(not q.empty()){
gg u = q.front();
res.push_back(u); // 生成拓扑序列
q.pop();
for(auto i : adj[u]){
degree[i]--;
if(degree[i] == 0) q.push(i);
}
}
if(res.szie() == n) return true; // n为结点个数
return false;
}
DFS版拓扑排序
bool DFS(gg u){
c[u] = -1; // 访问标准 表示正在拓扑 还未拓扑成功的结点 初始值为0
vector<gg>res;
for(auto i : adj[u]){
if(c[i] < 0) return false; // 表示有环 拓扑失败
else if(c[i] == 0 and not dfs(i)) return false; // 后续结点拓扑失败
}
c[u] = 1; // 表示拓扑成功
res.push_back(u);
return true;
}
bool topsort(){
memset(c,0,sizeof(c));
for(gg u =0;u<n;u++){
if(not c[u]){
if(not DFS(u)) return false; // 图如果是连通图,从任意节点拓扑一次,若失败,即有环,若成功,即无环
}
}
return true; // 拓扑成功
}