当前位置:   article > 正文

数据结构--DAG拓扑排序_5个元素的dag有几种拓扑排序

5个元素的dag有几种拓扑排序

一、引言

二、拓扑排序概念

1.在图中有一个重要的有向图类型,(有向图的表示方法仍然可以是邻接表或者邻接矩阵法)。

2.仅有有向图无环图才具备可以得到拓扑排序的序列。

3.进行拓扑排序有两种方式:

  • 利用图的DFS遍历,记住顶点退出遍历栈的顺序,将该顺序列反过来就是拓扑排序的一个解
  • 利用减治算法,在图中找一个没有输入边的顶点,记录下来并删除,然后下一次执行同样操作,直到删除所有节点。

三、理论讲解

PS:这里会详细介绍上述的两种拓扑排序的方式

1.DFS回溯法

  • 有如下有向无环图

  • 利用DFS进行遍历可得遍历栈的顺序:5,4,3,1 2 (注意是否有逗号)
  • 即出栈次序:54312
  • 可以得拓扑排序的序列之一为:2 1 3 4 5 ,(注意遍历邻顶点时的随机性) 

2. 减治法

  • 首先删除没有输入边的节点 1,
  • 然后删除 2 
  • 删除 3                         
  • 删除 4                 

     

  • 删除5 得到: 1 2    3 4 5

PS:可能会有人觉得两个结果怎么会不一样,这就时上面提到的随机性,先选择节点1和2 会得到不同的结果,其余节点有可能会发生这种情况,也就是说:拓扑排序和DFS一样序列可能有多个。

四、代码实例

