图的基本应用

最小生成树

连通图的生成树是包含图中全部顶点的极小连通子图。
注意区分极大连通子图和极小连通子图:极大连通子图是无向图的连通分量,它包含无向图的所有的边;极小连通子图是连通无向图的生成树,既要保持图连通,又要使得边数最小

最短路径

求解最短路径的算法通常都依赖于一种性质,也就是两点之间的最短路径也包含了路径上其他顶点的最短路径。这种最优子结构性质是动态规划和贪心算法是否适用的一个标记。
  带权有向图G的最短路径问题,一般可分为两类:一是单源最短路径,即求图中某一顶点到其他各顶点的最短路径,可通过经典的Dijkstra算法求解,此算法也是基于贪心算法的策略;二是求每一对顶点间的最短路径,可通过Floyd-Warshall算法来求解,此算法是基于动态规划的思想。

1.Dijkstra算法求单元最短路径问题

  求带权有向图中某个源点到其余各顶点的最短路径,最常用的是Dijkstra算法。该算法设置一个集合S,记录已求得的最短路径的顶点,初始时把源点v0放入S中。此外,在构造过程中还设置了两个辅助数组:
dist[]:记录了从源点v0到其他各顶点当前的最短路径长度,dist[i]初值为arcs[0][i].
path[]:path[i]表示从源点到顶点i之间的最短路径的前驱节点,在算法结束时,可根据其值追溯得到源点v0到顶点vi的最短路径。
假设从顶点0出发,即v0=0,集合S最初只包含顶点0,邻接矩阵arcs表示带权有向图,arcs[i][j]表示有向边< i,j>的权值,若不存在有向边< i,j>,则arcs[i][j]为∞。Dijkstra算法步骤如下:
1)初始化:集合S初始化为{0},dist[]的初始值dist[i]=arcs[0][i], i=1, 2, …, n-1。
2)从顶点集合V-S中选出vj,满足dist[j]=Min{dist[i] | vi∈V-S},vj就是当前求得的一条从v0出发的最短路径的终点,令S=S∪{j}。
3)修改从v0出发到集合V-S上任一顶点vk可达的最短路径长度;如果dist[j]+arcs[j][k] < dist[k],则令dist[k]=dist[j]+arcs[j][k].
4)重复2)~3)操作共n-1次,直到所有的定点都包含在S中。
例如,对下图应用Dijkstra算法求其最短路径的步骤
这里写图片描述
显然,Dijkstra算法是基于贪心策略的。若使用邻接矩阵表示,它的时间复杂度为O(|V²|)。若使用带权的邻接表表示,虽然修改dist[]的时间可以减少,但由于在 dist[]中选择最小分量的时间不变,其时间复杂度仍未O(|V²|)。
这里写图片描述
  人们可能只希望找到从原点到某一特定顶点的最短路径,但是,这个问题和求解源点到其他所有顶点的最短路径一样复杂,其实际复杂度也为O(|V|²)。
值得注意的是,如果边上带有负权值,Dijkstra算法并不适用。

vector<int> path(n);
vector<bool> vis(n,false);//判断节点是否被访问过
vector<int> dist(n, INT_MAX);//存放最短路径的值
void djstra(int dst){
    for(int i =0 ; i < n; i++){
        if(i != dst)
            dist[i] = matrix[src][i];//matrix存放两点间的距离
    }
    dist[dst] = 0;
    path.push_back(dst);
    vis[dst] = true;
    for(int i = 1; i < n; i++){
        int minset = INT_MAX;
        int tmp = dst;
        for(int j = 0; j < n; j++){
            if(dist[j] < minset && (!vis[j])){
                tmp = j;
                minset = dist[tmp];
            }
        }
        vis[tmp] = true;
        for(int j = 0; j < n; j++){
            if( (!vis[j]) && matrix[tmp][j] < INT_MAX){
                if((dist[tmp] + matrix[tmp][j] < dist[j])){
                    dist[j] = dist[tmp] + matrix[tmp][j];
                    path.push_back(tmp);//存放得出最短路径的前一个节点,用于路径还原
                }
            }
        }
    }
}

2.Floyd算法求各顶点之间最短路径问题

  求所有顶点之间的最短路径问题描述如下:已知一个各边权值均大于0的带权有向图,对每一对顶点vi ≠vj,要求求出vi与vj之间的最短路径长度。
  Floyd算法的基本思想:递推产生一个n阶方阵序列A^(-1), A^(0),…,A^(n-1),其中A^(k)[i][j]表示从顶点vi到顶点vj的路径长度,k表示绕行第k个顶点的运算步骤。初始时,对于任意两个顶点vi和vj,若他们之间存在边,则以次边上的权值作为它们之间的最短路径长度;若它们之间不存在有向边,则以∞作为他们之间的最短路径长度。以后逐步尝试在原路径中加入顶点k(k = 0,1,…,n-1)作为中间顶点。如果增加中间顶点后,将得到的路径比原来的路径长度减少了,则以新路径代替原路径。算法描述如下:
定义一个n阶方阵序列:A^(-1), A(0),…,A^(n-1),其中:
A^(-1)[i][j] = arcs[i][j]
A^(k)[i][j] = Min{A^(k-1)[i][j], A^(k-1)[i][k] + A^(k-1)[k][j]}, k =0,1,…,n-1
  其中,A^(0)[i][j]是从顶点vi到vj,中间顶点是v0的最短路径的长度,A^(k)[i][j]是从顶点vi到vj,中间顶点的序号不大于k的最短路径的长度。Floyd算法是一个迭代的过程,每迭代一次,在从vi到vj的最短路径上就多考虑了一个顶点:经过n次迭代后所得到的A^(n-1)[i][j]就是vi到vj的最短路径长度,即方针A^(n-1)中保存了任意一对顶点之间的最短路径长度。
Floyd算法执行过程
这里写图片描述
  Floyd算法的时间复杂度为(|V|³)。不过由于其代码很紧凑,而且不包含其他负债的数据结构,因此隐含的常数系数是很小的,即使对于中等规模的输入来说,它仍然是相当有效的。
  Floyd算法允许图中有带负权值的边,但不允许有包含带负权值的边组成的回路。Floyd算法同样也适用于带权无向图,因为带权无向图可以视为有往返二重边的有向图,只要在顶点vi和vj之间存在无向边(vi,vj),就可以视为在这两个顶点之间存在权值相同的两条有向边< vi,vj>和< vj, vi>.
  也可以用单源最短路径算法来解决每对顶点之间最短路径问题。每一次运行时,轮流将一个顶点作为源点,并且若所有边权值均为非负时,可以采用Dijkstra算法。
  

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值