第9章 图论算法

拓扑排序是对有向无圈图的顶点的一种排序,使得如果存在一条从Vi到Vj的路径,那么在排序中Vj就出现在Vi的后面。

一个简单的求拓扑排序的算法是先找出任意一个没有入边的顶点。然后显示出该顶点,并将它及其边一起从图中删除。然后,我们对图的其余部分同样应用这样的方法处理。 

简单拓扑排序的伪代码

 1     /**
 2      * void topsort() throws CycleFoundException
 3      * {
 4      *     for(int counter = 0; counter < NUM_VERTICES; counter++)
 5      *     {
 6      *         Vertex v = findNewVertexOfIndegreeZero();
 7      *         if(v = null)
 8      *              throw new CycleFoundException();
 9      *         v.topNum = counter;
10      *         for each Vertex w adjacent to v
11      *              w.indegree--;
12      *     }
13      * }
14      */

入度的计算由下面的代码实现,同理可见此计算的花销也是O(|E| + |V|),虽然其中有嵌套的循环。

1     /**
2      * for each Vertex v
3      *      v.indegree = 0;
4      *
5      * for each Vertex v
6      *      for each Vertex w adjacent to v
7      *          w.indegree++;
8      */

我们可以通过将所有(未分配拓扑编号)的入度为0的顶点放在一个特殊的盒子中而消除这种无效的劳动。此时findNewVertexOfIndegreeZero方法返回(并删除)的是该盒子中的任一项点。当我们降低它的邻接顶点的入度时,检查每一个顶点并在它的入度降为0时把它放入盒子中。

为实现这个盒子,我们可以使用一个栈或一个队列。首先,对每个顶点计算它的入度。然后,将所有入度为0的顶点放入一个初始为空的队列中。当队列不空时,删除一个顶点v,并将v邻接的所有顶点的入度均减1。只要一个顶点的入度降为0,就把该顶点放入队列中。此时,拓扑排序就是顶点出队的顺序。

 1     /**
 2      * void topsort() throws CycleFoundException
 3      * {
 4      *     Queue<Vertex> q = new Queue<Vertex>( );
 5      *     int count = 0;
 6      *     
 7      *     for each Vertex v
 8      *          if(v.indegree == 0)
 9      *                  q.enqueue(v);
10      *                  
11      *     while(!q.isEmpty( ))
12      *     {
13      *         Vertex v = q.dequeue();l
14      *         v.topNum = ++counter;
15      *         
16      *         for each Vertex w adjacent to v
17      *              if(--w.indegree == 0)
18      *                      q.enqueue(w);
19      *     }
20      *     if(counter != NUM_VERTICES)
21      *          throw new CycleFoundException;
22      * }
23      */

广度优先搜索

无权最短路径算法的伪代码1

 1     /**
 2      * void unweighted(Vertex s)
 3      * {
 4      *     for each Vertex v
 5      *     {
 6      *         v.dist = INFINITY;
 7      *         v.known = false;
 8      *     }
 9      *
10      *     s.dist = 0;
11      *
12      *     for(int currDist = 0; currDist < NUM_VERTICES; currDist++)
13      *          for each Vertex x
14      *              if(!v.known && v.dist == currDist)
15      *              {
16      *                  v.known = true;
17      *                  for each Vertex w adjacent to v
18      *                      if(w.dist == INFINITY)
19      *                      {
20      *                          w.dist = currDist + 1;
21      *                          w.path = x;
22      *                      }
23      *              }
24      * }
25      */

无权最短路径算法的伪代码2

在迭代开始的时候,队列只含有距离为currDist的那些顶点。当添加距离为currDist + 1的那些邻接顶点时,由于它们自队尾入队,因此这就保证它们直到所有距离为currDist的顶点都被处理之后才被处理。在距离currDist处的最后一个顶点出队并被处理之后,队列只含有距离为currDist + 1的顶点,因此该过程将不断进行下去。

 1     /**
 2      * void unweighted(Vertex s)
 3      * {
 4      *     Queue<Vertex> q = new Queue<Vertex>;
 5      *
 6      *     for each Vertex v
 7      *          v.dist = INFINITY;
 8      *
 9      *     s.dist = 0;
10      *     q.enqueue(s);
11      *
12      *     while(!q.isEmpty())
13      *     {
14      *         Vertex v = q.dequeue();
15      *         
16      *         for each Vertex w adjacent to v
17      *              if(w.dist == INFINITY)
18      *              {
19      *                  w.dist = v.dist + 1;
20      *                  w.path = v;
21      *                  q.enqueue(w);
22      *              }
23      *     }
24      * }
25      */

使用与对拓扑排序进行同样的分析,我们看到,只要使用邻接表,则运行时间就是O(|E| + |V|)。

