当前位置:   article > 正文

(Java)数据结构——图(第六节)Dijkstra实现单源最短路径

(Java)数据结构——图(第六节)Dijkstra实现单源最短路径

前言

本博客是博主用于复习数据结构以及算法的博客,如果疏忽出现错误,还望各位指正。

Dijkstra算法(Dijkstra的实现原理)

迪杰斯特拉算法的实现,很像Prim,基本原理是:

我先找到距离集合路径最短的邻接点,连接,然后看看加入这个新的点后,其他没有遍历过的结点是不是能够更短地到达,有的话就更新。

还是这个图,不过要注意的是,为了凸显出效果,我把AB之间的距离由7改为了8

来走一遍过程:我们以B开始吧

B遍历找到最短距离D权重为3

之后我们进入D,用{3(最短距离)+ D这一行未遍历到的结点}与 {dist中记录的权重}进行比较。A不通INF(注意Java中这时候如果“不通”用Integer.MAX_VALUE表示的话,不能再加,会变成负数的,直接忽略即可),B已经遍历,CD不通,E为3+8=11>5,不更新。结束

然后我们返回dist数组,选取最新的最短路径E权重为5

之后我们进入E,同理

再返回dist数组,选择C权重为6

之后我们进入C,注意,B到A是8,B到C再到A是7,于是进行更新dist和path中对应的值,之后继续……返回dist,最后选择A权重7

以上就是Dijkstra的原理。

Dijkstra的代码实现

主要的实现部分

理解是要格外注意Integer.MAX_VALUE+会变成负数,所以忽略。

  1. public int[] Dijkstra(int begin){
  2. int N = vertexList.size();
  3. //到其他点的最短路径,动态更新,直到最后返回
  4. int[] dist = new int[N];
  5. for(int i=0;i<N;i++){ //进行一下拷贝,以免更改核心数据
  6. dist[i] = edges[begin][i];
  7. }
  8. //System.out.println(Arrays.toString(dist));
  9. isVisited[begin] = true; //该点已经遍历过
  10. int[] path = new int[N]; //记录这个点从哪来的,到时候在path里寻找就行
  11. //比如path 2 1 1 1 1,那么A就是从C来的,然后去C,C是从B来的,B是从B来的,OK结束,路径出来
  12. Arrays.fill(path,begin);
  13. for(int i = 0;i<N-1;i++){//遍历N-1次即可
  14. int min = Integer.MAX_VALUE;
  15. int index = 0;
  16. for(int j = 0;j<N;j++){//寻找当前的最短路径
  17. if(!isVisited[j]){
  18. if(dist[j] < min){
  19. min = dist[j];
  20. index = j;
  21. }
  22. }
  23. }
  24. isVisited[index] = true; //置为遍历过
  25. for(int k = 0;k<N;k++){//新加入点后,看min+edges[新加点的那一行],看是不是比以前的小,小就换了
  26. if(!isVisited[k] && edges[index][k]!=Integer.MAX_VALUE){//此处格外注意,因为Integer.MAX_VALUE再+就变成负的了,所以一定要注意
  27. if(dist[index]+edges[index][k] < dist[k]){
  28. dist[k] = dist[index]+edges[index][k];
  29. path[k] = index;
  30. }
  31. }
  32. }
  33. //找到了之后呢
  34. }
  35. //System.out.println(Arrays.toString(dist));
  36. //System.out.println(Arrays.toString(path));
  37. return dist; //返回的是到每个点的最小路径
  38. }

之后是完整的实现代码(包含其他一些代码)

