当前位置:   article > 正文

11.图-有向图

有向图

目录

一、有向图。 

二、拓扑排序。

(1)检测有向图中是否有环。

 (2)基于深度优先的顶点排序(拓扑排序)。

(3)拓扑排序。

三、加权有向图。

(1)加权有向边。 

(2)加权有向图。

四、最短路径-Dijstra算法。 


 

一、有向图。 

73d727fc62724e7b8d57d29a713919bf.png

  1. package 图的入门.有向图;
  2. import 线性表.线性表_队列.Queue;
  3. public class Digraph {
  4. //顶点数目
  5. private final int V;
  6. //边的数目
  7. private int E;
  8. //邻接表
  9. private Queue<Integer>[] adj;
  10. public Digraph(int V){
  11. //初始化顶点数量
  12. this.V = V;
  13. //初始化边的数量
  14. this.E = 0;
  15. //初始化邻接表
  16. this.adj = new Queue[V];
  17. for (int i = 0; i < adj.length; i++) {
  18. adj[i] = new Queue<>();
  19. }
  20. }
  21. //获取顶点数目
  22. public int V(){
  23. return V;
  24. }
  25. //获取边的数目
  26. public int E(){
  27. return E;
  28. }
  29. //向有向图中添加一条边v-》w
  30. public void addEdge(int v,int w){
  31. //只需要让顶点w出现在顶点v的邻接表中,因为边是有方向的,最终顶点v的邻接表中存储的相邻顶点的含义是:v-》其他顶点
  32. adj[v].enqueue(w);
  33. E++;
  34. }
  35. //获取由v指出的边所连接的所有顶点
  36. public Queue<Integer> adj(int v){
  37. return adj[v];
  38. }
  39. //获取该图的反向图
  40. private Digraph reverse(){
  41. //创建有向图对象
  42. Digraph r = new Digraph(V);
  43. for (int v = 0; v < V; v++) {//原图中表示的是由顶点v-》w的边
  44. //获取由该顶点v指出的所有边
  45. for (Integer w : adj[v]) {
  46. r.addEdge(w,v);//w->v
  47. }
  48. }
  49. return reverse();
  50. }
  51. }

二、拓扑排序

5f1ddf3df8ac49e48df864d7b1ab10e1.png

(1)检测有向图中是否有环。

7c18a91b7ff74d6c8689a2f3f45266ee.png

  1. package 图的入门.有向图.图_拓扑排序_检测有向环;
  2. import 图的入门.有向图.Digraph;
  3. public class DirectedCycle {
  4. //索引代表顶点,值表示当前顶点是否已经被搜索
  5. private boolean[] marked;
  6. //记录图中是否有环
  7. private boolean hasCycle;
  8. //索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上
  9. private boolean[] onStack;
  10. //创建一个检测环对象,检测图G中是否有环
  11. public DirectedCycle(Digraph G){
  12. //初始化marked数组
  13. this.marked = new boolean[G.V()];
  14. //初始化hasCycle
  15. this.hasCycle = false;
  16. //初始化onStack数组
  17. this.onStack = new boolean[G.V()];//基本数据类型都有默认值,这里默认全部为false,引用类型数据默认是null
  18. //找到图中每一个顶点,让每一个顶点作为人口,调用一次dfs进行搜索v
  19. for (int v = 0; v < G.V(); v++) {
  20. //判断如果当前结点还没有搜索过(一个连通子图中有一个调用dfs就足够了,但是图不一定就全部相通(连通图),可能有很多连通子图),则调用dfs进行搜索
  21. if (!marked[v]){
  22. dfs(G,v);
  23. }
  24. }
  25. }
  26. //基于深度优先搜索,检测图G中是否有环
  27. private void dfs(Digraph G,int v){
  28. //把顶点v表示为已搜索
  29. marked[v] = true;
  30. //把当前顶点进栈
  31. onStack[v] = true;
  32. //进行深度搜索
  33. /**
  34. * 重点注意:这里的增强遍历其实不是拿出就删除了(因为这里是记录结点,然后指向下一个结点,是链表的使用),所以可以一直使用
  35. * 如果是调用dequeue()方法,那么就会删除
  36. */
  37. for (Integer w : G.adj(v)) {
  38. //如果当前顶点w没有被搜索过,则继续递归调用dfs方法完成深度优先搜索
  39. if (!marked[w]){
  40. dfs(G,w);
  41. }
  42. //判断当前顶点w是否已经在栈中,如果已经在栈中,证明当前顶点之前处于正在搜索的状态,那么现在又要搜索一次,证明检测到环了
  43. if (onStack[w]){
  44. hasCycle = true;
  45. return;
  46. }
  47. }
  48. //把当前顶点出栈
  49. onStack[v] = false;
  50. }
  51. //判断当前有向图G中是否有环
  52. public boolean hasCycle(){
  53. return hasCycle;
  54. }
  55. }

 (2)基于深度优先的顶点排序(拓扑排序)。