Dijkstra算法

Dijkstra算法中的Vertex类

 1     /**
 2      * class Vertex
 3      * {
 4      *     public List adj;         // Adjacency List
 5      *     public boolean known;
 6      *     public DistType dist;    // DistType is probably int
 7      *     public Vertex path;
 8      *          // Other fields and methods as needed
 9      * }
10      */

显示实际最短路径的例程

 1     /**
 2      * void printPath(Vertex v)
 3      * {
 4      *     if(v != null)
 5      *     {
 6      *         printPath(v.path)
 7      *         System.out.print("to");
 8      *     }
 9      *     System.out.print(v);
10      * }
11      */

Dijkstra算法的伪代码

 1     /**
 2      * void dijkstra(Vertex s)
 3      * {
 4      *     for each Vertex v
 5      *     {
 6      *         v.dist = INFINITY;
 7      *         v.known = false;
 8      *     }
 9      *
10      *     s.dist = 0;
11      *
12      *     while(there is an unknown distance vertex)
13      *     {
14      *         Vertex v = smallest unknown distance vertex;
15      *
16      *         v.known = true;
17      *
18      *         for each Vertex w adjacent to v
19      *              if(!w.known)
20      *              {
21      *                  DistType cvw = cost of edge from v to w;
22      *
23      *                  if(v.dist + cvw < w.dist)
24      *                  {
25      *                      // Update w
26      *                      decrease(w.dist to v.dist + cvw);
27      *                      w.path = v;
28      *                  }
29      *              }
30      *     }
31      * }
32      */

具有负边值的赋权最短路径算法的伪代码

 1    /**
 2      * void weightedNegative(Vertex v)
 3      * {
 4      *     Queue<Vertex> q = new Queue<Vertex>( );
 5      *
 6      *     for each Vertex v
 7      *          v.dist = INFINITY;
 8      *
 9      *     s.dist = 0;
10      *     q.enqueue(s);
11      *
12      *     while(!q.isEmpty())
13      *     {
14      *         Vertex v = q.dequeue();
15      *
16      *         for each Vertex w adjacent to v
17      *              if(v.dist + cvw < w.dist)
18      *              {
19      *                  //Update w
20      *                  w.dist = v.dist + cvw;
21      *                  w.path = v;
22      *                  if(w is not already in q)
23      *                      q.enqueue(w);
24      *              }
25      *     }
26      * }
27      */

事件节点图

为了找出方案的最早完成时间,我们只要找出从第一个事件到最后一个事件的最长路径的长。

借助顶点的拓扑顺序计算它们的最早完成时间,而最晚完成时间则通过倒转它们的拓扑顺序来计算。

事件节点图中每条边的松弛时间代表对应动作可以被延迟而又不至于推迟整体的完成的时间量。

松弛时间 = 最晚完成时间 - 最早完成时间。

某些动作的松弛时间为零,这些动作是关键性的动作,它们必须按计划结束。至少存在一种完全由零-松弛边组成的路径,这样的路径是关键路径

最短路径,求词梯的Java例程

第一个例程是findChain,它利用Map表示邻接表和两个要被连接的单词,同时返回一个Map,在该Map中,关键字是单词,而相应的值是位于从first开始的最短词梯上的关键字前面的那个单词。

getChainFromPreviousMap使用prev Map和second,它是Map中的一个关键字并返回用于形成词梯的那些单词,通过prev向后工作。通过使用LinkedList并在前头插入,我们得到以正确顺序排列的词梯。

 1     public static List<String>
 2     findChain(Map<String, List<String>>adjacentWords, String first, String second)
 3     {
 4         Map<String, String> previousWord = new HashMap<>();
 5         LinkedList<String> q = new LinkedList<>();
 6 
 7         q.addLast(first);
 8         while (!q.isEmpty())
 9         {
10             String current = q.removeFirst();
11             List<String> adj = adjacentWords.get(current);
12 
13             if (adj != null)
14                 for (String adjWord : adj)
15                     if (previousWord.get(adjWord) == null)
16                     {
17                         previousWord.put(adjWord, current);
18                         q.addLast(adjWord);
19                     }
20         }
21         previousWord.put(first, null);
22 
23         return getChainFromPreviousMap(previousWord, first, second);
24     }
25 
26     public static List<String> getChainFromPreviousMap(Map<String, String> prev, String first, String second)
27     {
28         LinkedList<String>result = null;
29 
30         if (prev.get(second) != null)
31         {
32             result = new LinkedList<>();
33             for (String str = second; str != null; str = prev.get(str))
34                 result.addLast(str);
35         }
36         return result;
37     }

