java實(shí)現(xiàn)最短路徑算法之Dijkstra算法
前言
Dijkstra算法是最短路徑算法中為人熟知的一種,是單起點(diǎn)全路徑算法。該算法被稱(chēng)為是“貪心算法”的成功典范。本文接下來(lái)將嘗試以最通俗的語(yǔ)言來(lái)介紹這個(gè)偉大的算法,并賦予java實(shí)現(xiàn)代碼。
一、知識(shí)準(zhǔn)備:
1、表示圖的數(shù)據(jù)結(jié)構(gòu)
用于存儲(chǔ)圖的數(shù)據(jù)結(jié)構(gòu)有多種,本算法中筆者使用的是鄰接矩陣。
圖的鄰接矩陣存儲(chǔ)方式是用兩個(gè)數(shù)組來(lái)表示圖。一個(gè)一維數(shù)組存儲(chǔ)圖中頂點(diǎn)信息,一個(gè)二維數(shù)組(鄰接矩陣)存儲(chǔ)圖中的邊或弧的信息。
設(shè)圖G有n個(gè)頂點(diǎn),則鄰接矩陣是一個(gè)n*n的方陣,定義為:


從上面可以看出,無(wú)向圖的邊數(shù)組是一個(gè)對(duì)稱(chēng)矩陣。所謂對(duì)稱(chēng)矩陣就是n階矩陣的元滿足aij = aji。即從矩陣的左上角到右下角的主對(duì)角線為軸,右上角的元和左下角相對(duì)應(yīng)的元全都是相等的。
從這個(gè)矩陣中,很容易知道圖中的信息。
(1)要判斷任意兩頂點(diǎn)是否有邊無(wú)邊就很容易了;
(2)要知道某個(gè)頂點(diǎn)的度,其實(shí)就是這個(gè)頂點(diǎn)vi在鄰接矩陣中第i行或(第i列)的元素之和;
(3)求頂點(diǎn)vi的所有鄰接點(diǎn)就是將矩陣中第i行元素掃描一遍,arc[i][j]為1就是鄰接點(diǎn);
而有向圖講究入度和出度,頂點(diǎn)vi的入度為1,正好是第i列各數(shù)之和。頂點(diǎn)vi的出度為2,即第i行的各數(shù)之和。
有向圖的定義也類(lèi)似,故不做贅述。
2、單起點(diǎn)全路徑
所謂單起點(diǎn)全路徑,就是指在一個(gè)圖中,從一個(gè)起點(diǎn)出發(fā),到所有節(jié)點(diǎn)的最短路徑。
3、圖論的基本知識(shí)(讀者需自行尋找相關(guān)資料)
4、互補(bǔ)松弛條件
設(shè)標(biāo)量d1,d2,....,dN滿足
dj<=di + aij, (i,j)屬于A,
且P是以i1為起點(diǎn)ik為終點(diǎn)的路,如果
dj = di + aij, 對(duì)P的所有邊(i, j)
成立,那么P是從i1到ik的最短路。其中,滿足上面兩式的被稱(chēng)為最短路問(wèn)題的互補(bǔ)松弛條件。
二、算法思想
1、令G = (V,E)為一個(gè)帶權(quán)無(wú)向圖。G中若有兩個(gè)相鄰的節(jié)點(diǎn),i和j。aij(在這及其后面都表示為下標(biāo),請(qǐng)注意)為節(jié)點(diǎn)i到節(jié)點(diǎn)j的權(quán)值,在本算法可以理解為距離。每個(gè)節(jié)點(diǎn)都有一個(gè)值di(節(jié)點(diǎn)標(biāo)記)表示其從起點(diǎn)到它的某條路的距離。
2、算法初始有一個(gè)數(shù)組V用于儲(chǔ)存未訪問(wèn)節(jié)點(diǎn)的列表,我們暫稱(chēng)為候選列表。選定節(jié)點(diǎn)1為起始節(jié)點(diǎn)。開(kāi)始時(shí),節(jié)點(diǎn)1的d1=0, 其他節(jié)點(diǎn)di=無(wú)窮大,V為所有節(jié)點(diǎn)。
初始化條件后,然后開(kāi)始迭代算法,直到V為空集時(shí)停止。具體迭代步驟如下:
將d值最小的節(jié)點(diǎn)di從候選列表中移除。(本例中V的數(shù)據(jù)結(jié)構(gòu)采用的是優(yōu)先隊(duì)列實(shí)現(xiàn)最小值出列,最好使用斐波那契對(duì),在以前文章有過(guò)介紹,性能有大幅提示)。對(duì)于以該節(jié)點(diǎn)為起點(diǎn)的每一條邊,不包括移除V的節(jié)點(diǎn), (i, j)屬于A, 若dj > di + aij(違反松弛條件),則令
dj = di + aij , (如果j已經(jīng)從V中移除過(guò),說(shuō)明其最小距離已經(jīng)計(jì)算出,不參與此次計(jì)算)
可以看到在算法的運(yùn)算工程中,節(jié)點(diǎn)的d值是單調(diào)不增的
具體算法圖解如下

