有向图基本算法 -- 遍历算法
1. 图的表示
2. 有向图的遍历算法:深度优先
3. 有向图的遍历算法:广度优先
4 代码反思
5. 下载
1. 图的表示
1.1 图的定义
图G定义为V和E的集合G={V, E},其中V表示图中的所有的顶点集合,E表示的是G中的所有的边的集合。图按照E中的元素是否有方向,分为有向图和无向图。
1.2 图的表示方法
上面给出的数学上图的定义,那么在计算机中如何表示图?通常意义上,有下面的两种方法:邻接表和邻接矩阵表示法。
无向图的邻接表和邻接矩阵表示如下所示:
有向图的邻接表和邻接矩阵表示如下所示:
根据上面的表示方法,下面定义图G的这种数据结构(邻接表),首先定义图的顶点GraphVertex:
// 顶点显示的符号
public char Symbol { get; set; }
// 顶点当前颜色
public VertexColor Color { get; set; }
// 顶点和开始节点之间的距离
public int Distance { get; set; }
// 广度遍历父节点
public GraphVertex Parent { get; set; }
// 深度优先搜索中的开始时间
public int StartTime { get; set; }
// 深度优先搜索中的结束时间
public int FinishTime { get; set; }
// 顶点对应的边
public List<GraphEdge> FollowEdges { get; set; }
定义图G的边的数据结构:
// 边开始顶点,在邻接表的存储中其实没有必要存储
public GraphVertex From { get; set; }
// 结束顶点
public GraphVertex To { get; set; }
// 边权重
public int Weight { get; set; }
定义图:
// 数据成员,这里假设的是顶点的symbol是各个不相同的
private Hashtable graph =
new Hashtable();
private int time = 0;
整体上的结构如下:
2. 有向图的深度优先算法
2.1 基本算法
其中d表明的是某个节点第一次被发现的时间点,f表明从节点出发的全部节点已经被发现的时间。
2.2 设计实现
// 深度优先遍历算法
public void DepthFirstVisit(GraphVertex v)
{
// 刚刚被发现,颜色为gray
Console.WriteLine(v.Symbol);
v.Color = VertexColor.GRAY;
this.time++;
// 开始时间
v.StartTime = this.time;
foreach (GraphEdge edge in v.FollowEdges)
{
// 还未被发现
if (edge.To.Color == VertexColor.WHITE)
{
edge.To.Parent = v;
DepthFirstVisit(edge.To);
}
}
// 如果边都已经发现完成
v.Color = VertexColor.BLACK;
this.time++;
v.FinishTime = this.time;
}
public void DepthFirstTravel()
{
// 全局时间变量
this.time = 0;
// 初始化
GraphVertex v;
foreach (DictionaryEntry e in this.graph)
{
v = (GraphVertex)e.Value;
v.Color = VertexColor.WHITE;
v.Parent = null;
}
// 递归调用
// 队所有的顶点
foreach (DictionaryEntry e in this.graph)
{
v = (GraphVertex)e.Value;
// 顶点为白色
if (v.Color == VertexColor.WHITE)
{
DepthFirstVisit(v);
}
}
}
3. 有向图的遍历算法:广度优先
3.1 基本算法
其中color域表示的是当前某个节点被发现的状态。如果是white表明没有被发现,gray表示当前顶点已经被发现,但是从该节点出发的节点还没有被全部发现。parent域定义的是在搜索算法时父节点。distance域表明的是从节点s到某个发现的节点v的路径距离。
3.2 设计实现
// 广度优先遍历算法,同时生成广度优先树
public void BreadthFirstTravel(GraphVertex s)
{
// 初始化所有节点
GraphVertex v;
foreach (DictionaryEntry e in this.graph)
{
v = (GraphVertex)e.Value;
v.Color = VertexColor.WHITE;
v.Distance = int.MaxValue;
v.Parent = null;
}
// 发现第一个节点
s.Color = VertexColor.GRAY;
s.Distance = 0;
s.Parent = null;
// 初始化队列
Queue context =
new Queue();
context.Enqueue(s);
// 如果队列不空的话
while (context.Count != 0)
{
// 队首元素出队
v = context.Dequeue() as GraphVertex;
Console.WriteLine(v.Symbol);
// 遍历v的节点
foreach (GraphEdge item in v.FollowEdges)
{
if ( .Color == VertexColor.WHITE)
{
.Color = VertexColor.GRAY;
.Distance = v.Distance + 1;
.Parent = v;
context.Enqueue();
}
}
v.Color = VertexColor.BLACK;
}
}
4. 代码反思
上面的搜索代码结构是比较典型的搜索结构:首先定义队列或者是栈来保存程序运行状态,如果容器不空,取出元素,然后对取出的元素做一些处理。
5. 代码下载
=================================================================
Java版代码实现
1. import
2.
3. import
4.
5.
6.
7. // 模块E
8.
9. public class
10.
11. protected SeqList<E> vertexlist; // 顺序表存储图的顶点集合
12.
13.
14.
15. protected int[][] adjmatrix; // 图的邻接矩阵 二维图 存储的是每个顶点的名称(A,B,C,D....)
16.
17.
18.
19. private final int MAX_WEIGHT = Integer.MAX_VALUE / 2;
20.
21.
22.
23. // private final int MAX_WEIGHT = 10000;
24.
25.
26.
27. // -------一,构造图:增删改查-------------------------//
28.
29. public AdjMatrixGraph(int n) {// n为顶点的数目
30.
31. this.vertexlist = new
32.
33. this.adjmatrix = new int[n][n];
34.
35. for (int i = 0; i < n; i++)
36.
37. for (int j = 0; j < n; j++)
38.
39. this.adjmatrix[i][j] = (i == j) ? 0
40.
41. // 对角线上为0,其他的都为无穷大。
42.
43. }
44.
45.
46.
47. // 构造函数内一个是字符串数组,一个是edge的set集合
48.
49. public
50.
51. this(vertices.length);
52.
53. for (int i = 0; i < vertices.length; i++)
54.
55. insertVertex(vertices[i]);// 添加顶点
56.
57. for (int j = 0; j < edges.length; j++)
58.
59. insertEdge(edges[j]);// 添加边
60.
61. }
62.
63.
64.
65. // 构造函数内一个是数组集合,一个是edge的set集合
66.
67. public
68.
69. this(list.length());
70.
71. this.vertexlist = list;
72.
73. for (int j = 0; j < edges.length; j++)
74.
75. insertEdge(edges[j]);
76.
77. }
78.
79.
80.
81. // 显示出一共顶点的数目
82.
83. public int
84.
85. return this.vertexlist.length();
86.
87. }
88.
89.
90.
91. // 根据编号得到该顶点
92.
93. public E get(int
94.
95. return this.vertexlist.get(i);
96.
97. }
98.
99.
100.
101. public boolean insertVertex(E vertex) { // 插入一个顶点,若插入成功,返回true
102.
103.
104.
105. return this.vertexlist.add(vertex);
106.
107. }
108.
109.
110.
111. public boolean insertEdge(int i, int j, int
112.
113. // 插入一条权值为weight的边<vi,vj>,若该边已有,则不插入
114.
115. {
116.
117. if (i >= 0 && i < vertexCount() && j >= 0
118.
119. && i != j && adjmatrix[i][j] == MAX_WEIGHT) {
120.
121. // 先判断该边两个顶点的编号是否在范围,该边的值是否为最大值,来确定所添加边的值是否存在;
122.
123. this.adjmatrix[i][j] = weight;// 添加权值
124.
125. return true;
126.
127. }
128.
129. return false;
130.
131. }
132.
133.
134.
135. public boolean
136.
137. if (edge != null)
138.
139. ;
140.
141. return
142.
143. }
144.
145.
146.
147. public
148.
149. String str = "顶点集合: " + vertexlist.toString() + "\n";
150.
151. str += "邻近矩阵: \n";
152.
153. int
154.
155. for (int i = 0; i < n; i++) {
156.
157. for (int j = 0; j < n; j++) {
158.
159. if
160.
161. str += " ∞";// 最大值(不存在)的时候的显示方式;
162.
163. else
164.
165. str += " " + adjmatrix[i][j];// 每一个顶点到其他顶点的权值
166.
167. }
168.
169. str += "\n";
170.
171. }
172.
173. return
174.
175. }
176.
177.
178.
179. public boolean removeEdge(int i, int j) // 删除边〈vi,vj〉,若成功,返回T
180.
181. {
182.
183. if (i >= 0 && i < vertexCount() && j >= 0
184.
185. && i != j && this.adjmatrix[i][j] != MAX_WEIGHT) {
186.
187. // 判断该边的两个顶点是否存在,以及改边的值是否为最大值来判断改边是否存在;
188.
189. this.adjmatrix[i][j] = MAX_WEIGHT; // 设置该边的权值为无穷大,说明已不存在;
190.
191. return true;
192.
193. }
194.
195. return false;
196.
197. }
198.
199.
200.
201. public boolean removeVertex(int v) // 删除序号为v的顶点及其关联的边
202.
203. {
204.
205. int n = vertexCount(); // 删除之前的顶点数
206.
207. if (v >= 0 && v < n) {// V的要求范围
208.
209. this.vertexlist.remove(v); // 删除顺序表的第i个元素,顶点数已减一
210.
211. for (int i = v; i < n - 1; i++)
212.
213. for (int j = 0; j < n; j++)
214.
215. this.adjmatrix[i][j] = this.adjmatrix[i + 1][j]; // 邻接矩阵:删除点以下往上移动一位
216.
217. for (int j = v; j < n - 1; j++)
218.
219. for (int i = 0; i < n - 1; i++)
220.
221. this.adjmatrix[i][j] = this.adjmatrix[i][j + 1]; // 邻接矩阵:删除点以右往左移动一位
222.
223. return true;
224.
225. }
226.
227. return false;
228.
229. }
230.
231.
232.
233. public int getFirstNeighbor(int v) // 返回顶点v的第一个邻接顶点的序号
234.
235. {
236.
237. return getNextNeighbor(v, -1);
238.
239. } // 若不存在第一个邻接顶点,则返回-1
240.
241.
242.
243. public int getNextNeighbor(int v, int w) { // 返回v在w后的下一个邻接顶点
244.
245. if (v >= 0 && v < vertexCount() && w >= -1 && w < vertexCount()// 对v
246.
247. // w的范围限定
248.
249. && v != w)
250.
251. for (int j = w + 1; j < vertexCount(); j++)
252.
253. // w=-1时,j从0开始寻找下一个邻接顶点
254.
255. if (adjmatrix[v][j] > 0
256.
257. // 遍历和v相关的点,得到下一个点
258.
259. return
260.
261. return -1;
262.
263. }
264.
265.
266.
267. // -------二,最小生成树-------------------------//
268.
269.
270.
271. /*
272.
273. * 普里姆算法的基本思想: 取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。 在添加的顶点 w
274.
275. * 和已经在生成树上的顶点v之间必定存在一条边, 并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。
276.
277. * 之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
278.
279. */
280.
281.
282.
283. public
284.
285. Edge[] mst = new Edge[this.vertexCount() - 1]; // n个顶点最小生成树有n-1条边
286.
287. int
288.
289. List<Integer> u = new ArrayList<Integer>();// 存放所有已访问过的顶点集合
290.
291. u.add(0);// 起始点默认为标识为0的顶点
292.
293. for (int i = 0; i < this.vertexCount() - 1; i++) {
294.
295. int minweight = MAX_WEIGHT;// 最小边的时候,权值
296.
297. int minstart = MAX_WEIGHT;// 最小边的时候,起点
298.
299. int mindest = MAX_WEIGHT;// 最小边的时候,终点
300.
301. for (int j = 0; j < u.size(); j++) {
302.
303. un = u.get(j);
304.
305. for (int k = 0; k < this.vertexCount(); k++) {
306.
307. // 获取最小值的条件:1.该边比当前情况下的最小值小;2.该边还未访问过;
308.
309. if
310.
311. minweight = adjmatrix[un][k];
312.
313. minstart = un;
314.
315. mindest = k;
316.
317. }
318.
319. }
320.
321. }
322.
323. System.out.println("一次遍历所添加的最小边:他的权值,起点,终点分别为:weight:"
324.
325. + "start:" + minstart + "dest:"
326.
327. u.add(mindest);
328.
329. Edge e = new
330.
331. mst[i] = e;
332.
333. }
334.
335. return new AdjMatrixGraph(this.vertexlist, mst); // 构造最小生成树相应的图对象
336.
337. }
338.
339.
340.
341. /*
342.
343. * public AdjMatrixGraph minSpanTree_kruskal() { }
344.
345. */
346.
347.
348.
349. // -------三,图的遍历(广度遍历,深度遍历)-------------------------//
350.
351. public void
352.
353. int n = this.vertexCount();
354.
355. boolean[] visited = new boolean[n];
356.
357. for (int i = 1; i < n; i++) {
358.
359. visited[i] = false;
360.
361. }
362.
363. // 编号0为起始点,进行一次深度优先遍历(一次得到一个连通分量)
364.
365. for (int j = 0; j < n; j++) {
366.
367. if
368.
369. System.out.println("以该顶点为" + j + "起始点的遍历:");
370.
371. this.DFS(j, visited);
372.
373. }
374.
375. }
376.
377. }
378.
379.
380.
381. // 参数1:遍历起始点的编号,参数2:记录各个顶点是否被访问过
382.
383. public void DFS(int v, boolean[] visited2) {
384.
385. boolean[] visited = visited2;
386.
387. visited[v] = true;
388.
389. System.out.println("遍历顶点"
390.
391. for (int w = this.getFirstNeighbor(v); w >= 0; w = this
392.
393. .getNextNeighbor(v, w)) {
394.
395. if
396.
397. visited[w] = true;
398.
399. DFS(w, visited);
400.
401. }
402.
403. }
404.
405. }
406.
407.
408.
409. public void
410.
411. int n = this.vertexCount();
412.
413. boolean[] visited = new boolean[n];
414.
415. MyQueue myqueue = new
416.
417. for (int i = 1; i < n; i++) {
418.
419. visited[i] = false;
420.
421. }
422.
423.
424.
425. for (int j = 0; j < n; j++) {
426.
427. if
428.
429. visited[j] = true;
430.
431. System.out.println("遍历起点:"
432.
433. myqueue.EnQueue(j);
434.
435. while
436.
437. int
438.
439. System.out.println("遍历点:"
440.
441. for (int w = this.getFirstNeighbor(v); w >= 0; w = this
442.
443. .getNextNeighbor(v, w)) {
444.
445. if
446.
447. visited[w] = true;
448.
449. myqueue.EnQueue(w);
450.
451. }
452.
453. }
454.
455. }
456.
457. }
458.
459. }
460.
461.
462.
463. }
464.
465.
466.
467. // -------四,图的最短路径Dijkstra算法-------------------------//
468.
469. public void
470.
471. int n = this.vertexCount();
472.
473. int
474.
475. int minUn = 0;
476.
477. int[] minmatrix = new int[n];// 存放当前起始点到其余各个顶点的距离;
478.
479. boolean[] isS = new boolean[n];// 判断各个是否被访问过
480.
481. String[] route = new String[n];// 每个字符串是显示对应顶点最短距离的路径;
482.
483. for (int i = 1; i < n; i++) {// 初始化
484.
485. minmatrix[i] = adjmatrix[0][i];
486.
487. isS[i] = false;
488.
489. route[i] = "起点->"
490.
491. }
492.
493. for (int i = 1; i < n; i++) {
494.
495. // 选择 当前 和起点 连通的,且值最小的顶点;
496.
497. for (int k = 1; k < n; k++) {
498.
499. if
500.
501. if
502.
503. minweight = minmatrix[k];
504.
505. minUn = k;
506.
507. }
508.
509. }
510.
511. }
512.
513. isS[minUn] = true;// 将该点设置为已访问;
514.
515. for (int j = 1; j < n; j++) {
516.
517. if (!isS[j]) {// 判断:该顶点还没加入到S中/属于U-S;
518.
519. if
520.
521. // 通过当下最小值 访问到得其他顶点的距离小于原先的最小值 则进行交换值
522.
523. minmatrix[j] = minweight + adjmatrix[minUn][j];
524.
525. route[j] = route[minUn] + "->"
526.
527. }
528.
529. }
530.
531. }
532.
533. minweight = MAX_WEIGHT;// 因为要放到下一个循环中,所以一定要重设置一下,回到最大值
534.
535. }
536.
537. for (int m = 1; m < n; m++) {
538.
539. System.out.println("从V0出发到达" + m + "点");
540.
541. if
542.
543. System.out.println("没有到达该点的路径");
544.
545. } else
546.
547. System.out.println("当前从V0出发到达该点的最短距离:"
548.
549. System.out.println("当前从V0出发到达该点的最短距离:"
550.
551.
552.
553. }
554.
555. }
556.
557. }
558.
559.
560.
561. // -------五,图的连通性-------------------------//
562.
563. public boolean
564.
565. int n = this.vertexCount();
566.
567. boolean[] visited = new boolean[n];
568.
569. // 记录不能一次深度优先遍历通过的数目
570.
571. // 全部顶点作为出发点开始遍历,如果全部都不能一次遍历通过(notConnectNum == n),说明该图不连通。
572.
573. int notConnectNum = 0;
574.
575. for (int j = 0; j < n; j++) {
576.
577. for (int i = 0; i < n; i++) {
578.
579. visited[i] = false;
580.
581. }
582.
583. this.DFS(j, visited);
584.
585. for (int k = 0; k < n; k++) {
586.
587. System.out.println(visited[k]);
588.
589. if (visited[k] == false) {
590.
591. notConnectNum++;
592.
593. break;// 一旦有没有被遍历到的顶点(说明该顶点不属于该连通分量),跳出循环
594.
595. }
596.
597. }
598.
599. }
600.
601. if
602.
603. System.out.println("此图是不连通的");
604.
605. return false;
606.
607. } else
608.
609. System.out.println("此图是连通的");
610.
611. return true;
612.
613. }
614.
615. }
616.
617.
618.
619. // -------六,图的拓扑排序-------------------------//
620.
621. public void
622.
623. int n = this.vertexCount();
624.
625. int[] indegree = new int[n];
626.
627. MyStack mystack = new
628.
629. String route = "拓扑排序出发:";
630.
631. int count = 0;
632.
633. for (int i = 0; i < n; i++) {
634.
635. indegree[i] = 0;
636.
637. for (int j = 0; j < n; j++) {//获取每一个顶点的入度
638.
639. if (adjmatrix[j][i] != 0
640.
641. indegree[i] += 1;
642.
643. }
644.
645. }//先将入度为0的顶点加入到栈中
646.
647. if (indegree[i] == 0) {
648.
649. mystack.push(i);
650.
651. }
652.
653. }
654.
655. while
656.
657. int v = (Integer) mystack.pop();//从栈中删除该顶点
658.
659. route += "->"
660.
661. ++count;
662.
663. for (int w = this.getFirstNeighbor(v); w >= 0; w = this
664.
665. .getNextNeighbor(v, w)) {
666.
667. indegree[w] -= 1;//因为该顶点被“删除”,所有以该顶点为弧尾的边的弧头的入度减一
668.
669. if (indegree[w] == 0) {
670.
671. mystack.push(w);//先将入度为0的顶点加入到栈中
672.
673. }
674.
675. }
676.
677. }
678.
679. if (count < n) {//当经历拓扑排序遍历后,所有顶点都被“删除”时(count=n),此时实现拓扑排序
680.
681. System.out.println("存在回路,不满足拓扑排序的条件");
682.
683. } else
684.
685. System.out.println("实现拓扑排序"
686.
687.
688.
689. }
690.
691. }
692.
693.
694. }
转载于:https://blog.51cto.com/tianya23/771711