PS:此处是转载 https://github.com/wangkuiwu/datastructs_and_algorithm/blob/master/source/graph/topsort/dag/java/ListDG.java

  • 此处是利用DFS遍历进行拓扑排序的方式,另一种比较简单,留给读者思考哦

 

  1. import java.io.IOException;
  2. import java.util.Scanner;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Queue;
  6. import java.util.LinkedList;
  7. public class ListDG {
  8. // 邻接表中表对应的链表的顶点
  9. private class ENode {
  10. int ivex; // 该边所指向的顶点的位置
  11. ENode nextEdge; // 指向下一条弧的指针
  12. }
  13. // 邻接表中表的顶点
  14. private class VNode {
  15. char data; // 顶点信息
  16. ENode firstEdge; // 指向第一条依附该顶点的弧
  17. };
  18. private List<VNode> mVexs; // 顶点数组
  19. /*
  20. * 创建图(自己输入数据)
  21. */
  22. public ListDG() {
  23. // 输入"顶点数"和"边数"
  24. System.out.printf("input vertex number: ");
  25. int vlen = readInt();
  26. System.out.printf("input edge number: ");
  27. int elen = readInt();
  28. if ( vlen < 1 || elen < 1 || (elen > (vlen*(vlen - 1)))) {
  29. System.out.printf("input error: invalid parameters!\n");
  30. return ;
  31. }
  32. // 初始化"顶点"
  33. mVexs = new ArrayList<VNode>();
  34. for (int i = 0; i < vlen; i++) {
  35. System.out.printf("vertex(%d): ", i);
  36. // 新建VNode
  37. VNode vnode = new VNode();
  38. vnode.data = readChar();
  39. vnode.firstEdge = null;
  40. // 将vnode添加到数组mVexs中
  41. mVexs.add(vnode);
  42. }
  43. // 初始化"边"
  44. //mMatrix = new int[vlen][vlen];
  45. for (int i = 0; i < elen; i++) {
  46. // 读取边的起始顶点和结束顶点
  47. System.out.printf("edge(%d):", i);
  48. char c1 = readChar();
  49. char c2 = readChar();
  50. int p1 = getPosition(c1);
  51. int p2 = getPosition(c2);
  52. // 初始化node1
  53. ENode node1 = new ENode();
  54. node1.ivex = p2;
  55. // 将node1链接到"p1所在链表的末尾"
  56. if(mVexs.get(p1).firstEdge == null)
  57. mVexs.get(p1).firstEdge = node1;
  58. else
  59. linkLast(mVexs.get(p1).firstEdge, node1);
  60. }
  61. }
  62. /*
  63. * 创建图(用已提供的矩阵)
  64. *
  65. * 参数说明:
  66. * vexs -- 顶点数组
  67. * edges -- 边数组
  68. */
  69. public ListDG(char[] vexs, char[][] edges) {
  70. // 初始化"顶点数"和"边数"
  71. int vlen = vexs.length;
  72. int elen = edges.length;
  73. // 初始化"顶点"
  74. mVexs = new ArrayList<VNode>();
  75. for (int i = 0; i < vlen; i++) {
  76. // 新建VNode
  77. VNode vnode = new VNode();
  78. vnode.data = vexs[i];
  79. vnode.firstEdge = null;
  80. // 将vnode添加到数组mVexs中
  81. mVexs.add(vnode);
  82. }
  83. // 初始化"边"
  84. for (int i = 0; i < elen; i++) {
  85. // 读取边的起始顶点和结束顶点
  86. char c1 = edges[i][0];
  87. char c2 = edges[i][1];
  88. // 读取边的起始顶点和结束顶点
  89. int p1 = getPosition(edges[i][0]);
  90. int p2 = getPosition(edges[i][1]);
  91. // 初始化node1
  92. ENode node1 = new ENode();
  93. node1.ivex = p2;
  94. // 将node1链接到"p1所在链表的末尾"
  95. if(mVexs.get(p1).firstEdge == null)
  96. mVexs.get(p1).firstEdge = node1;
  97. else
  98. linkLast(mVexs.get(p1).firstEdge, node1);
  99. }
  100. }
  101. /*
  102. * 将node节点链接到list的最后
  103. */
  104. private void linkLast(ENode list, ENode node) {
  105. ENode p = list;
  106. while(p.nextEdge!=null)
  107. p = p.nextEdge;
  108. p.nextEdge = node;
  109. }
  110. /*
  111. * 返回ch位置
  112. */
  113. private int getPosition(char ch) {
  114. for(int i=0; i<mVexs.size(); i++)
  115. if(mVexs.get(i).data==ch)
  116. return i;
  117. return -1;
  118. }
  119. /*
  120. * 读取一个输入字符
  121. */
  122. private char readChar() {
  123. char ch='0';
  124. do {
  125. try {
  126. ch = (char)System.in.read();
  127. } catch (IOException e) {
  128. e.printStackTrace();
  129. }
  130. } while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));
  131. return ch;
  132. }
  133. /*
  134. * 读取一个输入字符
  135. */
  136. private int readInt() {
  137. Scanner scanner = new Scanner(System.in);
  138. return scanner.nextInt();
  139. }
  140. /*
  141. * 深度优先搜索遍历图的递归实现
  142. */
  143. private void DFS(int i, boolean[] visited) {
  144. ENode node;
  145. visited[i] = true;
  146. System.out.printf("%c ", mVexs.get(i).data);
  147. node = mVexs.get(i).firstEdge;
  148. while (node != null) {
  149. if (!visited[node.ivex])
  150. DFS(node.ivex, visited);
  151. node = node.nextEdge;
  152. }
  153. }
  154. /*
  155. * 深度优先搜索遍历图
  156. */
  157. public void DFS() {
  158. boolean[] visited = new boolean[mVexs.size()]; // 顶点访问标记
  159. // 初始化所有顶点都没有被访问
  160. for (int i = 0; i < mVexs.size(); i++)
  161. visited[i] = false;
  162. System.out.printf("== DFS: ");
  163. for (int i = 0; i < mVexs.size(); i++) {
  164. if (!visited[i])
  165. DFS(i, visited);
  166. }
  167. System.out.printf("\n");
  168. }
  169. /*
  170. * 广度优先搜索(类似于树的层次遍历)
  171. */
  172. public void BFS() {
  173. int head = 0;
  174. int rear = 0;
  175. int[] queue = new int[mVexs.size()]; // 辅组队列
  176. boolean[] visited = new boolean[mVexs.size()]; // 顶点访问标记
  177. for (int i = 0; i < mVexs.size(); i++)
  178. visited[i] = false;
  179. System.out.printf("== BFS: ");
  180. for (int i = 0; i < mVexs.size(); i++) {
  181. if (!visited[i]) {
  182. visited[i] = true;
  183. System.out.printf("%c ", mVexs.get(i).data);
  184. queue[rear++] = i; // 入队列
  185. }
  186. while (head != rear) {
  187. int j = queue[head++]; // 出队列
  188. ENode node = mVexs.get(j).firstEdge;
  189. while (node != null) {
  190. int k = node.ivex;
  191. if (!visited[k])
  192. {
  193. visited[k] = true;
  194. System.out.printf("%c ", mVexs.get(k).data);
  195. queue[rear++] = k;
  196. }
  197. node = node.nextEdge;
  198. }
  199. }
  200. }
  201. System.out.printf("\n");
  202. }
  203. /*
  204. * 打印矩阵队列图
  205. */
  206. public void print() {
  207. System.out.printf("== List Graph:\n");
  208. for (int i = 0; i < mVexs.size(); i++) {
  209. System.out.printf("%d(%c): ", i, mVexs.get(i).data);
  210. ENode node = mVexs.get(i).firstEdge;
  211. while (node != null) {
  212. System.out.printf("%d(%c) ", node.ivex, mVexs.get(node.ivex).data);
  213. node = node.nextEdge;
  214. }
  215. System.out.printf("\n");
  216. }
  217. }
  218. /*
  219. * 拓扑排序
  220. *
  221. * 返回值:
  222. * -1 -- 失败(由于内存不足等原因导致)
  223. * 0 -- 成功排序,并输入结果
  224. * 1 -- 失败(该有向图是有环的)
  225. */
  226. public int topologicalSort() {
  227. int index = 0;
  228. int num = mVexs.size();
  229. int[] ins; // 入度数组
  230. char[] tops; // 拓扑排序结果数组,记录每个节点的排序后的序号。
  231. Queue<Integer> queue; // 辅组队列
  232. ins = new int[num];
  233. tops = new char[num];
  234. queue = new LinkedList<Integer>();
  235. // 统计每个顶点的入度数
  236. for(int i = 0; i < num; i++) {
  237. ENode node = mVexs.get(i).firstEdge;
  238. while (node != null) {
  239. ins[node.ivex]++;
  240. node = node.nextEdge;
  241. }
  242. }
  243. // 将所有入度为0的顶点入队列
  244. for(int i = 0; i < num; i ++)
  245. if(ins[i] == 0)
  246. queue.offer(i); // 入队列
  247. while (!queue.isEmpty()) { // 队列非空
  248. int j = queue.poll().intValue(); // 出队列。j是顶点的序号
  249. tops[index++] = mVexs.get(j).data; // 将该顶点添加到tops中,tops是排序结果
  250. ENode node = mVexs.get(j).firstEdge;// 获取以该顶点为起点的出边队列
  251. // 将与"node"关联的节点的入度减1;
  252. // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
  253. while(node != null) {
  254. // 将节点(序号为node.ivex)的入度减1。
  255. ins[node.ivex]--;
  256. // 若节点的入度为0,则将其"入队列"
  257. if( ins[node.ivex] == 0)
  258. queue.offer(node.ivex); // 入队列
  259. node = node.nextEdge;
  260. }
  261. }
  262. if(index != num) {
  263. System.out.printf("Graph has a cycle\n");
  264. return 1;
  265. }
  266. // 打印拓扑排序结果
  267. System.out.printf("== TopSort: ");
  268. for(int i = 0; i < num; i ++)
  269. System.out.printf("%c ", tops[i]);
  270. System.out.printf("\n");
  271. return 0;
  272. }
  273. public static void main(String[] args) {
  274. char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
  275. char[][] edges = new char[][]{
  276. {'A', 'G'},
  277. {'B', 'A'},
  278. {'B', 'D'},
  279. {'C', 'F'},
  280. {'C', 'G'},
  281. {'D', 'E'},
  282. {'D', 'F'}};
  283. ListDG pG;
  284. // 自定义"图"(输入矩阵队列)
  285. //pG = new ListDG();
  286. // 采用已有的"图"
  287. pG = new ListDG(vexs, edges);
  288. pG.print(); // 打印图
  289. //pG.DFS(); // 深度优先遍历
  290. //pG.BFS(); // 广度优先遍历
  291. pG.topologicalSort(); // 拓扑排序
  292. }
  293. }

 

 

执行的图和拓扑排序结果如下:

 

 

五、拓扑排序应用

1.拓扑排序通常用来“排序”具有依赖关系的任务。

比如,如果用一个DAG图来表示一个工程,其中每个顶点表示工程中的一个任务,用有向边 表示在做任务 B 之前必须先完成任务 A。故在这个工程中,任意两个任务要么具有确定的先后关系,要么是没有关系,绝对不存在互相矛盾的关系(即环路)。

 

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

闽ICP备14008679号