99adf1800178425fbb3c59a23191fab7.png

  1. package 图的入门.有向图.图_拓扑排序_顶点排序;
  2. import 图的入门.有向图.Digraph;
  3. import 线性表.线性表_栈.Stack;
  4. public class DepthFirstOrder {
  5. //索引代表顶点,值表示当前顶点是否已经被搜索
  6. private boolean[] marked;
  7. //使用栈,存储顶点序列
  8. private Stack<Integer> reversePost;
  9. //创建一个检测环对象,检测图G中是否有环
  10. public DepthFirstOrder(Digraph G){
  11. //初始化marked数组
  12. this.marked = new boolean[G.V()];
  13. //初始化reversePost栈
  14. this.reversePost = new Stack<>();
  15. //遍历图中每一个顶点,让每个顶点作为入口,完成一次深度优先搜索
  16. for (int v = 0; v < G.V(); v++) {
  17. if (!marked[v]){
  18. dfs(G,v);
  19. }
  20. }
  21. }
  22. //基于深度优先搜索,检测图G中是否有环
  23. private void dfs(Digraph G,int v){
  24. //标记当前v已经被搜索
  25. marked[v] = true;
  26. //通过循环深度搜索顶点v
  27. for (Integer w : G.adj(v)) {
  28. if (!marked[w]){
  29. dfs(G,w);
  30. }
  31. }
  32. //让顶点v进栈
  33. reversePost.push(v);
  34. }
  35. //获取顶点线性序列
  36. public Stack<Integer> reversePost(){
  37. return reversePost;
  38. }
  39. }

(3)拓扑排序。

e9644b7d52a4477e8de35624b8a3e851.png

 

  1. package 图的入门.有向图.图_拓扑排序;
  2. import 图的入门.有向图.Digraph;
  3. import 图的入门.有向图.图_拓扑排序_检测有向环.DirectedCycle;
  4. import 图的入门.有向图.图_拓扑排序_顶点排序.DepthFirstOrder;
  5. import 线性表.线性表_栈.Stack;
  6. public class TopoLogical {
  7. //顶点的拓扑排序
  8. private Stack<Integer> order;
  9. //构造拓扑排序对象
  10. public TopoLogical(Digraph G){
  11. /**
  12. * 虽然下面创建的两个对象都从同一个队列中取出数据
  13. * 但是使用的是增强for遍历,不会影响队列里面的数据
  14. */
  15. //创建一个检测有向环的对象
  16. DirectedCycle cycle = new DirectedCycle(G);
  17. //判断G图中有没有环,如果没有环,则进行顶点排序:创建一个顶点排序对象
  18. if (!cycle.hasCycle()){
  19. DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
  20. order = depthFirstOrder.reversePost();
  21. }
  22. }
  23. //判断G图是否有环
  24. private boolean isCycle(){
  25. return order == null;
  26. }
  27. //获取拓扑排序的所有顶点
  28. public Stack<Integer> order(){
  29. return order;
  30. }
  31. }

测试代码:

  1. package 图的入门.有向图.图_拓扑排序;
  2. import 图的入门.有向图.Digraph;
  3. import 线性表.线性表_栈.Stack;
  4. public class TopoLogicalTest {
  5. public static void main(String[] args) {
  6. //准备有向图
  7. Digraph digraph = new Digraph(6);
  8. digraph.addEdge(0,2);
  9. digraph.addEdge(0,3);
  10. digraph.addEdge(2,4);
  11. digraph.addEdge(3,4);
  12. digraph.addEdge(4,5);
  13. digraph.addEdge(1,3);
  14. //通过TopoLogical对象对有向图中的顶点进行排序
  15. TopoLogical topoLogical = new TopoLogical(digraph);
  16. //获取顶点的线性序列进行打印
  17. Stack<Integer> order = topoLogical.order();
  18. StringBuilder sb = new StringBuilder();
  19. for (Integer w : order) {
  20. sb.append(w+"->");
  21. }
  22. String str = sb.toString();
  23. int index = str.lastIndexOf("->");
  24. str = str.substring(0, index);
  25. System.out.println(str);
  26. }
  27. }