9.5.2 Kruskal算法

 1     /**
 2      * ArrayList<Edge>kruskal(List<Edge>edges, int numVertices)
 3      * {
 4      *     DisjSets ds = new DisjSets(numVertices);
 5      *     PriorityQueue<Edge>pq = new Priority<edges>;
 6      *     List<Edge> mst = new ArrayList<>();
 7      *
 8      *     while(mst.size() != numVertices - 1)
 9      *     {
10      *         Edge e = pq.deleteMin();
11      *         SetType uset = ds.find(e.getu());
12      *         SetType vset = ds.dind(e.getv());
13      *
14      *         if(uset != vset)
15      *         {
16      *             // Accept the edge
17      *             mst.add(e);
18      *             ds.union(uset, vset);
19      *         }
20      *     }
21      *     return mst;
22      * }
23      */

9.6 深度优先搜索模板(伪代码)

1     /**
2      * void dfs(Vertex v)
3      * {
4      *     v.visited = true;
5      *     for each Vertex w adjacent to v
6      *          if(!w.visited)
7      *              dfs(w);
8      * }
9      */

一个连通的无向图如果不存在被删除之后使得剩下的图不再连通的顶点,那么这样的无向连通图就称为是双连通

对顶点的Num赋值的例程(伪代码

 1     /**
 2      * void assignNum(Vertex v)
 3      * {
 4      *     v.num = counter++;
 5      *     v.visited = true;
 6      *     for each Vertex w adjacent to v
 7      *          if(!w.visited)
 8      *          {
 9      *              w.parent = v;
10      *              assignNum(w);  
11      *          }
12      * }
13      */

计算Low并检验是否割点的伪代码(忽略对根的检验)

 1     /**
 2      * // Assign low; also check for articulation points.
 3      * void assignLow(Vertex v)
 4      * {
 5      *     v.low = v.num;
 6      *     for each Vertex w adjacent to v
 7      *     {
 8      *         if(w.num > v.num)
 9      *         {
10      *             assignLow(w);
11      *             if(w.low >= v.num)
12      *                  System.out.println(v + "is an articulation point");
13      *             v.low = min(v.low, w.low);
14      *         }
15      *         else
16      *         if(v.parent != w)
17      *             v.low = min(v.low, w.num);
18      *     }
19      * }
20      */

不存在一个遍历必须是先序遍历或后序遍历的法则。在递归调用前和递归调用后都有可能进行处理

在一次深度优先搜索(忽略对根的测试)中对割点的检测(伪代码)

 1     /**
 2      * void findArt(Vertex v)
 3      * {
 4      *     v.visited = true;
 5      *     v.low = v.num = counter++;
 6      *     for each Vertex w adjacent to v
 7      *     {
 8      *         if(!w.visited)
 9      *         {
10      *             w.parent = v;
11      *             findArt(w);
12      *             if(w.low >= v.num)
13      *                  System.out.println(v + " is an articulation point");
14      *             v.low = min(v.low, w.low);
15      *         }
16      *         else
17      *         if(v.parent != w)
18      *              v.low = min(v.low, w.num);
19      *     }
20      * }
21      */

9.49 编写一个程序计算其单字母替换取值为1,而单字母添加或删除取值p > 0的词梯,取值由用户指定。

  1 import java.io.File;
  2 import java.util.ArrayList;
  3 import java.util.Scanner;
  4 
  5 public class WordLadder
  6 {
  7     static int INFINITY = 99999;
  8 
  9     private static class Vertex
 10     {
 11         Vertex()
 12         {
 13             adj = new ArrayList<>();
 14             weight = new ArrayList<>();
 15         }
 16         public ArrayList<Integer> adj;      // Adjacent list
 17         public ArrayList<Integer> weight;   // Weight list
 18         public boolean known;
 19         public int dist;
 20         public String name;
 21         public int path;
 22     }
 23 
 24     /**
 25      * Print shortest path to v after dijkstra has run.
 26      * Assume that the path exists.
 27      */
 28     static void printPath(int vIndex, ArrayList<Vertex>V)
 29     {
 30         if (vIndex >= 0 && V.get(vIndex).path > -1)
 31         {
 32             printPath(V.get(vIndex).path, V);
 33             System.out.println(" to ");
 34         }
 35         System.out.println(V.get(vIndex).name);
 36     }
 37 
 38     static void dijkstra(int sIndex, int tIndex, ArrayList<Vertex>Vertices)
 39     {
 40         int smallestDist;
 41         int smallestVertex;
 42         Vertex s, v, t;
 43         int n = Vertices.size();
 44 
 45         for ( ; ; )
 46         {
 47             smallestDist = INFINITY;
 48             smallestVertex = -1;
 49 
 50             for (int i = 0; i < n; i++)
 51             {
 52                 if (!Vertices.get(i).known && Vertices.get(i).dist < smallestDist)
 53                 {
 54                     smallestDist = Vertices.get(i).dist;
 55                     smallestVertex = i;
 56                 }
 57             }
 58 
 59             if (smallestVertex < 0 || smallestVertex == tIndex)
 60                 break;
 61 
 62             Vertices.get(smallestVertex).known = true;
 63             v = Vertices.get(smallestVertex);
 64 
 65             for (int j = 0; j < v.adj.size(); j++)
 66             {
 67                 if (!(Vertices.get(v.adj.get(j))).known)
 68                 {
 69                     if (v.dist + v.weight.get(j) < Vertices.get(v.adj.get(j)).dist)
 70                     {
 71                         // Update w
 72                         Vertices.get(v.adj.get(j)).dist = v.dist + v.weight.get(j);
 73                         Vertices.get(v.adj.get(j)).path = smallestVertex;
 74                     }
 75                 }
 76             }
 77         }
 78     }
 79 
 80     static ArrayList<Vertex>readWords(Scanner in)
 81     {
 82         String oneLine = new String();
 83         ArrayList<Vertex>v = new ArrayList<>();
 84         while (in.hasNext())
 85         {
 86             oneLine = in.next();
 87             Vertex w = new Vertex();
 88             w.name = oneLine;
 89             w.known = false;
 90             w.path = -1;
 91             w.dist = INFINITY;
 92             v.add(w);
 93         }
 94         return v;
 95     }
 96 
 97     static int oneCharOff(String word1, String word2, int p)
 98     {
 99         String big, small, shrink;
100         int cost;
101 
102         if (Math.abs((int)(word1.length() - word2.length())) > 1)
103             return 0;
104         else
105             if (word1.length() == word2.length())
106             {
107                 int diffs = 0;
108 
109                 for (int i = 0; i < word1.length(); i++)
110                     if (word1.charAt(i) != word2.charAt(i))
111                         if (++diffs > 1)
112                             return 0;
113                 if (diffs == 1)
114                     return 1;
115             }
116 
117             if (word2.length() > word1.length())
118             {
119                 big = word2;
120                 small = word1;
121             }
122             else
123             {
124                 big = word1;
125                 small = word2;
126             }
127 
128             for (int i = 0; i < big.length(); i++)
129             {
130                 shrink = big.substring(0, i) + big.substring(i + 1,big.length());
131                 if (shrink.compareTo(small) == 0)
132                     return p;
133             }
134 
135             return 0;
136     }
137 
138     // fills the adjacency lists for all words in the dictionary
139     static void fillAdjacencies(ArrayList<Vertex>words, int p)
140     {
141         int cost;
142 
143         for (int i = 0; i < words.size(); i++)
144             for (int j = i + 1; j < words.size(); j++)
145             {
146                 cost = oneCharOff(words.get(i).name, words.get(j).name, p);
147                 if (cost > 0)
148                 {
149                     words.get(i).adj.add(i);
150                     words.get(i).weight.add(cost);
151                     words.get(j).adj.add(j);
152                     words.get(j).weight.add(cost);
153                 }
154             }
155     }
156 
157     public static void main(String[] args)
158     {
159         int p = 0;
160         int w1Index, w2Index;
161         String w1 = new String();
162         String w2 = new String();
163         Scanner input = new Scanner(System.in);
164         Scanner dict;
165         try
166         {
167             dict = new Scanner(new File("dict.txt"));
168         }
169         catch (Exception e)
170         {
171             System.out.println("Error opening dictionary file");
172             return;
173         }
174 
175         System.out.println("What is the cost of single char deletions: ");
176         p = input.nextInt();
177 
178         ArrayList<Vertex>words = readWords(dict);
179 
180         do
181         {
182             System.out.println("Enter two words in the dictionary: ");
183             w1 = input.next();
184             w2 = input.next();
185             // Find their indices(here is where a map would be superior)
186             // However all other accesses are now in O(1) time
187             for (w1Index = 0; w1Index < words.size() && (words.get(w1Index).name).compareTo(w1) != 0; w1Index++);
188             for (w2Index = 0; w2Index < words.size() && (words.get(w2Index).name).compareTo(w2) != 0; w2Index++);
189         }while (w1Index >= words.size() || w2Index >= words.size());
190 
191         fillAdjacencies(words,p);           // make the adjacency list
192         dijkstra(w1Index, w2Index, words);  // use dijkstra's algorithm
193         System.out.println();
194         printPath(w2Index,words);           // print the result
195         System.out.println();
196     }
197 }
原文地址:https://www.cnblogs.com/tjj-love-world/p/10577153.html