最小生成树和单源最短路径
n个顶点的最小生成树只有n-1条边。
最小生成树能够保证整个拓扑图的所有路径之和最小,但不能保证任意两点之间是最短路径。
最短路径是从一点出发,到达目的地的路径最小。
最小生成树构成后所有的点都被连通,而最短路只要到达目的地走的是最短的路径即可,与所有的点连不连通没有关系。
1 最小生成树
最小生成树:构造连通网的生成树,且代价最小。
Prim算法:
- 找到任意一个点,以这个点为起点开始生成树
- 不断加入与树相邻且权值最小的边和相应的顶点
- 重复第2步直到全部顶点录入完毕,成为最小生成树
第2步的关键是在未收录的顶点中找到与树相邻并离树最近的顶点.
加入后,在剩下的顶点中继续寻找权值最小的边
1.1 书本代码:
起点从0开始
时间复杂度为O(n2)
const int MAX = 10e5+1;
const int NIL = -1;
const int WHITE = 0;//表示未访问
const int GRAY = 1;//表示访问中
const int BLACK = 2;//表示已访问
int m[101][101];
int n;
int d[MAX];//记录顶点内边中,权值最小的边的权值
int p[MAX];//记录MST中顶点v的父节点
int color[MAX];//记录访问状况
int prim()
{
int u,minv;
//初始化
for(int i=0;i<n;i++)
{
d[i] = MAX;
p[i] = -1;
color[i] = WHITE;
}
d[0] = 0;
while(1)
{
//取最小权值的边
minv = MAX;
u = -1;
for(int i=0;i<n;i++)
{
if(d[i]<minv&&color[i]!=BLACK)
{
u = i;
minv = d[i];
}
}
if(u == -1)
break;
color[u] = BLACK;
//维护
for(int v=0;v<n;v++)
{
if(color[v]!=BLACK&&m[u][v]!=MAX)//未访问且通
{
if(m[u][v]<d[v])
{
d[v] = m[u][v];
p[v] = u;
color[v] = GRAY;
}
}
}
}
//返回最小生成树各边权值总和
int sum = 0;
for(int i=0;i<n;i++)
if(p[i]!=-1)
sum+=m[i][p[i]];
return sum;
}
1.2 精简代码
时间复杂度为O(n2)
void prim()
{
memset(dis,127/3,sizeof(dis));//初始化
dis[0]=0;
for(int i=0;i<n;++i)
{
int k=-1;
for(int j=0;j<n;j++)//找出权值最小的点
if(!v[j]&&(k==-1||dis[j]<dis[k]))
k=j;
v[k]=1;//加入集合
for(int j=0;j<n;j++)//松弛
if(!v[j]&&a[k][j]<dis[j])
dis[j]=a[k][j];
}
for(int i=0;i<n;i++)
sum+=dis[i];
cout<<sum<<endl;
}
1.3 优化代码
利用优先队列管理权值,配合邻接表,降低时间复杂度
时间复杂度:O(elogn)
代码:
const int MAX = 10e5+1;
int v[MAX];//记录访问状况
vector<pair<int,int> >adj[MAX];
priority_queue<pair<int,int> >q;
//pair的first为权值,second为通往的点
void prim()
{
memset(dis,127/3,sizeof(dis));//初始化
dis[0]=0;
q.push(make_pair(0,0));
while(!q.empty())
{
pair<int,int>f = q.top();q.pop();
int u = f.second;
v[u]=1;//加入集合
if(dis[u]<f.first*(-1))
continue;
for(int j=0;j<adj[u].size();j++)//松弛
{
int v0 = adj[u][j].first;
if(v[v0] == 1)
continue;
if(adj[u][j].second<dis[v0])
{
dis[v0] = adj[u][j].second;
//优先队列默认最大值,所以要乘以-1
q.push(make_pair(dis[v0]*(-1),v0));
}
}
}
int sum = 0;
for(int i=0;i<n;i++)
sum+=dis[i];
cout<<sum<<endl;
}
2 单源最短路径
单源最短路径即以某顶点为起点,求该顶点到各顶点的最短路径上个边权值的总和。
dijkstra算法:
此算法与prim算法思想非常相似,同时不能处理负权值的图。
- 初始化两个集合(S,U),S为只有初试顶点A的集合,U为其他顶点
- 如果U不为空,取出最小顶点D
- 将D加入S
- 更新通过顶点D到达U所有点的距离(如果小于)
- 重复
2.1 书本代码
起点从0开始
时间复杂度:O(n2)
const int MAX = 10e5+1;
const int NIL = -1;
const int WHITE = 0;
const int GRAY = 1;
const int BLACK = 2;
int m[101][101];
int n;
int d[MAX];//记录起点到v的最短路径权值
int p[MAX];//记录最短路径树中顶点v的父节点
int color[MAX];//记录访问状况
int dijkstra()
{
int u,minv;
//初始化
for(int i=0;i<n;i++)
{
d[i] = MAX;
p[i] = -1;
color[i] = WHITE;
}
d[0] = 0;
while(1)
{
//找最小权值
minv = MAX;
u = -1;
for(int i=0;i<n;i++)
{
if(minv>d[i]&&color[i]!=BLACK)
{
u = i;
minv = d[i];
}
}
if(u == -1)
break;
color[u] = BLACK;
//维护
for(int v=0;v<n;v++)
{
if(color[v]!=BLACK&&m[u][v]!=MAX)
{
if(m[u][v]+d[u]<d[v])
{
d[v] = m[u][v]+d[u];
p[v] = u;
color[v] = GRAY;
}
}
}
}
for(int i=0;i<n;i++)
cout<<i<<" "<<(d[i]==MAX?-1:d[i])<<endl;
}
2.2 精简代码
时间复杂度:O(n2)
void dijkstra()
{
memset(dis,127/3,sizeof(dis));//初始化
dis[0]=0;
for(int i=0;i<n;++i)
{
int k=-1;
for(int j=0;j<n;j++)//找出距离最近的点
if(!v[j]&&(k==-1||dis[j]<dis[k]))
k=j;
v[k]=1;//加入集合
for(int j=0;j<n;j++)//松弛
if(!v[j]&&dis[k]+a[k][j]<dis[j])
dis[j]=dis[k]+a[k][j];
}
for(int i=0;i<n;i++)
cout<<i<<" "<<dis[i]<<endl;
}
2.3 优化代码
利用优先队列管理权值,配合邻接表,降低时间复杂度
时间复杂度:O((e+n)logn)
代码:
const int MAX = 10e5+1;
int v[MAX];//记录访问状况
vector<pair<int,int> >adj[MAX];
priority_queue<pair<int,int> >q;
//pair的first为权值,second为通往的点
void dijkstra()
{
memset(dis,127/3,sizeof(dis));//初始化
dis[0]=0;
q.push(make_pair(0,0));
while(!q.empty())
{
pair<int,int>f = q.top();q.pop();
int u = f.second;
v[u]=1;//加入集合
if(dis[u]<f.first*(-1))
continue;
for(int j=0;j<adj[u].size();j++)//松弛
{
int v0 = adj[u][j].first;
if(v[v0] == 1)
continue;
if(dis[u] + adj[u][j].second<dis[v0])
{
dis[v0] = dis[u] + adj[u][j].second;
//优先队列默认最大值,所以要乘以-1
q.push(make_pair(dis[v0]*(-1),v0));
}
}
}
for(int i=0;i<n;i++)
cout<<i<<" "<<(dis[i]==MAX?-1:dis[i])<<endl;
}
3 总结
最小生成树常用算法
- P算法
- K算法
最短路径常用算法
- D算法:特定两点之间的最短路径
- F算法:可以算出所有顶点两两之间的最短路径
- 深度优先和广度优先:找出两点之间的所有路径,比较得出结果
遇到求所有路径之和最小的问题用最小生成树&并查集解决;
遇到求两点间最短路径问题的用**最短路,**即从一个城市到另一个城市最短的路径问题。