三、加权有向图。

(1)加权有向边。 

a503bd82d3544042a944ccce057d746d.png

  1. package 图的入门.有向图.加权有向图;
  2. public class DirectedEdge {
  3. private final int v;//起点
  4. private final int w;//终点
  5. private final double weight;//当前边的权重
  6. //通过顶点v和w,以及权重weight值构造一个边对象
  7. public DirectedEdge(int v,int w,double weight){
  8. this.v = v;
  9. this.w = w;
  10. this.weight = weight;
  11. }
  12. //获取边的权重值
  13. public double weight(){
  14. return weight;
  15. }
  16. //获取有向边的起点
  17. public int from(){
  18. return v;
  19. }
  20. //获取有向边的终点
  21. public int to(){
  22. return w;
  23. }
  24. }

(2)加权有向图。

  1. package 图的入门.有向图.加权有向图;
  2. import 线性表.线性表_队列.Queue;
  3. public class EdgeWeightedDigraph {
  4. //顶点总数
  5. private final int V;
  6. //边的总数
  7. private int E;
  8. //邻接表
  9. private Queue<DirectedEdge>[] adj;
  10. //创建一个含有V个顶点的空加权有向图
  11. public EdgeWeightedDigraph(int V){
  12. //初始化顶点数量
  13. this.V = V;
  14. //初始化边的数量
  15. this.E = 0;
  16. //初始化邻接表
  17. this.adj = new Queue[V];
  18. for (int i = 0; i < adj.length; i++) {
  19. adj[i] = new Queue<>();
  20. }
  21. }
  22. //获取图中顶点的数量
  23. public int V(){
  24. return V;
  25. }
  26. //获取图中边的数量
  27. public int E(){
  28. return E;
  29. }
  30. //向加权有向图中添加一条边e
  31. public void addEdge(DirectedEdge e){
  32. //边e是有方向的,所以只需要让e出现在起点的邻接表中即可
  33. int v = e.from();
  34. adj[v].enqueue(e);
  35. E++;
  36. }
  37. //获取由顶点v指出的所有的边
  38. public Queue<DirectedEdge> adj(int v){
  39. return adj[v];
  40. }
  41. //获取加权有向图的所有边
  42. public Queue<DirectedEdge> edges(){
  43. //遍历图中的每一个顶点,得到该顶点的邻接表,遍历得到每一条边,添加到队列中返回即可
  44. Queue<DirectedEdge> allEdges = new Queue<>();
  45. for (int v = 0; v < V; v++) {
  46. for (DirectedEdge e : adj[v]) {
  47. allEdges.enqueue(e);
  48. }
  49. }
  50. return allEdges;
  51. }
  52. }

四、最短路径-Dijstra算法。 

最小生成树是连接所有的顶点,而最短路径是起点到终点,是一条路径,不需要连接所有顶点,只需要部分顶点。

003151bd70994f37b46c85799fc12d77.png

