拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题。
如果要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程,这个流程的时间就是最短时间。
在前面讲了AOV网的基础上,我们来介绍一个新的概念。
在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)。
把AOE网中没有入边的顶点称为始点或源点,没有出边的顶点称为终点或汇点。
由于一个工程,总有一个开始,一个结束,所以正常情况下,AOE网只有一个源点一个汇点。
我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
显然图7-9-3的AOE网而言,开始->发动机完成->部件集中到位->组装完成就是关键路径,路径长度5.5。
关键路径算法原理:
找到所有活动的最早开始时间和最晚开始时间,并且比较它们,如果相等就意味着此活动是关键活动,活动间的路径为关键路径,如果不等就不是。
关键路径算法:
具体详细看书吧
////// 邻接表 /// public class AdjacencyList { ////// 边表结点 /// public class EdgeNode { public int adjvex; //邻接点域,存储该顶点对应的下标 public int weight; //用于存储权值,对于非网图可以不需要(关键路径需要权值) public EdgeNode next; //链域,指向下一个邻接点 } ////// 顶点表结点 /// public class VertexNode { public int inDegree; //入度:拓扑排序用 public int data; //顶点域,存储顶点信息 public EdgeNode firstEdge; //边表头指针 } public int numVertexes; //顶点数 public int numEdges; //边数 public VertexNode[] adjacencyArr; //顶点表 ////// 说明:没有按书上写,需要的数据自行提供,包括顶点数据和边表数据 /// 这里也只是大概的思路 /// /// /// /// /// public AdjacencyList(int numVertexes, int numEdges, int[] datas, EdgeNode[] edgeData) { this.numEdges = numEdges; this.numVertexes = numVertexes; adjacencyArr = new VertexNode[numVertexes]; for (int i = 0; i < this.numVertexes; i++) //建立顶点表 { adjacencyArr[i].data = datas[i]; //顶点数据 adjacencyArr[i].firstEdge = edgeData[i]; //建立边表 } } private bool[] visited; ////// 邻接表的深度优先遍历 /// /// public void DFSTraverse(AdjacencyList gl) { for (int i = 0; i < gl.numVertexes; i++) { visited[i] = false; //初始化所有顶点都是未访问的状态 } for (int i = 0; i < gl.numVertexes; i++) { //对未访问过的顶点调用DFS,若是连通图,只会执行一次 if (!visited[i]) { DFS(gl, i); } } } ////// 邻接表的深度优先递归算法 /// /// /// private void DFS(AdjacencyList gl, int i) { visited[i] = true; //这里对顶点的 *** 作,这里简单的打印 Debug.Log(gl.adjacencyArr[i].data); EdgeNode temp = gl.adjacencyArr[i].firstEdge; while (temp != null) { if (!visited[temp.adjvex]) DFS(gl, temp.adjvex); temp = temp.next; } } ////// 邻接表:广度优先遍历 /// /// public void BFSTraverse(AdjacencyList gl) { Queuequeue = new Queue (); //初始化辅助队列 EdgeNode p; for (int i = 0; i < gl.numVertexes; i++) { visited[i] = false; } for (int i = 0; i < gl.numVertexes; i++) //对每一个顶点做循环 { if (!visited[i]) //若是未访问过就处理 { visited[i] = true; //设置当前顶点访问过 //这里对顶点的操作,这里简单的打印 Debug.Log(gl.adjacencyArr[i].data); queue.Enqueue(i); //将此顶点入队列 while (queue.Count > 0) //当前队列有元素 { i = queue.Dequeue(); //出队列 p = gl.adjacencyArr[i].firstEdge; //找到当前顶点边表链表头指针 while (p != null) { //此顶点未访问过 if (!visited[p.adjvex]) { visited[p.adjvex] = true; //这里对顶点的 *** 作,这里简单的打印 Debug.Log(gl.adjacencyArr[p.adjvex].data); queue.Enqueue(p.adjvex); //将此顶点入队列 } p = p.next; //指针指向下一个邻接点 } } } } } /// /// 普里姆算法:最小生成树 /// /// public void MiniSpanTree_Prim(Graph graph) { int minCost, i, j, minCostIndex; int[] adjvex = new int[graph.numVertex]; //保存相关顶点下标 int[] lowCost = new int[graph.numVertex]; //保存相关顶点间边的权值 lowCost[0] = 0; //初始化第一个权值为0,即V0加入生成树,lowCost的值为0,在这里就是此下标的顶点已经加入生成树 adjvex[0] = 0; //初始化第一个顶点下标为0,从顶点V0开始 //读取第一行,初始化 for (i = 1; i < graph.numVertex; i++) //循环除下标为0外的全部顶点 { lowCost[i] = graph.arcs[0, i]; //将V0顶点与之有边的权值存入数组 adjvex[i] = 0; //初始化都为V0的下标 } //构造最小生成树 for (i = 1; i < graph.numVertex; i++) { minCost = int.MaxValue; //初始化最小权值为极大值,通常设置为不可能的大数字,如:32765、65535等 j = 1; //顶点下标循环变量 minCostIndex = 0; //用来存储最小权值的顶点下标 //遍历每一行的数据 while (j < graph.numVertex) { if (lowCost[j] != 0 && lowCost[j] < minCost) { minCost = lowCost[j]; //让当前权值成为最小值 minCostIndex = j; //将当前最小值的下标存入k } j++; } Debug.Log("打印当前顶点边中权值最小边:" + adjvex[minCostIndex] + "---" + minCostIndex); lowCost[minCostIndex] = 0; //将当前顶点的权值设置为0,表示此顶点已经完成任务 for (j = 1; j < graph.numVertex; j++) //循环所有顶点 { if (lowCost[j] != 0 && graph.arcs[minCostIndex, j] < lowCost[j]) { //若下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 lowCost[j] = graph.arcs[minCostIndex, j]; //将较小权值存入lowCost adjvex[j] = minCostIndex; //将下标为k的顶点存入adjvex } } } } ////// 拓扑排序:需要一个栈辅助,存储处理过程中入度为0的顶点 /// 目的是为了避免么个查找时都要去遍历顶点表找有没有入度为0的顶点 /// /// 邻接表 ///无回路返回true,有回路返回false public bool TopologicalSort(AdjacencyList gl) { EdgeNode node; int k, topIndex; int count = 0; //用于统计输出顶点的个数 Stackstack = new Stack (gl.numVertexes); //建栈存储入度为0的顶点 for (int i = 0; i < gl.numVertexes; i++) { if (gl.adjacencyArr[i].inDegree == 0) { stack.Push(i); } } while (stack.Count != 0) { topIndex = stack.Pop(); //出栈 Debug.Log("->" + gl.adjacencyArr[topIndex].data); //打印此顶点 count++; //统计输出顶点数 for (node = gl.adjacencyArr[topIndex].firstEdge; node != null; node = node.next) { //对此顶点弧表遍历 k = node.adjvex; if ((--gl.adjacencyArr[k].inDegree) == 0) //将k号顶点邻接点的入度减1 { stack.Push(k); //若为0则入栈,以便下次循环输出 } } } if (count < gl.numVertexes) return false; //如果count小于顶点数,说明存在环 else return true; } int[] etv; //事件最早发生的时间数组 int[] ltv; //事件最迟发生的时间数组 Stack stack2; //存储拓扑序列的栈 /// /// 关键路径的拓扑排序:需要一个栈辅助,存储处理过程中入度为0的顶点 /// 目的是为了避免么个查找时都要去遍历顶点表找有没有入度为0的顶点 /// /// 邻接表 ///无回路返回true,有回路返回false public bool TopologicalSortForCriticalPath(AdjacencyList gl) { EdgeNode node; int k, topIndex; int count = 0; //用于统计输出顶点的个数 Stackstack = new Stack (gl.numVertexes); //建栈存储入度为0的顶点 for (int i = 0; i < gl.numVertexes; i++) { if (gl.adjacencyArr[i].inDegree == 0) { stack.Push(i); } } //为求关键路径添加的 etv = new int[gl.numVertexes]; for (int i = 0; i < gl.numVertexes; i++) { etv[i] = 0; } stack2 = new Stack (gl.numVertexes); while (stack.Count != 0) { topIndex = stack.Pop(); //出栈 Debug.Log("->" + gl.adjacencyArr[topIndex].data); //打印此顶点 count++; //统计输出顶点数 //为求关键路径添加的 stack2.Push(topIndex); //将d出的顶点序列号压入拓扑序列的栈 for (node = gl.adjacencyArr[topIndex].firstEdge; node != null; node = node.next) { //对此顶点弧表遍历 k = node.adjvex; if ((--gl.adjacencyArr[k].inDegree) == 0) //将k号顶点邻接点的入度减1 { stack.Push(k); //若为0则入栈,以便下次循环输出 } //为求关键路径添加的 if ((etv[topIndex] + node.weight) > etv[k]) //求各顶点事件最早发生时间值 { etv[k] = etv[topIndex] + node.weight; } } } if (count < gl.numVertexes) return false; //如果count小于顶点数,说明存在环 else return true; } /// /// 关键路径:gl为有向网,输出gl的各项关键活动 /// /// public void CriticalPath(AdjacencyList gl) { EdgeNode node; int i, topIndex, k, j; int ete, lte; //声明活动最早发生时间和最迟发生时间变量 TopologicalSortForCriticalPath(gl); //求拓扑序列,计算数组etv和stack2的值 ltv = new int[gl.numVertexes]; for (i = 0; i < gl.numVertexes; i++) { ltv[i] = etv[gl.numVertexes - 1]; //初始化ltv } while (stack2.Count != 0) //计算ltv { topIndex = stack2.Pop(); //将拓扑序列出栈,后进先出 for (node = gl.adjacencyArr[topIndex].firstEdge; node != null; node = node.next) { //求各顶点事件的最迟发生时间ltv值 k = node.adjvex; if (ltv[k] - node.weight < ltv[topIndex]) //求各顶点事件最晚发生时间ltv { ltv[topIndex] = ltv[k] - node.weight; } } } for (j = 0; j < gl.numVertexes; j++) //求ete,lte和关键活动 { for (node = gl.adjacencyArr[j].firstEdge; node != null; node = node.next) { k = node.adjvex; ete = etv[j]; //活动最早发生时间 lte = ltv[k] - node.weight; //活动最迟发生时间 if (ete == lte) //两者相等即在关键路径上 { Debug.Log($"length:{node.weight}"); } } } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)