路径规划(最短路径)算法C#实现
这个算法是错误的,转发一段时间,也照着写了代码(C++版本的)。后来和同事讨论时发现这个在逻辑上有漏洞。
我后来和会算法的哥们学习了算法,写了一个更加简单易懂的,最重要是正确的算法。
老说有时间写blog,这都又半年了……今天刚刚看到,这篇竟然有1k以上的阅读,真是害人不浅。
赶紧来声明一下。还是第一次遇到转发这么多的文章是错误的。
再次声明,我认为这篇算法是错误的。我以前转发的,现在发现是错误的,逻辑的深度和广度他只算了一个方面,逻辑有漏洞。
要是哪位认为没有漏洞,赶快来告诉我,我对这篇算法一直感觉很晕,总之,他的实现方法是很不简单明白的,很繁琐。
以前空闲的时候用C#实现的路径规划算法,今日贴它出来,看大家有没有更好的实现方案。关于路径规划(最短路径)算法的背景知识,大家可以参考《C++算法--图算法》一书。
该图算法描述的是这样的场景:图由节点和带有方向的边构成,每条边都有相应的权值,路径规划(最短路径)算法就是要找出从节点A到节点B的累积权值最小的路径。
首先,我们可以将“有向边”抽象为Edge类:
{
public string StartNodeID ;
public string EndNodeID ;
public double Weight ; // 权值,代价
}
节点则抽象成Node类,一个节点上挂着以此节点作为起点的“出边”表。
{
private string iD ;
private ArrayList edgeList ;//Edge的集合--出边表
public Node(string id )
{
this.iD = id ;
this.edgeList = new ArrayList() ;
}
property
}
在计算的过程中,我们需要记录到达每一个节点权值最小的路径,这个抽象可以用PassedPath类来表示:
/// PassedPath 用于缓存计算过程中的到达某个节点的权值最小的路径
/// </summary>
public class PassedPath
{
private string curNodeID ;
private bool beProcessed ; // 是否已被处理
private double weight ; // 累积的权值
private ArrayList passedIDList ; // 路径
public PassedPath( string ID)
{
this .curNodeID = ID ;
this .weight = double .MaxValue ;
this .passedIDList = new ArrayList() ;
this .beProcessed = false ;
}
#region property
public bool BeProcessed
{
get
{
return this .beProcessed ;
}
set
{
this .beProcessed = value ;
}
}
public string CurNodeID
{
get
{
return this .curNodeID ;
}
}
public double Weight
{
get
{
return this .weight ;
}
set
{
this .weight = value ;
}
}
public ArrayList PassedIDList
{
get
{
return this .passedIDList ;
}
}
#endregion
}
另外,还需要一个表PlanCourse来记录规划的中间结果,即它管理了每一个节点的PassedPath。
/// <summary>
/// PlanCourse 缓存从源节点到其它任一节点的最小权值路径=》路径表
/// </summary>
public class PlanCourse
{
private Hashtable htPassedPath ;
#region ctor
public PlanCourse(ArrayList nodeList , string originID)
{
this .htPassedPath = new Hashtable() ;
Node originNode = null ;
foreach (Node node in nodeList)
{
if (node.ID == originID)
{
originNode = node ;
}
else
{
PassedPath pPath = new PassedPath(node.ID) ;
this .htPassedPath.Add(node.ID ,pPath) ;
}
}
if (originNode == null )
{
throw new Exception( " The origin node is not exist ! " ) ;
}
this .InitializeWeight(originNode) ;
}
private void InitializeWeight(Node originNode)
{
if ((originNode.EdgeList == null ) || (originNode.EdgeList.Count == 0 ))
{
return ;
}
foreach (Edge edge in originNode.EdgeList)
{
PassedPath pPath = this [edge.EndNodeID] ;
if (pPath == null )
{
continue ;
}
pPath.PassedIDList.Add(originNode.ID) ;
pPath.Weight = edge.Weight ;
}
}
#endregion
public PassedPath this [ string nodeID]
{
get
{
return (PassedPath) this .htPassedPath[nodeID] ;
}
}
}
在所有的基础构建好后,路径规划算法就很容易实施了,该算法主要步骤如下:
(1)用一张表(PlanCourse)记录源点到任何其它一节点的最小权值,初始化这张表时,如果源点能直通某节点,则权值设为对应的边的权,否则设为double.MaxValue。
(2)选取没有被处理并且当前累积权值最小的节点TargetNode,用其边的可达性来更新到达其它节点的路径和权值(如果其它节点 经此节点后权值变小则更新,否则不更新),然后标记TargetNode为已处理。
(3)重复(2),直至所有的可达节点都被处理一遍。
(4)从PlanCourse表中获取目的点的PassedPath,即为结果。
下面就来看上述步骤的实现,该实现被封装在RoutePlanner类中:
/// RoutePlanner 提供图算法中常用的路径规划功能。
/// 2005.09.06
/// </summary>
public class RoutePlanner
{
public RoutePlanner()
{
}
#region Paln
// 获取权值最小的路径
public RoutePlanResult Paln(ArrayList nodeList , string originID , string destID)
{
PlanCourse planCourse = new PlanCourse(nodeList ,originID) ;
Node curNode = this .GetMinWeightRudeNode(planCourse ,nodeList ,originID) ;
#region 计算过程
while (curNode != null )
{
PassedPath curPath = planCourse[curNode.ID] ;
foreach (Edge edge in curNode.EdgeList)
{
PassedPath targetPath = planCourse[edge.EndNodeID] ;
double tempWeight = curPath.Weight + edge.Weight ;
if (tempWeight < targetPath.Weight)
{
targetPath.Weight = tempWeight ;
targetPath.PassedIDList.Clear() ;
for ( int i = 0 ;i < curPath.PassedIDList.Count ;i ++ )
{
targetPath.PassedIDList.Add(curPath.PassedIDList[i].ToString()) ;
}
targetPath.PassedIDList.Add(curNode.ID) ;
}
}
// 标志为已处理
planCourse[curNode.ID].BeProcessed = true ;
// 获取下一个未处理节点
curNode = this .GetMinWeightRudeNode(planCourse ,nodeList ,originID) ;
}
#endregion
// 表示规划结束
return this .GetResult(planCourse ,destID) ;
}
#endregion
#region private method
#region GetResult
// 从PlanCourse表中取出目标节点的PassedPath,这个PassedPath即是规划结果
private RoutePlanResult GetResult(PlanCourse planCourse , string destID)
{
PassedPath pPath = planCourse[destID] ;
if (pPath.Weight == int .MaxValue)
{
RoutePlanResult result1 = new RoutePlanResult( null , int .MaxValue) ;
return result1 ;
}
string [] passedNodeIDs = new string [pPath.PassedIDList.Count] ;
for ( int i = 0 ;i < passedNodeIDs.Length ;i ++ )
{
passedNodeIDs[i] = pPath.PassedIDList[i].ToString() ;
}
RoutePlanResult result = new RoutePlanResult(passedNodeIDs ,pPath.Weight) ;
return result ;
}
#endregion
#region GetMinWeightRudeNode
// 从PlanCourse取出一个当前累积权值最小,并且没有被处理过的节点
private Node GetMinWeightRudeNode(PlanCourse planCourse ,ArrayList nodeList , string originID)
{
double weight = double .MaxValue ;
Node destNode = null ;
foreach (Node node in nodeList)
{
if (node.ID == originID)
{
continue ;
}
PassedPath pPath = planCourse[node.ID] ;
if (pPath.BeProcessed)
{
continue ;
}
if (pPath.Weight < weight)
{
weight = pPath.Weight ;
destNode = node ;
}
}
return destNode ;
}
#endregion
#endregion
}
2006.05.22 应众多朋友要求,下面给出一个简单示例:
RoutePlanner.Plan 过程详解:
(1)用一张表(PlanCourse)记录源点到任何其它一节点的最小权值,初始化这张表时,如果源点能直通某节点,则权值设为对应的
边的权,否则设为double.MaxValue
(2)选取没有被处理并且当前累积权值最小的节点TargetNode,用其边的可达性来更新到达其它节点的路径和权值(如果其它节点
经此节点后权值变小则更新,否则不更新),然后标记TargetNode为已处理
(3)重复(2),直至所有的可达节点都被处理一遍。
(4)从PlanCourse表中获取目的点的PassedPath,即为结果。
static void Main( string [] args)
{
ArrayList nodeList = new ArrayList() ;
// ***************** B Node *******************
Node aNode = new Node( " A " ) ;
nodeList.Add(aNode) ;
// A -> B
Edge aEdge1 = new Edge() ;
aEdge1.StartNodeID = aNode.ID ;
aEdge1.EndNodeID = " B " ;
aEdge1.Weight = 10 ;
aNode.EdgeList.Add(aEdge1) ;
// A -> C
Edge aEdge2 = new Edge() ;
aEdge2.StartNodeID = aNode.ID ;
aEdge2.EndNodeID = " C " ;
aEdge2.Weight = 20 ;
aNode.EdgeList.Add(aEdge2) ;
// A -> E
Edge aEdge3 = new Edge() ;
aEdge3.StartNodeID = aNode.ID ;
aEdge3.EndNodeID = " E " ;
aEdge3.Weight = 30 ;
aNode.EdgeList.Add(aEdge3) ;
// ***************** B Node *******************
Node bNode = new Node( " B " ) ;
nodeList.Add(bNode) ;
// B -> C
Edge bEdge1 = new Edge() ;
bEdge1.StartNodeID = bNode.ID ;
bEdge1.EndNodeID = " C " ;
bEdge1.Weight = 5 ;
bNode.EdgeList.Add(bEdge1) ;
// B -> E
Edge bEdge2 = new Edge() ;
bEdge2.StartNodeID = bNode.ID ;
bEdge2.EndNodeID = " E " ;
bEdge2.Weight = 10 ;
bNode.EdgeList.Add(bEdge2) ;
// ***************** C Node *******************
Node cNode = new Node( " C " ) ;
nodeList.Add(cNode) ;
// C -> D
Edge cEdge1 = new Edge() ;
cEdge1.StartNodeID = cNode.ID ;
cEdge1.EndNodeID = " D " ;
cEdge1.Weight = 30 ;
cNode.EdgeList.Add(cEdge1) ;
// ***************** D Node *******************
Node dNode = new Node( " D " ) ;
nodeList.Add(dNode) ;
// ***************** C Node *******************
Node eNode = new Node( " E " ) ;
nodeList.Add(eNode) ;
// C -> D
Edge eEdge1 = new Edge() ;
eEdge1.StartNodeID = eNode.ID ;
eEdge1.EndNodeID = " D " ;
eEdge1.Weight = 20 ;
eNode.EdgeList.Add(eEdge1) ;
RoutePlanner planner = new RoutePlanner() ;
RoutePlanResult result = planner.Paln(nodeList , " A " , " D " ) ;
planner = null ;
}
参考文章:
迪杰斯特拉算法——百度百科
平:这个里面的GIF动画很形象生动好理解。
http://baike.baidu.com/link?url=6OZpXnxKlemD7jm0xegf_mEn_xoYW8ThEI6PBlFyOxHDQCWVJp-Y9lJ97MVUi6p-rzrKH4mAM3aj4Ss87GNW1a
路径规划(最短路径)算法C#实现
平:细致的讲解了C#代码的实现。每个类的功能注解很清楚。
但是总感觉各个类的功能不明确。应该可以细分。有时间我写一个。不过很难,前一阵去洗分了,感觉细分好混乱啊。
http://zhuweisky.cnblogs.com/archive/2005/09/29/246677.html