对于非网图,它没有边上的权值,最短路径就是指两顶点之间经过的边数最少的路径。
对于网图,最短路径指两顶点之间经过的边上权值之和最少的路径,称路径上第一个顶点是源点,最后一个顶点是终点。
两种求最短路径的算法:
1.迪杰斯特拉(Dijkstra)算法
2.弗洛伊德(Floyd)算法
迪杰斯特拉(Dijkstra)算法:
//测试数据
int[] vertexs = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
int[,] edges = new int[,]
{
//0 1 2 3 4 5 6 7 8
{ 0, 1, 5, MAX, MAX, MAX, MAX, MAX, MAX, }, //0
{ 1, 0, 3, 7, 5, MAX, MAX, MAX, MAX, }, //1
{ 5, 3, 0, MAX, 1, 7, MAX, MAX, MAX, }, //2
{ MAX, 7, MAX, 0, 2, MAX, 3, MAX, MAX, }, //3
{ MAX, 5, 1, 2, 0, 3, 6, 9, MAX, }, //4
{ MAX, MAX, 7, MAX, 3, 0, MAX, 5, MAX, }, //5
{ MAX, MAX, MAX, 3, 6, MAX, 0, 2, 7, }, //6
{ MAX, MAX, MAX, MAX, 9, 5, 2, 0, 4, }, //7
{ MAX, MAX, MAX, MAX, MAX, MAX, 7, 4, 0, }, //8
};
/// <summary>
/// 迪杰斯特拉算法:最短路径
/// 求有向网graph的V0顶点到其余顶点v最短路径pathArr[v]及带权长度shortPath[v]
/// </summary>
/// <param name="v0">第一个顶点的下标</param>
/// <param name="pathArr">存储最短路径下标的数组</param>
/// <param name="shortPath">存储到各点最短路径的权值和</param>
public void ShortestPath_Dijkstra(int v0, int[] pathArr, int[] shortPath)
{
int v, w, k = 0, min;
int[] final = new int[maxNumVertex]; //final[w]=1表示求得顶点V0至Vw的最短路径
for (v = 0; v < vertexs.Length; v++) //初始化数据
{
final[v] = 0; //全部顶点初始化为未知最短路径状态
shortPath[v] = edges[v0, v]; //存储V0到每个顶点的最短路径权值之和
pathArr[v] = 0; //初始化路径数组为0,存储顶点v的前驱结点
}
pathArr[v0] = 0; //v0到v0路径为0
final[v0] = 1; //v0至v0不需要求路径
//开始主循环,每次求得v0到某个v顶点的最短路径
for (v = 1; v < vertexs.Length; v++)
{
min = int.MaxValue;
for (w = 0; w < vertexs.Length; w++)
{
if (final[w] == 0 && shortPath[w] < min)
{
k = w;
min = shortPath[w]; //w顶点离v0顶点更近
}
}
final[k] = 1; //将目前找到的最近的顶点置为1
for (w = 0; w < vertexs.Length; w++)
{
//从V0开始,如果经过k顶点到达w顶点的路径比现在这条路径的长度短的话,更新值。
//之前最近的点不参与,未连通的点不参与,自身不参与,新的最短路径出现替换原来的值
if (final[w] == 0 && edges[k, w] != int.MaxValue && edges[k, w] != 0 && (min + edges[k, w] < shortPath[w]))
{
//说明找到了更短的路径,shortPath[w]和pathArr[w]
shortPath[w] = min + edges[k, w];
pathArr[w] = k; //存储最短路径到达w顶点的前驱顶点k,从V0开始经过k到w最短。
}
}
}
}
通过迪杰斯特拉算法解决了从某个源点到其余各顶点的最短路径问题,从循环嵌套可以很容易得到此算法的时间复杂度为O(n的2次方)。
想知道任一顶点到其余所有顶点的最短路径,就是对每个顶点执行一次迪杰斯特拉算法,等于在原有算法的基础上,再来一次循环,此时算法的时间复杂度为O(n的3次方)。
弗洛伊德(Floyd)算法:求所有顶点到所有顶点的最短路径,时间复杂度为O(n的3次方)
测试数据和上面的迪杰斯特拉算法一样
/// <summary>
/// 弗洛伊德算法:求网图中各顶点v到其余顶点w最短路径pathMatrix[v,w]及带权长度shortPath[v,w]
/// 传参方式
/// </summary>
/// <param name="pathMatrix">存储最短路径下标的数组</param>
/// <param name="shortPath">存储到各点最短路径的权值和</param>
public void ShortestPath_Floyd(int[,] pathMatrix, int[,] shortPath)
{
int v, w, k;
for (v = 0; v < vertexs.Length; v++)
{
for (w = 0; w < vertexs.Length; w++)
{
shortPath[v, w] = edges[v, w]; //shortPath[v,w]值即为对应点间的权值
pathMatrix[v, w] = w; //初始化pathMatrix
}
}
for (k = 0; k < vertexs.Length; k++) //k表示中转的顶点, v->k->w
{
for (v = 0; v < vertexs.Length; v++) //v源点
{
for (w = 0; w < vertexs.Length; w++) //w终点
{
//v和k不通 或 k和w不通,跳出,此路不通,这里极大值用的是int.MaxValue
if (shortPath[v, k] == int.MaxValue || shortPath[k, w] == int.MaxValue) continue;
//如果经过下标为k顶点路径比原两点路径更短,将当前两点权值设为更小的一个
if (shortPath[v, w] > shortPath[v, k] + shortPath[k, w])
{
shortPath[v, w] = shortPath[v, k] + shortPath[k, w];
pathMatrix[v, w] = pathMatrix[v, k];
}
}
}
}
//最短路径显示代码
for (v = 0; v < vertexs.Length; v++)
{
for (w = v + 1; w < vertexs.Length; w++)
{
Debug.Log("源点:" + v + " 终点:" + w + " weight:" + shortPath[v, w]);
k = pathMatrix[v, w]; //获得第一个路径顶点下标
string str = "path:" + v; //源点
while (k != w) //如果路径顶点下标不是终点
{
str += "->" + k; //路径顶点
k = pathMatrix[k, w]; //获得下一个路径顶点下标
}
str += "->" + w; //终点
Debug.Log(str);
}
}
}
具体详细步骤请看书!
邻接矩阵源码:遍历、最小生成树、最短路径
/// <summary>
/// 泛型邻接矩阵
/// </summary>
public class AdjacentMatrix<T>
{
#region 数据
//数据
private T[] vertexs; //顶点数组:数据
private int[,] edges; //边数组:权值
private int maxNumVertex; //最大顶点数
#endregion
#region 操作
/// <summary>
/// 创建无向网图的邻接矩阵表示
/// </summary>
/// <param name="vertexs">顶点数据</param>
/// <param name="edges">边数据:权值</param>
public AdjacentMatrix(T[] vertexs, int[,] edges)
{
maxNumVertex = vertexs.Length;
this.vertexs = new T[maxNumVertex];
this.edges = new int[maxNumVertex, maxNumVertex];
Array.Copy(vertexs, this.vertexs, vertexs.Length);
Array.Copy(edges, this.edges, edges.Length);
}
/// <summary>
/// 创建无向网图的邻接矩阵表示
/// </summary>
/// <param name="vertexs">顶点数据</param>
/// <param name="edges">边数据:权值</param>
/// <param name="maxNumVertex">最大顶点数组容量</param>
public AdjacentMatrix(T[] vertexs, int[,] edges, int maxNumVertex)
{
maxNumVertex = maxNumVertex < vertexs.Length ? vertexs.Length : maxNumVertex;
this.vertexs = new T[maxNumVertex];
this.edges = new int[maxNumVertex, maxNumVertex];
this.maxNumVertex = maxNumVertex;
Array.Copy(vertexs, this.vertexs, vertexs.Length);
Array.Copy(edges, this.edges, edges.Length);
}
private bool[] visited;
/// <summary>
/// 邻接矩阵的深度优先遍历
/// </summary>
/// <param name="gl"></param>
public void DFSTraverse()
{
visited = new bool[maxNumVertex];
for (int i = 0; i < vertexs.Length; i++)
{
visited[i] = false; //初始化所有顶点都是未访问的状态
}
for (int i = 0; i < vertexs.Length; i++)
{
//对未访问过的顶点调用DFS,若是连通图,只会执行一次
if (!visited[i])
{
DFS(i);
}
}
}
/// <summary>
/// 邻接矩阵的深度优先递归算法
/// </summary>
/// <param name="gl"></param>
/// <param name="i"></param>
private void DFS(int i)
{
visited[i] = true;
//这里对顶点的操作,这里简单的打印
Debug.Log(vertexs[i]);
for (int j = 0; j < vertexs.Length; j++)
{
//edges[i, j] == 1表示两顶点是连通的
//如果是权值,可以判断: edges[i,j]!=0 && edges[i,j]! = MaxValue
//MaxValue自己定,可以用int.MaxValue,long.MaxValue,float.MaxValue等
//具体权值用int long 还是float自己定
if (edges[i, j] == 1 && !visited[j])
{
DFS(j);
}
}
}
/// <summary>
/// 邻接矩阵:广度优先遍历
/// </summary>
/// <param name="graph"></param>
public void BFSTraverse()
{
Queue<int> queue = new Queue<int>(); //初始化辅助队列
visited = new bool[maxNumVertex];
for (int i = 0; i < vertexs.Length; i++)
{
visited[i] = false;
}
for (int i = 0; i < vertexs.Length; i++) //对每一个顶点做循环
{
if (!visited[i]) //若是未访问过就处理
{
visited[i] = true; //设置当前顶点访问过
//这里对顶点的操作,这里简单的打印
Debug.Log(vertexs[i]);
queue.Enqueue(i); //将此顶点入队列
while (queue.Count > 0) //当前队列有元素
{
i = queue.Dequeue(); //出队列
for (int j = 0; j < vertexs.Length; j++)
{
//edges[i, j] == 1表示两顶点是连通的
//如果是权值,可以判断: edges[i,j]!=0 && edges[i,j]! = MaxValue
//MaxValue自己定,可以用int.MaxValue,long.MaxValue,float.MaxValue等
//具体权值用int long 还是float自己定
if (edges[i, j] == 1 && !visited[j])
{
visited[j] = true;
//这里对顶点的操作,这里简单的打印
Debug.Log(vertexs[j]);
queue.Enqueue(j);
}
}
}
}
}
}
/// <summary>
/// 普里姆算法:最小生成树
/// </summary>
public void MiniSpanTree_Prim()
{
int minWeight, i, j, minWeightIndex;
int[] adjvex = new int[vertexs.Length]; //保存相关顶点下标
int[] lowWeight = new int[vertexs.Length]; //保存相关顶点间边的权值
lowWeight[0] = 0; //初始化第一个权值为0,即V0加入生成树,lowCost的值为0,在这里就是此下标的顶点已经加入生成树
adjvex[0] = 0; //初始化第一个顶点下标为0,从顶点V0开始
//读取第一行,初始化
for (i = 1; i < vertexs.Length; i++) //循环除下标为0外的全部顶点
{
lowWeight[i] = edges[0, i]; //将V0顶点与之有边的权值存入数组
adjvex[i] = 0; //初始化都为V0的下标
}
//构造最小生成树
for (i = 1; i < vertexs.Length; i++)
{
minWeight = int.MaxValue; //初始化最小权值为极大值,通常设置为不可能的大数字,如:32765、65535等
j = 1; //顶点下标循环变量
minWeightIndex = 0; //用来存储最小权值的顶点下标
//遍历每一行的数据
while (j < vertexs.Length)
{
if (lowWeight[j] != 0 && lowWeight[j] < minWeight)
{
minWeight = lowWeight[j]; //让当前权值成为最小值
minWeightIndex = j; //将当前最小值的下标存入k
}
j++;
}
Debug.Log("打印当前顶点边中权值最小边:" + adjvex[minWeightIndex] + "---" + minWeightIndex);
lowWeight[minWeightIndex] = 0; //将当前顶点的权值设置为0,表示此顶点已经完成任务
for (j = 1; j < vertexs.Length; j++) //循环所有顶点
{
if (lowWeight[j] != 0 && edges[minWeightIndex, j] < lowWeight[j])
{
//若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值
lowWeight[j] = edges[minWeightIndex, j]; //将较小权值存入lowCost
adjvex[j] = minWeightIndex; //将下标为k的顶点存入adjvex
}
}
}
}
/// <summary>
/// 迪杰斯特拉算法:最短路径,返回元组,不用传参
/// 求有向网graph的V0顶点到其余顶点v最短路径pathArr[v]及带权长度shortPath[v]
/// </summary>
/// <param name="v0">第一个顶点的下标</param>
public (int[], int[]) ShortestPath_Dijkstra(int v0)
{
int v, w, k = 0, min;
int[] pathArr = new int[vertexs.Length]; //存储最短路径下标的数组
int[] shortPath = new int[vertexs.Length]; //存储到各点最短路径的权值和
int[] final = new int[maxNumVertex]; //final[w]=1表示求得顶点V0至Vw的最短路径
for (v = 0; v < vertexs.Length; v++) //初始化数据
{
final[v] = 0; //全部顶点初始化为未知最短路径状态
shortPath[v] = edges[v0, v]; //存储V0到每个顶点的最短路径权值之和
pathArr[v] = 0; //初始化路径数组为0,存储顶点v的前驱结点
}
pathArr[v0] = 0; //v0到v0路径为0
final[v0] = 1; //v0至v0不需要求路径
//开始主循环,每次求得v0到某个v顶点的最短路径
for (v = 1; v < vertexs.Length; v++)
{
min = int.MaxValue;
for (w = 0; w < vertexs.Length; w++)
{
if (final[w] == 0 && shortPath[w] < min)
{
k = w;
min = shortPath[w]; //w顶点离v0顶点更近
}
}
final[k] = 1; //将目前找到的最近的顶点置为1
for (w = 0; w < vertexs.Length; w++)
{
//从V0开始,如果经过k顶点到达w顶点的路径比现在这条路径的长度短的话,更新值。
//之前最近的点不参与,未连通的点不参与,自身不参与,新的最短路径出现替换原来的值
if (final[w] == 0 && edges[k, w] != int.MaxValue && edges[k, w] != 0 && (min + edges[k, w] < shortPath[w]))
{
//说明找到了更短的路径,shortPath[w]和pathArr[w]
shortPath[w] = min + edges[k, w];
pathArr[w] = k; //存储最短路径到达w顶点的前驱顶点k,从V0开始经过k到w最短。
}
}
}
return (pathArr, shortPath); //返回结果,这里用的C#元组语法
}
/// <summary>
/// 迪杰斯特拉算法:最短路径,传统传参方式
/// 求有向网graph的V0顶点到其余顶点v最短路径pathArr[v]及带权长度shortPath[v]
/// </summary>
/// <param name="v0">第一个顶点的下标</param>
/// <param name="pathArr">存储最短路径下标的数组</param>
/// <param name="shortPath">存储到各点最短路径的权值和</param>
public void ShortestPath_Dijkstra(int v0, int[] pathArr, int[] shortPath)
{
int v, w, k = 0, min;
int[] final = new int[maxNumVertex]; //final[w]=1表示求得顶点V0至Vw的最短路径
for (v = 0; v < vertexs.Length; v++) //初始化数据
{
final[v] = 0; //全部顶点初始化为未知最短路径状态
shortPath[v] = edges[v0, v]; //存储V0到每个顶点的最短路径权值之和
pathArr[v] = 0; //初始化路径数组为0,存储顶点v的前驱结点
}
pathArr[v0] = 0; //v0到v0路径为0
final[v0] = 1; //v0至v0不需要求路径
//开始主循环,每次求得v0到某个v顶点的最短路径
for (v = 1; v < vertexs.Length; v++)
{
min = int.MaxValue;
for (w = 0; w < vertexs.Length; w++)
{
if (final[w] == 0 && shortPath[w] < min)
{
k = w;
min = shortPath[w]; //w顶点离v0顶点更近
}
}
final[k] = 1; //将目前找到的最近的顶点置为1
for (w = 0; w < vertexs.Length; w++)
{
//从V0开始,如果经过k顶点到达w顶点的路径比现在这条路径的长度短的话,更新值。
//之前最近的点不参与,未连通的点不参与,自身不参与,新的最短路径出现替换原来的值
if (final[w] == 0 && edges[k, w] != int.MaxValue && edges[k, w] != 0 && (min + edges[k, w] < shortPath[w]))
{
//说明找到了更短的路径,shortPath[w]和pathArr[w]
shortPath[w] = min + edges[k, w];
pathArr[w] = k; //存储最短路径到达w顶点的前驱顶点k,从V0开始经过k到w最短。
}
}
}
}
/// <summary>
/// 弗洛伊德算法:求网图中各顶点v到其余顶点w最短路径pathMatrix[v,w]及带权长度shortPath[v,w]
/// 传参方式
/// </summary>
/// <param name="pathMatrix">存储最短路径下标的数组</param>
/// <param name="shortPath">存储到各点最短路径的权值和</param>
public void ShortestPath_Floyd(int[,] pathMatrix, int[,] shortPath)
{
int v, w, k;
for (v = 0; v < vertexs.Length; v++)
{
for (w = 0; w < vertexs.Length; w++)
{
shortPath[v, w] = edges[v, w]; //shortPath[v,w]值即为对应点间的权值
pathMatrix[v, w] = w; //初始化pathMatrix
}
}
for (k = 0; k < vertexs.Length; k++)
{
for (v = 0; v < vertexs.Length; v++)
{
for (w = 0; w < vertexs.Length; w++)
{
//v和k不通 或 k和w不通,跳出,此路不通
if (shortPath[v, k] == int.MaxValue || shortPath[k, w] == int.MaxValue) continue;
//如果经过下标为k顶点路径比原两点路径更短,将当前两点权值设为更小的一个
if (shortPath[v, w] > shortPath[v, k] + shortPath[k, w])
{
shortPath[v, w] = shortPath[v, k] + shortPath[k, w];
pathMatrix[v, w] = pathMatrix[v, k];
}
}
}
}
//最短路径显示代码
for (v = 0; v < vertexs.Length; v++)
{
for (w = v + 1; w < vertexs.Length; w++)
{
Debug.Log("源点:" + v + " 终点:" + w + " weight:" + shortPath[v, w]);
k = pathMatrix[v, w]; //获得第一个路径顶点下标
string str = "path:" + v; //源点
while (k != w) //如果路径顶点下标不是终点
{
str += "->" + k; //路径顶点
k = pathMatrix[k, w]; //获得下一个路径顶点下标
}
str += "->" + w; //终点
Debug.Log(str);
}
}
}
/// <summary>
/// 弗洛伊德算法:求网图中各顶点v到其余顶点w最短路径pathMatrix[v,w]及带权长度shortPath[v,w]
/// 元组形式
/// </summary>
/// <param name="pathMatrix">存储最短路径下标的数组</param>
/// <param name="shortPath">存储到各点最短路径的权值和</param>
public (int[,], int[,]) ShortestPath_Floyd()
{
int[,] pathMatrix = new int[vertexs.Length, vertexs.Length];
int[,] shortPath = new int[vertexs.Length, vertexs.Length];
int v, w, k;
for (v = 0; v < vertexs.Length; v++)
{
for (w = 0; w < vertexs.Length; w++)
{
shortPath[v, w] = edges[v, w]; //shortPath[v,w]值即为对应点间的权值
pathMatrix[v, w] = w; //初始化pathMatrix
}
}
for (k = 0; k < vertexs.Length; k++)
{
for (v = 0; v < vertexs.Length; v++)
{
for (w = 0; w < vertexs.Length; w++)
{
//v和k不通 或 k和w不通,跳出,此路不通
if (shortPath[v, k] == int.MaxValue || shortPath[k, w] == int.MaxValue) continue;
//如果经过下标为k顶点路径比原两点路径更短,将当前两点权值设为更小的一个
if (shortPath[v, w] > shortPath[v, k] + shortPath[k, w])
{
shortPath[v, w] = shortPath[v, k] + shortPath[k, w];
pathMatrix[v, w] = pathMatrix[v, k];
}
}
}
}
//最短路径显示代码
for (v = 0; v < vertexs.Length; v++)
{
for (w = v + 1; w < vertexs.Length; w++)
{
Debug.Log("源点:" + v + " 终点:" + w + " weight:" + shortPath[v, w]);
k = pathMatrix[v, w]; //获得第一个路径顶点下标
string str = "path:" + v; //源点
while (k != w) //如果路径顶点下标不是终点
{
str += "->" + k; //路径顶点
k = pathMatrix[k, w]; //获得下一个路径顶点下标
}
str += "->" + w; //终点
Debug.Log(str);
}
}
return (pathMatrix, shortPath);
}
#endregion
}