Dijkstra演算法——《演算法導論》學習心得(十三)
這兩天在做一個專案,關於北京市計程車的,然後用到了Dijkstra演算法,所以這篇文章就先寫Dijkstra演算法了。在大二下的時候學了資料結構,書裡面也講了Dijkstra演算法,但是當時怎麼也沒理解,結果考試的時候就考了,哎蛋疼!現在用到了,又得硬著頭皮去學,結果很快弄明白了,只是在寫程式碼時出了一些很低階的錯誤,調Bug用了不少時間。最後總結只能說:不是你不會,而是沒到你非會不可的地步!在這篇文章裡我就用實際的專案給大家講Dijkstra演算法。
背景:
迪傑斯特拉演算法是由荷蘭電腦科學家狄克斯特拉於1959
年提出的,因此又叫狄克斯特拉演算法。是從一個頂點到其餘各頂點的最短路徑演算法,解決的是有向圖中最短路徑問題。迪傑斯特拉演算法主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。(我覺得這個演算法實質就是每一次你就在所有的路里面找一條最短的路走,這條路走完就不要再走了,在找一條,最後走到你要到的終點就行了,這個過程中沒有任何的啟發性,所以演算法計算量就會很大,對於大型地圖是不實際的,都得需要優化。)
演算法描述
演算法思想:設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分成兩組,第一組為已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,以後每求得一條最短路徑 , 就將加入到集合S中,直到全部頂點都加入到S中,演算法就結束了),第二組為其餘未確定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點為中間頂點的當前最短路徑長度。
演算法步驟:
a.初始時,S只包含源點,即S={v},v的距離為0。U包含除v外的其他頂點,即:U={其餘頂點},若v與U中頂點u有邊,則<u,v>正常有權值,若u不是v的出邊鄰接點,則<u,v>權值為∞。
b.從U中選取一個距離v最小的頂點k,把k,加入S中(該選定的距離就是v到k的最短路徑長度)。
c.以k為新考慮的中間點,修改U中各頂點的距離;若從源點v到頂點u的距離(經過頂點k)比原來距離(不經過頂點k)短,則修改頂點u的距離值,修改後的距離值的頂點k的距離加上邊上的權。
d.重複步驟b和c直到所有頂點都包含在S中。
執行動畫過程如下圖:
我的運用:
在這個專案中我就是需要找到兩個點之間的最短路徑,然後在圖我是用一個ArrayList
先看一些實體類吧:
Point類:
<span style="font-size:14px;"><span style="font-size:14px;">package com.tangbo;
public class Point {
private String pointId;//longitude+latitude構建出來的
private String date;//這個點創造的實際日期,專案需要的屬性,大家可以不用管
private double longitude;//經度
private double latitude;//緯度
private String isHavePeople;//專案需要的屬性,大家可以不用管
private String roadNum;//專案需要的屬性,大家可以不用管
private double ObservProbability;//專案需要的屬性,大家可以不用管
private int position;//記錄這個點在該路里面是第幾個點,因為一條路是由點集構成的,我需要知道這個點在這條路的那個位置
public Point() {
super();
}
public Point(String pointId)
{
this.pointId = pointId;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getIsHavePeople() {
return isHavePeople;
}
public void setIsHavePeople(String isHavePeople) {
this.isHavePeople = isHavePeople;
}
public String getRoadNum() {
return roadNum;
}
public void setRoadNum(String roadNum) {
this.roadNum = roadNum;
}
public double getObservProbability() {
return ObservProbability;
}
public void setObservProbability(double observProbability) {
ObservProbability = observProbability;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
@Override
public String toString() {
return longitude + "," + latitude;
}
}
</span></span>
LineSegment(路段)類:
<span style="font-size:14px;"><span style="font-size:14px;">package com.tangbo;
public class LineSegment {
private String roadNum;//路段的編號
private Point starPoint;//起點
private Point endPoint;//終點
private double disc;//這段路有多長
public LineSegment() {
super();
}
public LineSegment(Point starPoint, Point endPoint,double disc) {
super();
this.starPoint = starPoint;
this.endPoint = endPoint;
this.disc = disc;
}
public LineSegment(Point starPoint, Point endPoint) {
super();
this.starPoint = starPoint;
this.endPoint = endPoint;
this.disc = Util.calcuDistByCoordinate(starPoint, endPoint);
}
public Point getStarPoint() {
return starPoint;
}
public void setStarPoint(Point starPoint) {
this.starPoint = starPoint;
}
public Point getEndPoint() {
return endPoint;
}
public void setEndPoint(Point endPoint) {
this.endPoint = endPoint;
}
public String getRoadNum() {
return roadNum;
}
public void setRoadNum(String roadNum) {
this.roadNum = roadNum;
}
public double getDisc() {
return disc;
}
public void setDisc(double disc) {
this.disc = disc;
}
@Override
public String toString() {
return "LineSegment [roadNum=" + roadNum + ", starPoint=" + starPoint
+ ", endPoint=" + endPoint + ", disc=" + disc + "]";
}
}
</span></span>
ShortPath類:
<span style="font-size:14px;"><span style="font-size:14px;">package com.tangbo;
import java.util.ArrayList;
public class ShortPath {
private ArrayList<Point> points;//路徑是由點構成的
private double disc;//距離
public ShortPath() {
super();
points = new ArrayList<Point>();
}
public ShortPath(Point point) {
points = new ArrayList<Point>();
points.add(point);
this.disc=ConfigurationFiles.FLAG;
}
public ArrayList<Point> getPoints() {
return points;
}
public void setPoints(ArrayList<Point> points) {
this.points = points;
}
public double getDisc() {
return disc;
}
public void setDisc(double disc) {
this.disc = disc;
}
public void addNode(Point parent) {
if(points==null)
points = new ArrayList<Point>();
points.add(0, parent);
}
public void addWeight(double desc) {
if(desc==ConfigurationFiles.FLAG)
{
disc = ConfigurationFiles.FLAG;
}else
{
disc= disc+desc;
}
}
public void print() {
System.out.println("開始點");
for(int i=0;i<points.size();i++)
{
System.out.println(points.get(i));
}
System.out.println("結束!");
}
@Override
public String toString() {
String temp="";
for(int i=0;i<points.size();i++)
{
temp=temp+points+"#";
}
return temp;
}
}
</span></span>
Dijkstra具體演算法:
<span style="font-size:14px;"><span style="font-size:14px;">package com.tangbo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
public class Dijkstra{
public ArrayList<LineSegment> map=null;//圖
public ArrayList<Point> pointCollection = new ArrayList<Point>();;//頂點集
public ArrayList<Point> bluePoints = null;//還未加入的點
public ArrayList<ShortPath> currentShortPaths = null;//存放目前的所有的最短路徑集合
public int count=0;
public Map<String, Integer> pointIndex = new HashMap<String, Integer>();//建立點的索引,用來判斷一個點是否已經加入頂點集
public Map<String, Double> roadIndex = new HashMap<String, Double>();//建立路段索引,用來判斷兩條路是否連線
public void iniMap() {
//我的所有路段是存在MongoDB裡面,我需要把資料讀出來,然後把地圖初始化
map = new ArrayList<LineSegment>();
pointIndex = new HashMap<String, Integer>();
String json = "{MapID:\"605751\"}";
DBCursor dbCursor = ConfigurationFiles.mongoDB4CRUDRoadInformation.getCollections().find((DBObject)JSON.parse(json));
//DBCursor dbCursor = ConfigurationFiles.mongoDB4CRUDRoadInformation.getCollections().find();
System.out.println("dbCursor:"+dbCursor.count());
while(dbCursor.hasNext())
{
DBObject dbObject = dbCursor.next();
ArrayList<Point> pointsTemp= Util.getTrajectoryByDBObject(dbObject);
LineSegment lineSegment = new LineSegment();
Point starPoint = pointsTemp.get(0);
Point endPoint = pointsTemp.get(pointsTemp.size()-1);
lineSegment.setStarPoint(starPoint);//加入起點
lineSegment.setEndPoint(endPoint);//加入終點
double disc = Util.calcuDistByCoordinate(starPoint, endPoint);
lineSegment.setDisc(disc);//計算距離
lineSegment.setRoadNum(pointsTemp.get(0).getRoadNum());//設定路段標示
map.add(lineSegment);
addPoint(starPoint, endPoint,disc);
}
}
//判斷兩個點是否已經加入到頂點集中
public void addPoint(Point starPoint,Point endPoint,double disc)
{
String starPointId = starPoint.getLongitude()+""+starPoint.getLatitude();
String endPointId = endPoint.getLongitude()+""+endPoint.getLatitude();
roadIndex.put(starPointId+"#"+endPointId, disc);
if(pointIndex.get(starPointId)==null)//利用pointIndex判斷這個點是否已經加入到頂點集中
{
pointCollection.add(starPoint);//將起點加入到
starPoint.setPointId(starPointId);
pointIndex.put(starPointId, pointCollection.size()-1);//然後在pointMap裡面進行備註,用於快速查詢
}
if(pointIndex.get(endPointId)==null)
{
pointCollection.add(endPoint);//將終點加入到
endPoint.setPointId(endPointId);
pointIndex.put(endPointId, pointCollection.size()-1);//然後在pointMap裡面進行備註,用於快速查詢
}
}
//Dijkstra演算法開始
public ShortPath dijkstra(String startPointID,String endPointID)
{
ShortPath shortPath = null;
bluePoints = new ArrayList<Point>();//還未加入的點
currentShortPaths = new ArrayList<ShortPath>();//存放目前已經找到的所有最短路徑
//開始初始化紅點集和藍點集
System.out.println("pointCollection.size():"+pointCollection.size());
if(pointIndex.get(startPointID)!=null)
{
System.out.println("開始節點沒有找到!");
System.out.println(pointCollection.get(pointIndex.get(startPointID)));
}else
{
System.out.println("沒有找到!");
System.exit(0);
}
for(int i=0;i<pointCollection.size();i++)//初始化藍點集
{
Point point = pointCollection.get(i);
bluePoints.add(point);
}
bluePoints.remove(pointCollection.get(pointIndex.get(startPointID)));//講開始節點移出藍點集
ShortPath tempShortPath = new ShortPath(pointCollection.get(pointIndex.get(startPointID)));//開始節點本身是一條最短路徑,加入到currentShortPaths
currentShortPaths.add(tempShortPath);
while(bluePoints.size()>0)
{
System.out.println("bluePoints.size():"+bluePoints.size());
double minDis[][]=new double[currentShortPaths.size()][bluePoints.size()];//儲存每個parent的shortpath的最後一個point與bluepoints之間的最小距離。
for(int i=0;i<minDis.length;i++)//初始化每一個藍點集的權重
{
for(int j=0;j<minDis[0].length;j++)
{
minDis[i][j]=Integer.MAX_VALUE;
}
}
for(int i=0;i<currentShortPaths.size();i++)//給每一個藍點集的節點計算到所有最短路徑的距離
{
ShortPath sortPH=currentShortPaths.get(i);
ArrayList<Point> arrayPoints=sortPH.getPoints();
Point lastPoint=arrayPoints.get(arrayPoints.size()-1);
double dis=ConfigurationFiles.FLAG;//ConfigurationFiles.FLAG=-1,代表這個條路不同
int y=0;//用來儲存bluepoint的位置
for(int j=0;j<bluePoints.size();j++)
{
Point bluePoint=bluePoints.get(j);
if(dis==ConfigurationFiles.FLAG)
{
dis=getWeight(lastPoint, bluePoint);
if(dis!=ConfigurationFiles.FLAG)
{
y=j;
}
continue;
}
if(dis!=ConfigurationFiles.FLAG)
{
double tempDis=getWeight(lastPoint, bluePoint);
if(tempDis!=ConfigurationFiles.FLAG&&tempDis<dis)
{
dis=tempDis;
y=j;
}
}
}
if(dis!=ConfigurationFiles.FLAG)
{
minDis[i][y]=dis+sortPH.getDisc();
}
}
double mindis=minDis[0][0];
int qhlRedPoint=0;
int qhlBluePoint=0;
for(int i=0;i<minDis.length;i++)
{
for(int j=0;j<minDis[i].length;j++)
{
if(minDis[i][j]<mindis)
{
mindis=minDis[i][j];
qhlRedPoint=i;
qhlBluePoint=j;
}
}
}
ShortPath newShortPath=copyShortPath(currentShortPaths.get(qhlRedPoint));
Point newBluePoint=bluePoints.get(qhlBluePoint);
newShortPath.getPoints().add(newBluePoint); //把新的點加入到這個新的最短路徑中
newShortPath.setDisc(mindis); //設定距離
currentShortPaths.add(newShortPath);//新增最短路徑
bluePoints.remove(qhlBluePoint);//移出這個藍點
if(newBluePoint.getPointId().equals(endPointID))
{
shortPath = newShortPath;
break;
}
}
return shortPath;
}
//複製一條最短路徑
private ShortPath copyShortPath(ShortPath shortPath) {
ShortPath tempShortPath = new ShortPath();
tempShortPath.setDisc(shortPath.getDisc());
Point tempPoint;
for(int i=0;i<shortPath.getPoints().size();i++)
{
tempPoint = new Point();
tempPoint = shortPath.getPoints().get(i);
tempShortPath.getPoints().add(tempPoint);
}
return tempShortPath;
}
//獲得兩個點之間的距離,如果這兩個點不直接相連就是他們不是在一個LineSegment上,那麼他的值為ConfigurationFiles.FLAG,代表不通
public double getWeight(Point star,Point end)
{
double result = ConfigurationFiles.FLAG;
String starPointId = star.getLongitude()+""+star.getLatitude();
String endPointId = end.getLongitude()+""+end.getLatitude();
String situation1 = starPointId+"#"+endPointId;
String situation2 = endPointId+"#"+starPointId;
if(roadIndex.get(situation1)!=null)//通過索引檢視這兩個點是否直接相連
{
result = roadIndex.get((starPointId+"#"+endPointId));
}
if(roadIndex.get(situation2)!=null)
{
result=roadIndex.get((endPointId+"#"+starPointId));
}
if(result!=ConfigurationFiles.FLAG)
{
count++;
}
return result;
}
}</span></span>
原始碼下載:http://pan.baidu.com/s/1pJ2sBd5。基本上這個演算法就是這樣,當然還得加一下區域性優化演算法,這個演算法才能真正用於實際的工程,否則演算法執行時間太長了!如果大家還有什麼想法,歡迎來一起交流:tangbon[email protected]。