最近在问答上看到了一个求取最短路径的问题,曾经也了解过,但从来没有实现出来。正好有空就好好研究下呗,能力有限参考了各位大佬的文章,我就记录记录我的思考和实现过程吧。
Dijkstra算法
算法的思路,有很多篇文章都讲过了,我就不再累赘。我用简短的话语重述一下步骤吧:
路径图需要满足一定的条件及进行预处理。有两个集合:S和U,S中为已求得最短距离节点,U中为待求得的最短距离节点。
1.两两相邻节点间的距离为正,如0—>1: Δ3,不相邻节点间距离为∞或其他标志符,如0—>4: ∞。
2.从第一个起始节点开始,遍历到其它节点的距离且排除S集合中的节点(0—>1,0—>2,0—>3,,,),得到其中距离最短的节点。
3.将2步中求得的节点加入集合S中,并以此节点为新的起始节点。
4.重复2、3步,直到所有节点求取完毕。
1、具体思想
个人理解:
1.将各相邻节点距离规范化,尤其是不相邻节点距离标记为∞,这样能够很好的用于计算机处理。
2.为何在当前遍历的节点距离集中,选择最小的值及其对应的节点作为最终的短路径?
因为此节点必为最短路径值。例如以0节点为起始点:
路径 | 距离值 |
---|---|
0—>1 | 3 |
0—>2 | 2 |
0—>3 | ∞ |
0—>4 | ∞ |
0—>5 | ∞ |
可见0—>2路径值最小,但可能还有其他路径0—>1—>2或者0—>3—>4—>2等通过其他节点间接到达2节点。
但是,0—>3和0—>4等不相邻已排除,0—>1—>n—>2的距离等于”0—>1”+”1—>n”+”n—>2”,首先0—>1的距离值就已经大于0—>2的值,所以不存在其它最短路径到2节点,依次类推。
3.为何选取2步中得出的最短节点作为下一次的起点?
排除2步中求得节点2之后,当前路径值如下所示:
路径 | 距离值 |
---|---|
0—>1 | 3 |
0—>3 | ∞ |
0—>4 | ∞ |
0—>5 | ∞ |
但我们不确定0—>1是否是最短路径值,只有比它距离短的一条“0—>2”路径+另一条路径才可能出现更短的路径,即“2+?<3”。
所以选择上步得到的最短路径来排除剩余的路径是否为最短路径。
2、实施步骤
仅靠语言描述还不太清楚,具体的细节看如下步骤吧。
路径 | 距离值 |
---|---|
0—>1 | 3 |
0—>2 | 2 |
0—>3 | ∞ |
0—>4 | ∞ |
0—>5 | ∞ |
选出2节点,排除并以2节点为新的起始点遍历其余点。
路径 | 距离值 | 合并 |
---|---|---|
0—>1 | 3 | 2+0.5 |
0—>3 | ∞ | 2+5 |
0—>4 | ∞ | ∞ |
0—>5 | ∞ | ∞ |
0—>2—>1 | 2+0.5 | |
0—>2—>3 | 2+5 | |
0—>2—>4 | 2+∞ | |
0—>2—>5 | 2+∞ |
选出1节点,排除并以1节点为新的起始点遍历其余点。
路径 | 距离值 | 合并 |
---|---|---|
0—>3 | ∞ | 2+5 |
0—>4 | ∞ | 2.5+3.5 |
0—>5 | ∞ | ∞ |
0—>2—>3 | 2+5 | |
0—>2—>4 | 2+∞ | |
0—>2—>5 | 2+∞ | |
0—>2—>1—>3 | 2.5+4.5 | |
0—>2—>1—>4 | 2.5+3.5 | |
0—>2—>1—>5 | 2.5+∞ |
选出4节点,排除并以4节点为新的起始点遍历其余点。
路径 | 距离值 | 合并 |
---|---|---|
0—>3 | ∞ | 2+5 |
0—>5 | ∞ | 6+1 |
0—>2—>3 | 2+5 | |
0—>2—>5 | 2+∞ | |
0—>2—>1—>3 | 2.5+4.5 | |
0—>2—>1—>5 | 2.5+∞ | |
0—>2—>1—>4—>3 | 6+3 | |
0—>2—>1—>4—>5 | 6+1 |
0—>2—>1—>3和0—>2—>1—>4—>5距离值相同,任取其一,排除并重新遍历剩余点。
路径 | 距离值 | 合并 |
---|---|---|
0—>5 | ∞ | 6+1 |
0—>2—>5 | 2+∞ | |
0—>2—>1—>5 | 2.5+∞ | |
0—>2—>1—>4—>5 | 6+1 | |
0—>2—>1—>3—>5 | 7+∞ |
最后选出节点5,得到最短距离为7,循环结束。
3、总结规律
根据最短路径选取的步骤,总结化简,得出以下规律:
规律 |
---|
1.无论中间经过多少个节点,最终都是求初始顶点到其余点的距离,所以可以合并各路径值,如上列表中的合并栏,只取当前最小值替换。 |
2.合并后的序列组,然后只需比较合并后的数组得出的最小值,即为所有序列的路径的最小值。 |
3.每循环一次,都可得出一组节点的最短距离,所以最多循环n次可求出所有节点的最短距离。 |
已知条件和待求问题为:
已知条件 | 待求值 |
---|---|
两两节点间的距离 | 初始顶点到每个节点的最小值 |
每步选取的节点号 | |
排除的节点标记 |
总结规律,化简各种条件便于编写程序。
4、Java程序实现
import java.util.Scanner;;
public class ShortestPath
{
private double[][] distance; //两两节点间的距离矩阵
private double[] path; //起点到各节点间的最短距离值
private boolean[] flag; //已选出的节点标记
private String[]route; //最短路径规划方案
public static void main(String[] argument)
{
ShortestPath test=new ShortestPath();
test.init(6);
test.Calculate(1);
test.showDistance();
}
/**
* 初始化两两间的路径
* @param count 总节点数
* 不相邻的节点距离设为-1
*/
public void init(int count)
{
distance=new double[count][count];
path=new double[count];
flag=new boolean[count];
route=new String[count];
Scanner keyboard=new Scanner(System.in);
for(int i=0;i<count;i++)
{
for(int j=i+1;j<count;j++)
{
System.out.println("Please enter the distance "+i+"--->"+j+":");
distance[i][j]=keyboard.nextDouble();
distance[j][i]=distance[i][j];
}
distance[i][i]=0;
path[i]=Double.MAX_VALUE;
flag[i]=false;
}
}
/**
* 显示各节点间的距离、初始顶点到各节点的最短路径方案和距离值
*/
public void showDistance()
{
for(int i=0;i<distance.length;i++)
{
for(int j=0;j<distance.length;j++)
{
System.out.println(i+"--->"+j+": "+distance[i][j]);
}
}
System.out.println("The shortest path:");
for(int j=0;j<distance.length;j++)
{
System.out.println("--->"+j+": "+path[j]+" "+route[j]);
}
}
/**
* 运算出起点到每个节点的最短距离
* @param startNode 起始顶点
*/
public void Calculate(int startNode)
{
//第一次初始化写入Path时使distance+min<path成立
double min=0;
String shortRoute=String.valueOf(startNode);
int node=startNode;
//每循环一次找出一个节点,最多循环n次
for(int i=0;i<distance.length;i++)
{
//合并成01,02,03,,,的n列数组
for(int j=0;j<distance.length;j++)
{
//已经选出的最短节点和不相邻节点(-1)不参与
if(distance[startNode][j]>=0&&flag[j]==false)
{
//当有更短的路径方案时,替换路径值和方案
if(distance[startNode][j]+min<path[j])
{
path[j]=distance[startNode][j]+min;
route[j]=shortRoute+"--->"+j;
}
}
}
//找出数组中最短的值,提取出该节点序列号和距离值
min=Double.MAX_VALUE;
for(int j=0;j<distance.length;j++)
{
if(flag[j]==false)
{
if(path[j]<min)
{
min=path[j];
node=j;
}
}
}
flag[node]=true;
startNode=node;
shortRoute=route[node];
}
}
}
5、测试效果
为了便于判断,我设定两两不相邻节点间距离为负数,暂定为“-1”,并且0—>3的值等于3—>0的值。
修改定义的起始顶点,测试不同结果。
public static void main(String[] argument)
{
ShortestPath test=new ShortestPath();
test.init(6);
//将初始顶点设为3,计算3到其余节点的最短路径
test.Calculate(3);
test.showDistance();
}
6、总结
在编写程序中,当然还有些小细节和技巧。例如最后的最优路径规划图3—>2—>0的得出(总结规律,是在上次最优路径基础上加上—>当前节点,且”上次最优值+当前节点值<历史最优path”才进行替换操作)。
当然不可否认,当前程序还不够精炼,处理效率还是低了点,并且还有其它更好的寻径算法。偶尔研究下算法进行脑力训练也是不错的。