【google笔试题】一个环形公路,给出相邻两点的距离(一个数组),求任意两点的最短距离,要求空间复杂度不超过O(N)。
如果从有向图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿此路径上各边上的权值总和达到最小。
最短路径问题是图论研究中的一个经典算法问题,旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。算法具体的形式包括:
解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法等。
图的存储结构主要分两种,一种是邻接矩阵,一种是邻接表。
图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。
邻接表是数组与链表相结合的存储方法。
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。如下图所示。
邻接表的处理方法是这样的:
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。举例来说,如果图中的顶点表示城市,而边上的权重表示著城市间开车行经的距离,该算法可以用来找到两个城市之间的最短路径。
每次找到离源点(如1号结点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
代码来自于书《Data Structure & Algorithm in JAVA》
// path.java
// demonstrates shortest path with weighted, directed graphs
// to run this program: C>java PathApp
////////////////////////////////////////////////////////////////
class DistPar // distance and parent
{ // items stored in sPath array
public int distance; // distance from start to this vertex
public int parentVert; // current parent of this vertex
// -------------------------------------------------------------
public DistPar(int pv, int d) // constructor
{
distance = d;
parentVert = pv;
}
// -------------------------------------------------------------
} // end class DistPar
///////////////////////////////////////////////////////////////
class Vertex {
public char label; // label (e.g. 'A')
public boolean isInTree;
// -------------------------------------------------------------
public Vertex(char lab) // constructor
{
label = lab;
isInTree = false;
}
// -------------------------------------------------------------
} // end class Vertex
////////////////////////////////////////////////////////////////
class Graph {
private final int MAX_VERTS = 20;
private final int INFINITY = 1000000;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private int nTree; // number of verts in tree
private DistPar sPath[]; // array for shortest-path data
private int currentVert; // current vertex
private int startToCurrent; // distance to currentVert
// -------------------------------------------------------------
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
nTree = 0;
for (int j = 0; j < MAX_VERTS; j++) // set adjacency
for (int k = 0; k < MAX_VERTS; k++) // matrix
adjMat[j][k] = INFINITY; // to infinity
sPath = new DistPar[MAX_VERTS]; // shortest paths
} // end constructor
// -------------------------------------------------------------
public void addVertex(char lab) {
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end, int weight) {
adjMat[start][end] = weight; // (directed)
}
// -------------------------------------------------------------
// find all shortest paths
public void path() {
//step1 initial
int startTree = 0; // start at vertex 0
vertexList[startTree].isInTree = true; //isInTree records whether the vertex's visited
nTree = 1; //record how many vertices has been visited
// transfer row of distances from adjMat to sPath
for (int j = 0; j < nVerts; j++) {
int tempDist = adjMat[startTree][j];
sPath[j] = new DistPar(startTree, tempDist); //sPath is the note, here represent as an array
}
while (nTree < nVerts) { //base case: until all vertices are in the tree
//step2 get minimum from sPath
int indexMin = getMin();
int minDist = sPath[indexMin].distance;
//special case: if all infinite or in tree,sPath is complete
if (minDist == INFINITY) {
System.out.println("There are unreachable vertices");
break;
} else { // reset currentVert
currentVert = indexMin; // to closest vert
// minimum distance from startTree is to currentVert, and is startToCurrent
startToCurrent = sPath[indexMin].distance;
}
// put current vertex in tree
vertexList[currentVert].isInTree = true;
nTree++;
//step3 update path
updatePath(); // update sPath[] array
} // end while(nTree
Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)。
从任意节点i到任意节点j的最短路径不外乎2种可能,一是直接从i到j,二是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
//Floyd算法(多源最短路径算法)
bool Floyd(){
for(int k = 1 ; k < this->Nv+1 ; k++){ //k代表中间顶点
for(int i = 1 ; i < this->Nv+1 ; i++){//i代表起始顶点
for(int j = 1 ; j < this->Nv+1 ; j++){//j代表终点
if(this->dist[i][k] + this->dist[k][j] < this->dist[i][j]){
this->dist[i][j] = this->dist[i][k] + this->dist[k][j];
if(i == j && this->dist[i][j] < 0){//发现了负值圈
return false;
}
this->path[i][j] = k;
}
}
}
}
return true;
}
是解决任意两点间的最短路径的一种算法,时间复杂度为O(N^3),空间复杂度为O(N^2)。可以正确处理有向图或负权的最短路径问题。
设 dist(i,j) 为从节点i到节点j的最短距离若最短路径经过点k,则dist(i,j)=dist(i,k) + dist(k,j),将该路径与先前的dist(i,j)比较获取最小值,即dist(i,j)=min( dist(i,k) + dist(k,j) ,dist(i,j) )。
Floyd-Warshall算法的描述如下:
//根据图的邻接矩阵,或邻接链表,初始化dist(i,j)
//其中dist(i,j)表示由点i到点j的代价,当dist(i,j)为 inf 表示两点之间没有任何连接。
For i←1 to n do
For j←1 to n do
dist(i,j) = weight(i,j)
//计算最短路径
for k ← 1 to n do
for i ← 1 to n do
for j ← 1 to n do
if (dist(i,k) + dist(k,j) < dist(i,j)) then // 是否是更短的路径?
dist(i,j) = dist(i,k) + dist(k,j)
求单源最短路,可以判断有无负权回路(若有,则不存在最短路),时效性较好,时间复杂度O(VE)。
step1:初始化dist(i),除了初始点的值为0,其余都为infinit(表示无穷大,不可到达),pred表示经过的前一个顶点
step2:执行n-1(n等于图中点的个数)次松弛计算:dist(j)=min( dist(i)+weight(i,j),dist(j) )
step3:再重复操作一次,如国dist(j) > distdist(i)+weight(i,j)表示途中存在从源点可达的权为负的回路。
因为,如果存在从源点可达的权为负的回路,则应为无法收敛而导致不能求出最短路径。
因为负权环可以无限制的降低总花费,所以如果发现第n次操作仍可降低花销,就一定存在负权环。
int[] dist=new int[n];
int[] pre=new int[n];
public void Bellman_Ford(){
//初始化
for(int i=1;i
SPFA是Bellman-Ford的队列优化,时效性相对好,时间复杂度O(kE)。(k<
扩展:路由算法
我的微信公众号:架构真经(id:gentoo666),分享Java干货,高并发编程,热门技术教程,微服务及分布式技术,架构设计,区块链技术,人工智能,大数据,Java面试题,以及前沿热门资讯等。每日更新哦!
参考资料: