在学习图论时,求一个图的最短(长)路是一个比较基本的问题。
无权图即有权图,只不过权值都是为1,无权图也可以用有权图的操作。
无权图
当处理一个无权值的图时,可以采用DFS或BFS来对图进行搜索并及时更新某些标记数组,从而求得最短(长)路。
若采用BFS时,根据入队的顺序性(求最短路的层次性),可以将先访问到的节点打上标记并更新权值,从而实现最短路问题的求解。BFS时间复杂度:O(n+e)
queue<int>q;
for( i =1 ; i <= n ; i ++)
{
path[i] = -1; dist[i] = -1; //初始化
}
dist[v] = 0; //v是起点
q.push(v);
while (!q.empty())
{
u = q.front(); // 队头顶点u出队
q.pop();
for(int j = 0;j<(a[u][j].size());j++)
{
k = a[u][j];
if (dist[k] == -1)
{
q.push(k) // u未访问的邻接顶点入队
dist[k] = dist[u] + 1;
path[k] = u;
}
}
}
而当求解最长路时(DAG),选择用DFS较为合适
在这里我们可以用一个vector<int>temp 作为临时数组存放节点,path作为最终数组,当其.size()大小关系发生改变时,path清空并赋值为temp即可。DFS时间复杂度:O(n*e)
void Dfs(int n,vector<int>&temp)
{
if(temp.size()>path.size()) //更新
{
path.clear();
path=temp;
}
for(int i=0;i<a[n].size();i++)
{
temp.push_back(a[n][i]); //试探
Dfs(a[n][i],temp);
temp.pop_back(); //回溯
}
}
有权图
若要求最短路,对于权值全部为正数的图,可以采用Dijkstra算法,每次选未被标记且路径值最小的点,并对该点的邻接点进行松弛。在每次选择路径值最小点时,可以考虑选择堆优化。
void dijkstra(int v)
{
for (int i = 1;i<=n;i++) //初始化
{
path[i] = 0;dist[i] = INF;s[i] = 0;
}
int k;
priority_queue<pair<long long int,int>>q; //优先队列堆优化
dist[v] = 0;
q.push(make_pair(0,v)); //存二元数对,方便存取
while (!q.empty())
{
int u = q.top().second; //找到最小路径点
q.pop();
if (s[u]) continue;
s[u] = 1;
for (int i = 1;i<=int(a[u].size());i++)
{
k = a[u][i-1].to;
if (dist[u] + a[u][i-1].cost<dist[k]) //松弛
{
dist[k] = dist[u] + a[u][i-1].cost;
q.push(make_pair(-dist[k],k)); //存负数,因为要取最小值
path[k] = u;
}
}
}
}
优化之后时间复杂度为O(E*logN),较为良好。
Dijkstra也能求最长路,但是所求的最长路只能是全是正数。只需要将权值全部改成负数即可。
不过这样就不能使用堆优化了。
_____________________________________________________________________________
对于权值有正有负的图来说,SPFA算法较为合适。SPFA是bellman-ford算法的改进版本
即构建一个队列,若队列中的点能够松弛,先让此点出列,再让此点将所松弛的但是不在队列中的点进入队列,直到队列为空。该算法时间复杂度为O(kE),k一般认为是2左右。
另外该算法也可以判断图中是否存在负环。可以记录一个点的入队次数,若入队次数 > V,这个图就存在负环。
for (int i = 1;i<=n;i++) //初始化
{
d[i] = INF;mark[i] = 0;times[i] = 0;
}
d[v] = 0;mark[v] = 1;
q.push(v);
while (!q.empty())
{
u = q.front(); //取队首元素
q.pop();
mark[u] = 0;
for (int j = 0;j<int(a[u].size());j++)
{
k = a[u][j].to;
if (d[u] + a[u][j].cost < d[k]) //可松弛且不在队列中的元素入队
{
d[k] = d[u] + a[u][j].cost;
if (!mark[k])
{
q.push(k);
mark[k] = 1;
times[k]++; //判断负环的标志
if (times[k] == n)
return;
}
}
}
若要求最长路,只需要在建图的时候将权值赋值为其相反数,最后在取反即可!
=========================================================================总结:
对于无权图,求最短路可用BFS;求最长路用DFS;
对于有权图,最长路、最短路用SPFA即可!