如果要实现每个点到其他点的最短路径,那么只需要遍历一遍即可,不过别忘了重置isVisited!

  1. //package GraphTest.Demo;
  2. import java.util.*;
  3. public class Graph{//这是一个图类
  4. /***基础属性***/
  5. int[][] edges; //邻接矩阵存储边
  6. ArrayList<EData> to = new ArrayList<>(); //EData包含start,end,weight三个属性,相当于另一种存储方式,主要是为了实现kruskal算法定义的
  7. ArrayList<String> vertexList = new ArrayList<>(); //存储结点名称,当然你若想用Integer也可以,这个是我自己复习用的
  8. int numOfEdges; //边的个数
  9. boolean[] isVisited;
  10. //构造器
  11. Graph(int n){
  12. this.edges = new int[n][n];
  13. //为了方便,决定讲结点初始化为INF,这也方便后续某些操作
  14. int INF = Integer.MAX_VALUE;
  15. for(int i=0;i<n;i++){
  16. Arrays.fill(edges[i],INF);
  17. }
  18. this.numOfEdges = 0;
  19. this.isVisited = new boolean[n];
  20. }
  21. //插入点
  22. public void insertVertex(String vertex){//看自己个人喜好,我这边是一个一个在主方法里插入点的名称
  23. vertexList.add(vertex);
  24. }
  25. //点的个数
  26. public int getNumOfVertex(){
  27. return vertexList.size();
  28. }
  29. //获取第i个节点的名称
  30. public String getVertexByIndex(int i){
  31. return vertexList.get(i);
  32. }
  33. //获取该节点的下标
  34. public int getIndexOfVertex(String vertex){
  35. return vertexList.indexOf(vertex);
  36. }
  37. //插入边
  38. public void insertEdge(int v1,int v2,int weight){
  39. //注意,这里是无向图
  40. edges[v1][v2] = weight;
  41. edges[v2][v1] = weight;
  42. this.numOfEdges++;
  43. //如果要用Kruskal算法的话这里+
  44. to.add(new EData(v1,v2,weight)); //加入from to这种存储方式
  45. }
  46. //边的个数
  47. public int getNumOfEdge(){
  48. return this.numOfEdges;
  49. }
  50. //得到点到点的权值
  51. public int getWeight(int v1,int v2){//获取v1和v2边的权重
  52. return edges[v1][v2];
  53. }
  54. //打印图
  55. public void showGraph(){
  56. for(int[] line:edges){
  57. System.out.println(Arrays.toString(line));
  58. }
  59. }
  60. //获取index行 第一个邻接结点
  61. public int getFirstNeighbor(int index){
  62. for(int i = 0;i < vertexList.size();i++){
  63. if(edges[index][i] != Integer.MAX_VALUE){
  64. return i; //找到就返回邻接结点的坐标
  65. }
  66. }
  67. return -1; //没找到的话,返回-1
  68. }
  69. //获取row行 column列之后的第一个邻接结点
  70. public int getNextNeighbor(int row,int column){
  71. for(int i = column + 1;i < vertexList.size();i++){
  72. if(edges[row][i] != Integer.MAX_VALUE){
  73. return i; //找到就返回邻接结点的坐标
  74. }
  75. }
  76. return -1; //没找到的话,返回-1
  77. }
  78. //DFS实现,先定义一个isVisited布尔数组确认该点是否遍历过
  79. public void DFS(int index,boolean[] isVisited){
  80. System.out.print(getVertexByIndex(index)+" "); //打印当前结点
  81. isVisited[index] = true;
  82. //查找index的第一个邻接结点f
  83. int f = getFirstNeighbor(index);
  84. //
  85. while(f != -1){//说明有
  86. if(!isVisited[f]){//f没被访问过
  87. DFS(f,isVisited); //就进入该节点f进行遍历
  88. }
  89. //如果f已经被访问过,从当前 i 行的 f列 处往后找
  90. f = getNextNeighbor(index,f);
  91. }
  92. }
  93. //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
  94. public void DFS(){
  95. for(int i=0;i<vertexList.size();i++){
  96. if(!isVisited[i]){
  97. DFS(i,isVisited);
  98. }
  99. }
  100. }
  101. public void BFS(int index,boolean[] isVisited){
  102. //BFS是由队列实现的,所以我们先创建一个队列
  103. LinkedList<Integer> queue = new LinkedList<>();
  104. System.out.print(getVertexByIndex(index)+" "); //打印当前结点
  105. isVisited[index] =true; //遍历标志ture
  106. queue.addLast(index); //队尾加入元素
  107. int cur,neighbor; //队列头节点cur和邻接结点neighbor
  108. while(!queue.isEmpty()){//如果队列不为空的话,就一直进行下去
  109. //取出队列头结点下标
  110. cur = queue.removeFirst(); //可以用作出队
  111. //得到第一个邻接结点的下标
  112. neighbor = getFirstNeighbor(cur);
  113. //之后遍历下一个
  114. while(neighbor != -1){//邻接结点存在
  115. //是否访问过
  116. if(!isVisited[neighbor]){
  117. System.out.print(getVertexByIndex(neighbor)+" ");
  118. isVisited[neighbor] = true;
  119. queue.addLast(neighbor);
  120. }
  121. //在cur行找neighbor列之后的下一个邻接结点
  122. neighbor = getNextNeighbor(cur,neighbor);
  123. }
  124. }
  125. }
  126. //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
  127. public void BFS(){
  128. for(int i=0;i<vertexList.size();i++){
  129. if(!isVisited[i]){
  130. BFS(i,isVisited);
  131. }
  132. }
  133. }
  134. public void prim(int begin){
  135. //Prim原理:从当前集合选出权重最小的邻接结点加入集合,构成新的集合,重复步骤,直到N-1条边
  136. int N = vertexList.size();
  137. //当前的集合 与其他邻接结点的最小值
  138. int[] lowcost = edges[begin];
  139. //记录该结点是从哪个邻接结点过来的
  140. int[] adjvex = new int[N];
  141. Arrays.fill(adjvex,begin);
  142. //表示已经遍历过了,isVisited置true
  143. isVisited[begin] = true;
  144. for(int i =0;i<N-1;i++){//进行N-1次即可,因为只需要联通N-1条边
  145. //寻找当前集合最小权重邻接结点的操作
  146. int index = 0;
  147. int mincost = Integer.MAX_VALUE;
  148. for(int j = 0;j<N;j++){
  149. if(isVisited[j]) continue;
  150. if(lowcost[j] < mincost){//寻找当前松弛点
  151. mincost = lowcost[j];
  152. index = j;
  153. }
  154. }
  155. System.out.println("选择节点"+index+"权重为:"+mincost);
  156. isVisited[index] = true;
  157. System.out.println(index);
  158. //加入集合后更新的操作,看最小邻接结点是否更改
  159. for(int k = 0;k<N;k++){
  160. if(isVisited[k]) continue;//如果遍历过就跳过
  161. if(edges[index][k] < lowcost[k]){ //加入新的节点之后更新,检查原图的index节点,加入后,是否有更新的
  162. lowcost[k] = (edges[index][k]);
  163. adjvex[k] = index;
  164. }
  165. }
  166. }
  167. }
  168. //用于对之前定义的to进行排序
  169. public void toSort(){
  170. Collections.sort(this.to, new Comparator<EData>() {
  171. @Override
  172. public int compare(EData o1, EData o2) {
  173. //顺序对就是升序,顺序不对就是降序,比如我现在是o1和o2传进来,比较时候o1在前就是升序,o1在后就是降序
  174. int result = Integer.compare(o1.weight,o2.weight);
  175. return result;
  176. }
  177. });
  178. }
  179. //并查集查找
  180. public int find(int x,int[] f){
  181. while(x!=f[x]){
  182. x = f[x];
  183. }
  184. return x;
  185. }
  186. //并查集合并
  187. public int union(int x,int y,int[] f){
  188. find(x,f);
  189. find(y,f);
  190. if(x!=y){
  191. f[x] = y;
  192. return y;
  193. }
  194. return -1; //如果一样的集合就返回-1
  195. }
  196. public ArrayList<Integer> kruskal(){
  197. //kruskal是对form to weight这种结构的类进行排序,然后选取最短的一条边,看是否形成回路,加入
  198. toSort(); //调用toSort进行排序
  199. //由于要判断是否形成回路,我们可以用DFS或者BFS,因为之前都写过,所以我们在这用并查集
  200. //初始化并查集
  201. int[] f = new int[vertexList.size()];
  202. for(int i = 0;i<vertexList.size();i++){
  203. f[i] = i;
  204. }
  205. ArrayList<Integer> res = new ArrayList<>();
  206. int count = 0;
  207. for(int i = 0;count != vertexList.size()-1 && i<this.to.size();i++){
  208. //之后就是开始取边了
  209. EData temp = this.to.get(i);
  210. if(union(temp.start,temp.end,f)!=-1){//如果查到不是一个集,就可以添加
  211. //这里都加进来是方便看哪个边
  212. res.add(temp.start);
  213. res.add(temp.end);
  214. count++;
  215. }
  216. }
  217. return res; //最后把集合返回去就行
  218. }
  219. public int[] Dijkstra(int begin){
  220. int N = vertexList.size();
  221. //到其他点的最短路径,动态更新,直到最后返回
  222. int[] dist = new int[N];
  223. for(int i=0;i<N;i++){ //进行一下拷贝,以免更改核心数据
  224. dist[i] = edges[begin][i];
  225. }
  226. //System.out.println(Arrays.toString(dist));
  227. isVisited[begin] = true; //该点已经遍历过
  228. int[] path = new int[N]; //记录这个点从哪来的,到时候在path里寻找就行
  229. //比如path 2 1 1 1 1,那么A就是从C来的,然后去C,C是从B来的,B是从B来的,OK结束,路径出来
  230. Arrays.fill(path,begin);
  231. for(int i = 0;i<N-1;i++){//遍历N-1次即可
  232. int min = Integer.MAX_VALUE;
  233. int index = 0;
  234. for(int j = 0;j<N;j++){//寻找当前的最短路径
  235. if(!isVisited[j]){
  236. if(dist[j] < min){
  237. min = dist[j];
  238. index = j;
  239. }
  240. }
  241. }
  242. isVisited[index] = true; //置为遍历过
  243. for(int k = 0;k<N;k++){//新加入点后,看min+edges[新加点的那一行],看是不是比以前的小,小就换了
  244. if(!isVisited[k] && edges[index][k]!=Integer.MAX_VALUE){//此处格外注意,因为Integer.MAX_VALUE再+就变成负的了,所以一定要注意
  245. if(dist[index]+edges[index][k] < dist[k]){
  246. dist[k] = dist[index]+edges[index][k];
  247. path[k] = index;
  248. }
  249. }
  250. }
  251. //找到了之后呢
  252. }
  253. //System.out.println(Arrays.toString(dist));
  254. //System.out.println(Arrays.toString(path));
  255. return dist; //返回的是到每个点的最小路径
  256. }
  257. public static void main(String[] args) {
  258. int n = 5;
  259. String[] Vertexs ={"A","B","C","D","E"};
  260. //创建图对象
  261. Graph graph = new Graph(n);
  262. for(String value:Vertexs){
  263. graph.insertVertex(value);
  264. }
  265. graph.insertEdge(0,1,8);
  266. graph.insertEdge(0,2,1);
  267. graph.insertEdge(1,2,6);
  268. graph.insertEdge(1,3,3);
  269. graph.insertEdge(1,4,5);
  270. graph.insertEdge(3,4,8);
  271. // graph.showGraph();
  272. //
  273. // graph.DFS(1, graph.isVisited);
  274. // System.out.println();
  275. // graph.DFS();//再求求所有的,看有没有剩下的
  276. // System.out.println();
  277. // Arrays.fill(graph.isVisited,false);
  278. // graph.BFS(1, graph.isVisited);
  279. // System.out.println();
  280. // Arrays.fill(graph.isVisited,false);
  281. // graph.BFS();
  282. // System.out.println();
  283. // Arrays.fill(graph.isVisited,false);
  284. // graph.prim(2);
  285. // graph.toSort();
  286. // for(EData i : graph.to){
  287. // System.out.println(i.toString());
  288. // }
  289. // System.out.println(graph.kruskal().toString());;
  290. // Arrays.fill(graph.isVisited,false);
  291. for(int i = 0;i<graph.getNumOfVertex();i++){//每个点的到其他点的最短路径只需要遍历一遍即可,时间复杂度On3
  292. Arrays.fill(graph.isVisited,false);
  293. System.out.println(Arrays.toString(graph.Dijkstra(i)));
  294. }
  295. }
  296. }
  297. class EData{
  298. //当然,这是为了方便,直接记录结点下标,而不记录像"A"这种
  299. int start;
  300. int end;
  301. int weight;
  302. EData(int start,int end,int weight){
  303. this.start = start;
  304. this.end = end;
  305. this.weight = weight;
  306. }
  307. @Override
  308. public String toString() {
  309. return "EData{" +
  310. "start=" + start +
  311. ", end=" + end +
  312. ", weight=" + weight +
  313. '}';
  314. }
  315. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/412682
推荐阅读
相关标签
  

闽ICP备14008679号