三、java代碼實(shí)現(xiàn)
public class Vertex implements Comparable<Vertex>{
/**
* 節(jié)點(diǎn)名稱(chēng)(A,B,C,D)
*/
private String name;
/**
* 最短路徑長(zhǎng)度
*/
private int path;
/**
* 節(jié)點(diǎn)是否已經(jīng)出列(是否已經(jīng)處理完畢)
*/
private boolean isMarked;
public Vertex(String name){
this.name = name;
this.path = Integer.MAX_VALUE; //初始設(shè)置為無(wú)窮大
this.setMarked(false);
}
public Vertex(String name, int path){
this.name = name;
this.path = path;
this.setMarked(false);
}
@Override
public int compareTo(Vertex o) {
return o.path > path?-1:1;
}
}
public class Graph {
/*
* 頂點(diǎn)
*/
private List<Vertex> vertexs;
/*
* 邊
*/
private int[][] edges;
/*
* 沒(méi)有訪問(wèn)的頂點(diǎn)
*/
private Queue<Vertex> unVisited;
public Graph(List<Vertex> vertexs, int[][] edges) {
this.vertexs = vertexs;
this.edges = edges;
initUnVisited();
}
/*
* 搜索各頂點(diǎn)最短路徑
*/
public void search(){
while(!unVisited.isEmpty()){
Vertex vertex = unVisited.element();
//頂點(diǎn)已經(jīng)計(jì)算出最短路徑,設(shè)置為"已訪問(wèn)"
vertex.setMarked(true);
//獲取所有"未訪問(wèn)"的鄰居
List<Vertex> neighbors = getNeighbors(vertex);
//更新鄰居的最短路徑
updatesDistance(vertex, neighbors);
pop();
}
System.out.println("search over");
}
/*
* 更新所有鄰居的最短路徑
*/
private void updatesDistance(Vertex vertex, List<Vertex> neighbors){
for(Vertex neighbor: neighbors){
updateDistance(vertex, neighbor);
}
}
/*
* 更新鄰居的最短路徑
*/
private void updateDistance(Vertex vertex, Vertex neighbor){
int distance = getDistance(vertex, neighbor) + vertex.getPath();
if(distance < neighbor.getPath()){
neighbor.setPath(distance);
}
}
/*
* 初始化未訪問(wèn)頂點(diǎn)集合
*/
private void initUnVisited() {
unVisited = new PriorityQueue<Vertex>();
for (Vertex v : vertexs) {
unVisited.add(v);
}
}
/*
* 從未訪問(wèn)頂點(diǎn)集合中刪除已找到最短路徑的節(jié)點(diǎn)
*/
private void pop() {
unVisited.poll();
}
/*
* 獲取頂點(diǎn)到目標(biāo)頂點(diǎn)的距離
*/
private int getDistance(Vertex source, Vertex destination) {
int sourceIndex = vertexs.indexOf(source);
int destIndex = vertexs.indexOf(destination);
return edges[sourceIndex][destIndex];
}
/*
* 獲取頂點(diǎn)所有(未訪問(wèn)的)鄰居
*/
private List<Vertex> getNeighbors(Vertex v) {
List<Vertex> neighbors = new ArrayList<Vertex>();
int position = vertexs.indexOf(v);
Vertex neighbor = null;
int distance;
for (int i = 0; i < vertexs.size(); i++) {
if (i == position) {
//頂點(diǎn)本身,跳過(guò)
continue;
}
distance = edges[position][i]; //到所有頂點(diǎn)的距離
if (distance < Integer.MAX_VALUE) {
//是鄰居(有路徑可達(dá))
neighbor = getVertex(i);
if (!neighbor.isMarked()) {
//如果鄰居沒(méi)有訪問(wèn)過(guò),則加入list;
neighbors.add(neighbor);
}
}
}
return neighbors;
}
/*
* 根據(jù)頂點(diǎn)位置獲取頂點(diǎn)
*/
private Vertex getVertex(int index) {
return vertexs.get(index);
}
/*
* 打印圖
*/
public void printGraph() {
int verNums = vertexs.size();
for (int row = 0; row < verNums; row++) {
for (int col = 0; col < verNums; col++) {
if(Integer.MAX_VALUE == edges[row][col]){
System.out.print("X");
System.out.print(" ");
continue;
}
System.out.print(edges[row][col]);
System.out.print(" ");
}
System.out.println();
}
}
}
public class Test {
public static void main(String[] args){
List<Vertex> vertexs = new ArrayList<Vertex>();
Vertex a = new Vertex("A", 0);
Vertex b = new Vertex("B");
Vertex c = new Vertex("C");
Vertex d = new Vertex("D");
Vertex e = new Vertex("E");
Vertex f = new Vertex("F");
vertexs.add(a);
vertexs.add(b);
vertexs.add(c);
vertexs.add(d);
vertexs.add(e);
vertexs.add(f);
int[][] edges = {
{Integer.MAX_VALUE,6,3,Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE},
{6,Integer.MAX_VALUE,2,5,Integer.MAX_VALUE,Integer.MAX_VALUE},
{3,2,Integer.MAX_VALUE,3,4,Integer.MAX_VALUE},
{Integer.MAX_VALUE,5,3,Integer.MAX_VALUE,5,3},
{Integer.MAX_VALUE,Integer.MAX_VALUE,4,5,Integer.MAX_VALUE,5},
{Integer.MAX_VALUE,Integer.MAX_VALUE,Integer.MAX_VALUE,3,5,Integer.MAX_VALUE}
};
Graph graph = new Graph(vertexs, edges);
graph.printGraph();
graph.search();
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java?EasyExcel實(shí)現(xiàn)合并相同內(nèi)容單元格與動(dòng)態(tài)標(biāo)題功能
這篇文章主要為大家詳細(xì)介紹了Java?EasyExcel如何實(shí)現(xiàn)合并相同內(nèi)容單元格與動(dòng)態(tài)標(biāo)題功能,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考下2023-12-12
基于python locust庫(kù)實(shí)現(xiàn)性能測(cè)試
這篇文章主要介紹了基于python locust庫(kù)實(shí)現(xiàn)性能測(cè)試,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn)
這篇文章主要介紹了Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Spring實(shí)戰(zhàn)之@Autowire注解用法詳解
這篇文章主要介紹了Spring實(shí)戰(zhàn)之@Autowire注解用法,結(jié)合實(shí)例形式詳細(xì)分析了Spring @Autowire注解具體實(shí)現(xiàn)步驟與相關(guān)使用技巧,需要的朋友可以參考下2019-12-12
Java技術(shù)長(zhǎng)久占居主要地位的12個(gè)原因
這篇文章主要為大家詳細(xì)介紹了12個(gè)Java長(zhǎng)久占居主要地位的原因,感興趣的小伙伴們可以參考一下2016-07-07
Java 發(fā)送http請(qǐng)求上傳文件功能實(shí)例
本文通過(guò)實(shí)例代碼給大家介紹了Java 發(fā)送http請(qǐng)求上傳文件功能,需要的朋友參考下吧2017-06-06