b8afbc0c29fd430882c4941ed9506116.png

  1. package 图的入门.有向图.最短路径_Dijstra算法;
  2. import 优先队列.IndexMinPrioirityQueue;
  3. import 图的入门.有向图.加权有向图.DirectedEdge;
  4. import 图的入门.有向图.加权有向图.EdgeWeightedDigraph;
  5. import 线性表.线性表_队列.Queue;
  6. public class DijstraSP {
  7. //索引代表顶点,值表示从顶点s到当前顶点的最短路径上的最后一条边
  8. private DirectedEdge[] edgeTo;
  9. //索引代表顶点,值从顶点s到当前顶点的最短路径的总权重
  10. private double[] distTo;
  11. //存放树中顶点与非树中顶点之间的有向横切边
  12. private IndexMinPrioirityQueue<Double> pq;
  13. //根据一副加权有向图G和顶点s,创建一个计算顶点s的最短路径树对象
  14. public DijstraSP(EdgeWeightedDigraph G,int s){
  15. //初始化edgeTo
  16. this.edgeTo = new DirectedEdge[G.V()];
  17. //初始化distTo
  18. this.distTo = new double[G.V()];
  19. for (int i = 0; i < distTo.length; i++) {
  20. distTo[i] = Double.POSITIVE_INFINITY;//double类型的最大值
  21. }
  22. //初始化pq
  23. this.pq = new IndexMinPrioirityQueue<>(G.V());
  24. //找到图G中以顶点s为起点的最短路径树
  25. //默认让顶点s进入最短路径树中
  26. distTo[s] = 0.0;
  27. pq.insert(s,0.0);
  28. //遍历pq
  29. while (!pq.isEmpty()){
  30. relax(G,pq.delMin());
  31. }
  32. }
  33. //松弛图G中的顶点v
  34. private void relax(EdgeWeightedDigraph G,int v){
  35. for (DirectedEdge edge : G.adj(v)) {
  36. //获取到该边的终点w
  37. int w = edge.to();
  38. //通过松弛技术,判断从起点s到顶点w的最短路径是否需要先从顶点s到顶点v,然后再由顶点v到顶点w
  39. if (distTo[v] + edge.weight() < distTo[w]){//这一步已经将已存在最短路径中的顶点排除了,进不来
  40. distTo[w] = distTo[v] + edge.weight();
  41. edgeTo[w] = edge;
  42. //判断pq中是否已经存在顶点w,如果存在,则更新权重,如果不存在,则支架添加
  43. if (pq.contains(w)){
  44. pq.changeItem(w,distTo[w]);
  45. }else {
  46. pq.insert(w,distTo[w]);
  47. }
  48. }
  49. }
  50. }
  51. //获取从顶点s到顶点v的最短路径的总权重
  52. public double distTo(int v){
  53. return distTo[v];
  54. }
  55. //判断从顶点s到顶点v是否可通达
  56. public boolean hasPathTo(int v){
  57. return edgeTo[v] != null;
  58. //也可以这样
  59. // return distTo[v] < Double.POSITIVE_INFINITY;
  60. }
  61. //查询从起点s到顶点v的最短路径中所有的边
  62. public Queue<DirectedEdge> pathTo(int v){
  63. //判断从顶点s到顶点v是否可达,如果不可达,直接返回null
  64. if (!hasPathTo(v)){
  65. return null;
  66. }
  67. //创建队列对象
  68. Queue<DirectedEdge> allEdges = new Queue<>();
  69. while (true){
  70. DirectedEdge e = edgeTo[v];
  71. if (e == null){
  72. break;
  73. }
  74. allEdges.enqueue(e);
  75. v = e.from();
  76. }
  77. return allEdges;
  78. }
  79. }
  1. package 图的入门.有向图.最短路径_Dijstra算法;
  2. import 图的入门.有向图.加权有向图.DirectedEdge;
  3. import 图的入门.有向图.加权有向图.EdgeWeightedDigraph;
  4. import 线性表.线性表_队列.Queue;
  5. import java.io.BufferedReader;
  6. import java.io.IOException;
  7. import java.io.InputStreamReader;
  8. public class DijstraSPTest {
  9. public static void main(String[] args) throws IOException {
  10. //创建一副加权有向图
  11. BufferedReader br = new BufferedReader(new InputStreamReader(DijstraSPTest.class.getClassLoader().getResourceAsStream("min_route_test.txt")));
  12. int total = Integer.parseInt(br.readLine());
  13. EdgeWeightedDigraph G = new EdgeWeightedDigraph(total);
  14. int edgeNubers = Integer.parseInt(br.readLine());
  15. for (int i = 0; i < edgeNubers; i++) {
  16. String line = br.readLine();
  17. String[] str = line.split(" ");
  18. int v = Integer.parseInt(str[0]);
  19. int w = Integer.parseInt(str[1]);
  20. double weight = Double.parseDouble(str[2]);
  21. //创建加权有向边
  22. DirectedEdge edge = new DirectedEdge(v, w, weight);
  23. G.addEdge(edge);
  24. }
  25. //创建DijstraSP对象,查找最短路径树
  26. DijstraSP dijstraSP = new DijstraSP(G, 0);
  27. //查找最短路径,0->6的最短路径
  28. Queue<DirectedEdge> edges = dijstraSP.pathTo(6);
  29. //遍历打印
  30. for (DirectedEdge edge : edges) {
  31. System.out.println(edge.from()+"->"+edge.to()+" :: "+edge.weight());
  32. }
  33. }
  34. }

 

 

 

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

闽ICP备14008679号