Floyd最短路径算法
废话多说
Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。 ---------来源百度百科
背景
从前有个孩子,名字叫弗洛伊德,我们就叫他"小德"吧。小德在城市内经常开车不知道走,于是经常绕远路,每当加油时肉疼不已,经过细心观察,发现在卫星地图中,各个点的分布是固定的,那么从两点之间肯定是有最短的路径可以走的,为了三两银子的油钱,于是小德踏上了自己漫长的路径计算这条不归路,终于功夫不负有心人,小德最后发现了一套适用的最短路径规划算法。
实现
数学中我们都知道,两点之间,直线最短,但是在错综复杂的城市中,往往开车,直线是行驶不了的,但是,鉴于矢量性(可以把它理解成单行道);我们先制定以下规则:
在A->B和A->C->B的行程距离中,可以计算出A->B的距离是有一个定值的,A->C->B也是一个定值,假定A->B的距离是10,而A->C->B的距离是15,那么我们就认为A->B的最短路径就是A->B,而不是A->C->B;并且B->A是没有路线,因为A->B是单行道,则认为B->A为无穷大(∞);如果点C->D走不通,那么可以认为C->D之间的距离是无穷大(∞);如果起始点和终点是同一个站点,我们认为距离是0。
下面我们使用一张图来介绍如何去实现这个过程,以之前的博客中AGV地图编辑的图片中,站点1、站点2、站点3、站点4、站点5、站点6、站点8、站点9、站点14这九个点为例,如下图红色框内所示:
第一步:初始化map地图矩阵
假定1->2的距离是1+2=3,那么2->1就是∞,同理1->3就是4,3->1就是∞…接着套用我们上面讲的矢量性,我们就可以得到一个地图矩阵(也就是邻接矩阵,如果起点和终点不相邻,则权值为∞,如果起点等于终点,则权值为0):
终点 1 2 3 4 5 6 8 9 14
起点
1 0 3 4 ∞ ∞ ∞ ∞ ∞ ∞
2 ∞ 0 ∞ ∞ ∞ ∞ ∞ ∞ 16
3 ∞ ∞ 0 7 8 ∞ ∞ ∞ ∞
4 ∞ ∞ ∞ 0 ∞ ∞ 12 ∞ ∞
5 ∞ ∞ ∞ ∞ 0 11 ∞ ∞ ∞
6 ∞ ∞ ∞ ∞ ∞ 0 ∞ 15 ∞
8 ∞ ∞ ∞ ∞ ∞ ∞ 0 17 ∞
9 ∞ ∞ ∞ ∞ ∞ ∞ ∞ 0 ∞
14 ∞ ∞ ∞ ∞ ∞ ∞ 22 ∞ 0
第二步:N阶推算
以上面的邻接矩阵为参考,我们可以将起点定义成代码里面的一维索引,终点定义成代码里面的二维索引,也就是a[0][0]=0,a[0][1]=3…以此类推;以站点1为中介点,假定a[i][j]>a[i][k]+a[k][j],则设置成a[i][j]=a[i][k]+a[j][k];说明,如果存在从i点到j点中的其他定点k,并且i到k的距离加上k到j的距离要比i到j的距离小,然后i->k->j就是i->j的更短距离,以此经过N次递归推算,我们可以得到一系列顶点k1、k2…kn,最后得到i->k1->k2->…->kn->j就是i->j的最短路径,核心代码:
for (int k = 0; k < MAXV; k++)
{
for (int i = 0; i < MAXV; i++)
{
for (int j = 0; j < MAXV; j++)
{
//从i到j的路径比从i经过k到j的路径长
if (D[i][j] > D[i][k] + D[k][j])
{
//更改路径长度
D[i][j] = D[i][k] + D[k][j];
}
}
}
}
其中MAXV就是地图中站点的个数。
第三步:代码实现
我们先创建两个结构体和一个方法帮助类:
1、结构体1-----顶点类型
#region 顶点类型
/// <summary>
/// 顶点类型
/// </summary>
public struct VertexType
{
/// <summary>
/// 顶点编号
/// </summary>
public int no;
/// <summary>
/// 顶点X
/// </summary>
public float pointX;
/// <summary>
/// 顶点Y
/// </summary>
public float pointY;
};
#endregion
2、结构体2-----邻接矩阵
#region 邻接矩阵
/// <summary>
/// 图的邻接矩阵类型
/// </summary>
public struct MGraph
{
/// <summary>
/// 邻接矩阵
/// </summary>
public int[][] edges;
public VertexType[] vexs; //存放顶点信息
};
#endregion
3、方法帮助类:public class FloydHelper
3.1、先定义一些数据变量:
考虑到我们AGV地图编辑软件生成的后台文件是xml文件,xml文件内容如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model>
<property name="Demo-01" udpateDate="2021-04-16 03:23:51" />
<backGround name="mapPanelDocker" color="black" size="1788,943" />
<point name="Point-039" xPosition="384" yPosition="189" type="站点" />
<path name="Point-024---Point-025" sourcePoint="Point-024" destinationPoint="Point-025" controlPoint1="0,0" controlPoint2="0,0" length="83" type="直线路径" locked="false" />
</model>
我们可以以此定义两个类:
类1-----AGV站点信息类:
/// <summary>
/// AGV地图上面的站点
/// </summary>
public class AGV_Point
{
/// <summary>
/// 站点名用于显示
/// </summary>
public string _Name { get; set; }
/// <summary>
/// 控件上的坐标
/// </summary>
public string xPosition { get; set; }
public string yPosition { get; set; }
/// <summary>
/// 站点类型
/// </summary>
public string _type { get; set; }
}
类2-----AGV路径信息类
public class AGV_Line
{
/// <summary>
/// 路径名称
/// </summary>
public string LineName { get; set; }
/// <summary>
/// 开始控件
/// </summary>
public string StartControl { get; set; }
/// <summary>
/// 结束控件
/// </summary>
public string EndControl { get; set; }
/// <summary>
/// 控制点1
/// </summary>
public Point controlPoint1 { get; set; }
/// <summary>
/// 控制点2
/// </summary>
public Point controlPoint2 { get; set; }
/// <summary>
/// 长度
/// </summary>
public string Length { get; set; }
/// <summary>
/// 类型
/// </summary>
public string _type { get; set; }
/// <summary>
/// 是否被禁用
/// </summary>
public bool isLock { get; set; }
}
3.2、内部使用的数据
/// <summary>
/// 指无穷大
/// </summary>
private int INF;
/// <summary>
/// 顶点最大个数
/// </summary>
private int MAXV;
/// <summary>
/// 站台编号对应的索引
/// </summary>
Dictionary<int, int> StationIndex = new Dictionary<int, int>();
/// <summary>
/// 索引对应的站台编号
/// </summary>
Dictionary<int, int> IndexStation = new Dictionary<int, int>();
/// <summary>
/// 路径结果
/// </summary>
private List<string> luJingResult = new List<string>();
/// <summary>
/// 邻接矩阵
/// </summary>
private MGraph g;
private AGV_Point[] agvPoints;
private AGV_Line[] agvLines;
3.3、初始化定义的变量数据
public FloydHelper()
{
INF = 32767;
agvPoints = 从配置文件加载所有的站点数据
agvLines = 从配置文件加载所有的路径数据
MAXV = agvPoints == null ? 0 : agvPoints.Length;
g.vexs = new VertexType[MAXV];
for (int i = 0; i < g.vexs.Length; i++)
{
g.vexs[i] = new VertexType();
}
initdata();
}
#region 初始化邻接矩阵
/// <summary>
/// 初始化邻接矩阵数据
/// </summary>
private void initdata()
{
g.edges = new int[MAXV][];
for (int i = 0; i < g.edges.Length; i++)
{
g.edges[i] = new int[MAXV];
}
g.vexs = new VertexType[MAXV];
int[][] a = GetDataFromMap();
#region 建立图的邻接矩阵
for (int i = 0; i < MAXV; i++)
{
for (int j = 0; j < MAXV; j++)
{
g.edges[i][j] = a[i][j];
}
}
#endregion
}
#endregion
#region 从配置中加载初始化邻接矩阵
/// <summary>
/// 从配置中加载初始化邻接矩阵
/// </summary>
/// <param name="Msg"></param>
/// <returns></returns>
private int[][] GetDataFromMap()
{
int[][] a = new int[MAXV][];
if (agvPoints != null && agvPoints.Length != 0)
{
Dictionary<string, float> dicTemp = new Dictionary<string, float>();
Dictionary<string, string> dicTempIndex = new Dictionary<string, string>();
#region 站点数据
for (int i = 0; i < agvPoints.Length; i++)
{
int index = i;
VertexType ver = new VertexType()
{
no = int.Parse(agvPoints[i]._Name.Substring(7).ToString()),
pointX = float.Parse(agvPoints[i].xPosition.ToString()),
pointY = float.Parse(agvPoints[i].yPosition.ToString())
};
g.vexs[i] = ver;
if (!StationIndex.Keys.Contains(ver.no))
{
StationIndex.Add(ver.no, index);
}
if (!IndexStation.Keys.Contains(index))
{
IndexStation.Add(index, ver.no);
}
string strTemp1 = (int.Parse(agvPoints[i]._Name.Replace("Point-", "").ToString())).ToString();
for (int j = 0; j < agvPoints.Length; j++)
{
string strTemp2 = (int.Parse(agvPoints[j]._Name.Replace("Point-", "").ToString())).ToString();
string keyStr = strTemp1 + "," + strTemp2;
string keyStr2 = i + "," + j;
float valueFloat = i == j ? 0 : INF;
if ((!dicTemp.Keys.Contains(keyStr)) && (!dicTempIndex.Keys.Contains(keyStr)))
{
dicTemp.Add(keyStr, valueFloat);
dicTempIndex.Add(keyStr2, keyStr);
}
}
}
#endregion
#region 路径数据
if (agvLines != null && agvLines.Length != 0)
{
for (int i = 0; i < agvLines.Length; i++)
{
string strTemp = int.Parse(agvLines[i].StartControl.Replace("Point-", "")).ToString() + ","
+ int.Parse(agvLines[i].EndControl.Replace("Point-", "")).ToString();
float valueFloat = float.Parse(agvLines[i].Length.ToString());
if (dicTemp.Keys.Contains(strTemp))
{
if (agvLines[i].isLock)//如果路径被锁,则认为不通
{
dicTemp[strTemp] = INF;
}
else
{
dicTemp[strTemp] = valueFloat;
}
}
}
}
#endregion
#region 初始化数据
for (int i = 0; i < a.Length; i++)
{
a[i] = new int[MAXV];
for (int j = 0; j < a[i].Length; j++)
{
string strTemp = i + "," + j;
if (i == j)
{
a[i][j] = 0;//等于自身设置为通路 距离0
}
else
{
string valueKeyStr = "";
if (dicTempIndex.Keys.Contains(strTemp))
{
valueKeyStr = dicTempIndex[strTemp];
}
if (valueKeyStr != "")
{
if (dicTemp.Keys.Contains(valueKeyStr))
{
a[i][j] = (int)dicTemp[valueKeyStr];
}
}
else
{
a[i][j] = INF;//全部初始化为无穷大 不通
}
}
}
}
#endregion
}
return a;
}
#endregion
3.4、写计算路径的方法:
#region 佛洛依德算法计算路径
/// <summary>
/// 佛洛依德算法计算
/// </summary>
/// <param name="g"></param>
private List<string> Floyd(int startPoint, int endPoint, out bool isHaveResult)
{
#region 建立存放当前顶点之间的最短路径长度,分量D[i,j]表示当前顶点vi到顶点vj的最短路径长度
float[][] D = new float[MAXV][];
for (int i = 0; i < D.Length; i++)
{
D[i] = new float[MAXV];
}
#endregion
#region 建立从顶点vi到顶点vj的路径上所经过的顶点编号不大于k的最短路径长度
int[][] path = new int[MAXV][];
for (int i = 0; i < path.Length; i++)
{
path[i] = new int[MAXV];
}
#endregion
#region 初始化各个顶点之间的路径和距离
for (int i = 0; i < MAXV; i++)
{
//对各个节点初始已经知道的路径和距离
for (int j = 0; j < MAXV; j++)
{
D[i][j] = g.edges[i][j];
path[i][j] = -1;//-1表示不通
}
}
#endregion
#region 如果i->j的距离大于i->k->j,则D[i,j]=D[i,k]+d[k,j],path[i,j]=k
for (int k = 0; k < MAXV; k++)
{
for (int i = 0; i < MAXV; i++)
{
for (int j = 0; j < MAXV; j++)
{
//从i到j的路径比从i经过k到j的路径长
if (D[i][j] > D[i][k] + D[k][j])
{
//更改路径长度
D[i][j] = D[i][k] + D[k][j];
//更改路径信息经过k
path[i][j] = k;
}
}
}
}
#endregion
return Dispath(D, path, startPoint, endPoint, out isHaveResult);
}
#endregion
#region 输出路径
/// <summary>
/// 输出路径
/// </summary>
/// <param name="D">距离矩阵</param>
/// <param name="path"></param>
/// <param name="n"></param>
private List<string> Dispath(float[][] D, int[][] path, int startPoint, int endPointIndex, out bool isHaveResult)
{
isHaveResult = false;
luJingResult = new List<string>();
if (StationIndex.Keys.Contains(startPoint) && StationIndex.Keys.Contains(endPointIndex))
{
int i = StationIndex[startPoint];
int j = StationIndex[endPointIndex];
if (D[i][j] == INF)
{
if (i != j)
{
string strTemp = string.Format("从{0}到{1}没有路径\n", startPoint, endPointIndex);
luJingResult = null;
return luJingResult;
}
}
else
{
string strTemp = string.Format("从{0}到{1}=>路径长度:{2} 路径:", startPoint, endPointIndex, D[i][j]);
isHaveResult = true;
luJingResult = new List<string>();
strTemp = string.Format("{0},", startPoint); //输出路径上的起点
if (!luJingResult.Contains(strTemp))
{
luJingResult.Add(strTemp);
}
Ppath(ref luJingResult, path, i, j); //输出路径上的中间点
strTemp = string.Format("{0}", endPointIndex); //输出路径上的终点
if (!luJingResult.Contains(strTemp))
{
luJingResult.Add(strTemp);
}
}
}
return luJingResult;
}
#endregion
#region 前向递归查找路径上的顶点
/// <summary>
/// 前向递归查找路径上的顶点
/// </summary>
/// <param name="path"></param>
/// <param name="i"></param>
/// <param name="j"></param>
private void Ppath(ref List<string> rsult, int[][] path, int i, int j)
{
int k = path[i][j];
if (k == -1)//找到了起点则返回
{
return;
}
Ppath(ref rsult, path, i, k); //找顶点i的前一个顶点k
if (IndexStation.Keys.Contains(k))
{
string strTemp = string.Format("{0},", IndexStation[k]); //输出路径上的终点
if (!rsult.Contains(strTemp))
{
rsult.Add(strTemp);
}
}
Ppath(ref rsult, path, k, j); //找顶点k的前一个顶点j
}
#endregion
总结
小德这种方法理解起来还是很容易的,可以计算出任意两点之间的最短距离,代码实现起来也很简单。但是它也是有他自己的缺点的,从代码中我们可以看出,时间复杂度还是比较高的,一旦站点的数据量非常庞大的时候,不考虑使用这种方法。
最后,代码需要筒子们自己进行整合,还是那句老话,程序员要学会偷懒0.0!