工作中总能遇到 一些 奇葩的需求,提出这些奇葩需求的人,多半也是奇葩的人,要么不懂相关的计算机软件知识,要么就是瞎扯蛋,异想天开,然而这些奇葩的需求,我也总能碰到。言规正传,在一次项目中,使用了 MindFusion 这个组件,在 Winform  窗体中画一些工序(生产中的工艺流程)节点,然后在工序节点间建立有向箭头连线,以此来表示工序的顺序,并行关系。因为有很多工序,每个工序都有一个持续时间,来表示该工序要多少时间才能完成,以天为单位,为了明确整个工序的工期,我们组的领导,想到用项目管理中的关键路径 (从工期开始到结束累计持续时间最长的一条或几条路径)来表示,这个倒是可以的,但是,项目中的每个工序的持续时间有问题,要么是0,要么是-1,要么是大于0的数字,这个就不能用关键路径的算法 来求了,而且工序的开始和结束 可以任意画,想画几个就画几个,想怎么连都可以,这也是个奇葩! 只能呵呵哒,领导还放话了,工序中持续时间为0或-1的可以忽略,这又是个奇葩,这样来看,是不能用关键路径 的算法 来求的,可领导的意图就是关键路径,还让求什么工序最晚开始时间,工序的松弛时间,这些完全是关键路径 中所用到的概念,必须得用关键路径 的算法, 因为工序中存在持续时间为0和-1的节点,用关键路径的算法来做,是行不通的!!! 我都怀疑领导是不是真的懂关键路径的一些概念,吧啦吧啦费了半天劲向领导解释关键路径的一些概念,他总算明白了点,最后决定只把耗时最长的一条或几条工序路径标记出来。这下好了,需求明确了,难度也不大,把图中从开始到结束间的所有可达路径遍历出来 ,然后找出累计持续时间最长一个或几个就行,解决方案如下 :

1. 考虑到 MindFusion 画的节点中,节点与节点间的关系,简单的表示如下 : A-->B  A-->C B-->E  C-->D D-->E,代码如下:

  1. private void StuffLinks(DataRow[] rows)
  2. {
  3. foreach (DataRow dr in rows)
  4. {
  5. //这是业务逻辑,获取工序节点的名称
  6. Model.DataSet_Config.PL_M_THRD_CFG_WORKRELATION2Row drRelation = dr as Model.DataSet_Config.PL_M_THRD_CFG_WORKRELATION2Row;
  7. // 连接名称
  8. string linkName = drRelation.I_PREWORK_ID.ToString() + "-" + drRelation.I_SUBWORK_ID.ToString();
  9. // hsLink 是哈稀表,用来保存工序节点间的连接, key: 连接名称 , value: 连接对象
  10. if (!hsLink.Contains(linkName))
  11. {
  12. // ShapeNode ,DiagramLink diagram 都是 MindFusion.Diagramming中的对象
  13. // 前驱工序
  14. ShapeNode preNode = (ShapeNode)hsNode[drRelation.I_PREWORK_ID];
  15. // 后继工序
  16. ShapeNode subNode = (ShapeNode)hsNode[drRelation.I_SUBWORK_ID];
  17. // 工序间的连接
  18. DiagramLink link = new DiagramLink(diagram, preNode, subNode);
  19. // 连接的画笔
  20. link.Pen = _Pen;
  21. link.Tag = drRelation;
  22. link.AutoRoute = false;
  23. link.LayoutTraits[FlowLayoutTraits.LogicID] = "ControlFlow";
  24. // 在图中添加连接,此时便会显示
  25. diagram.Links.Add(link);
  26. // 向哈稀表中添加 link 对象
  27. hsLink.Add(linkName, link);
  28. }
  29. }
  30. }

2.  要遍历这些工序节点间的路径,涉及到有向图路径的相关知识, 有向图中顶点的存储方式有两种: 一、用邻接表结构 ;二、用矩阵 ;用矩阵来存储,遍历比较复杂,也很消耗资源,在此采用了 邻接表结构,建立的邻接表结构如下 :

  1. /// <summary>
  2. /// AOV 网邻接表数据结构
  3. /// </summary>
  4. public class Vertex
  5. {
  6. /// <summary>
  7. /// 工序
  8. /// </summary>
  9. public DiagramNode Node { get; set; }
  10. /// <summary>
  11. /// 前驱节点
  12. /// </summary>
  13. public List<DiagramNode> PreNodes { get; set; }
  14. /// <summary>
  15. /// 后续节点
  16. /// </summary>
  17. public DiagramNode DesNodes { get; set; }
  18. /// <summary>
  19. /// 工序的耗时
  20. /// </summary>
  21. public decimal Weight { get; set; }
  22. /// <summary>
  23. /// 入度
  24. /// </summary>
  25. public int InDegree { get; set; }
  26. /// <summary>
  27. /// 出度
  28. /// </summary>
  29. public int OutDegree { get; set; }
  30. }

4.  通过工序节点来创建 AOV 网邻接表,为每一个节点设置入度和出度,用来区分是开始工序(只有出度没有入度)还是结束工序(只有入度没有出度),然后从结束节点开始向开始节点搜索(也可以从开始节点开始向结束节点搜索)具体代码如下:

  1. /// <summary>
  2. /// 获取关键路径
  3. /// </summary>
  4. private void GetCriticalPath()
  5. {
  6. // 原画笔颜色
  7. var originalPenBrush = new MindFusion.Drawing.SolidBrush(Color.FromArgb(, , ));
  8. // 原节点颜色
  9. var originalNodeBrush = new MindFusion.Drawing.LinearGradientBrush(Color.FromArgb(, , ), Color.FromArgb(, , ), );
  10. for (int i = ; i < stackCriticalPathLink.Count; i++)
  11. {
  12. DiagramLink item = stackCriticalPathLink.Pop();
  13. item.Pen = this._Pen;
  14. item.Brush = originalPenBrush;
  15. item.HeadPen = this._Pen;
  16. }
  17.  
  18. foreach (var v in listAllPath)
  19. {
  20. foreach (var item in v)
  21. {
  22. item.Node.Brush = originalNodeBrush;
  23. }
  24. }
  25. // 清空集合
  26. listAllPath.Clear();
  27. DiagramLinkCollection dlc = this.diagram.Links;
  28. if (dlc.Count == )
  29. {
  30. this.labelMsg.Text = "工序间未建立关系";
  31. return;
  32. }
  33. // AOV 网节点
  34. List<Vertex> listVertex = new List<Vertex>();
  35. // 构造AOV 网数据结构
  36. foreach (DiagramLink item in dlc)
  37. {
  38. var pre = item.Origin;
  39. var des = item.Destination;
  40. var vPre = listVertex.Find(p => p.Node.Equals(pre));
  41. if (vPre == null)
  42. {
  43. vPre = new Vertex();
  44. vPre.Node = pre;
  45. vPre.Weight = GetWeight((NodeTagAtrribute)pre.Tag);
  46. listVertex.Add(vPre);
  47. }
  48. var vDes = listVertex.Find(p => p.Node.Equals(des));
  49. if (vDes == null)
  50. {
  51. vDes = new Vertex();
  52. vDes.Node = des;
  53. vDes.Weight = GetWeight((NodeTagAtrribute)des.Tag);
  54. listVertex.Add(vDes);
  55. }
  56. }
  57. // 前驱工序,设置节点的出度,入度
  58. foreach (var item in listVertex)
  59. {
  60. GetPreNodes(item, dlc);
  61. }
  62. // 终止节点,出度为0的节点
  63. List<Vertex> listEnd = new List<Vertex>();
  64. GetStartEndNodes(dlc, listVertex, listEnd);
  65. // 全部路径
  66. GetAllPath(listVertex, listEnd);
  67. // 候选路径
  68. List<decimal> listWeights = new List<decimal>();
  69. foreach (var list in this.listAllPath)
  70. {
  71. decimal tempLength = list.Sum(p => p.Weight);
  72. listWeights.Add(tempLength);
  73. }
  74. // 关键路径
  75. decimal pathLenth = listWeights.Max();
  76. int num = ;
  77. for (int i = ; i < listWeights.Count; i++)
  78. {
  79. if (listWeights[i] == pathLenth)
  80. {
  81. var temp = listAllPath[num];
  82. listAllPath[num] = listAllPath[i];
  83. listAllPath[i] = temp;
  84. num++;
  85. }
  86. }
  87. int totalPath = this.listAllPath.Count();
  88. // 移除不是关键路径的项
  89. this.listAllPath.RemoveRange(num, listAllPath.Count() - num);
  90. // 关键路径节点,连线着色
  91. Color lineColor = Color.FromArgb(, , );
  92. Color nodeColor = Color.FromArgb(, , );
  93. var penLine = new MindFusion.Drawing.Pen(lineColor, 1.0F);
  94. var penBrush = new MindFusion.Drawing.SolidBrush(lineColor);
  95. var nodeBrush = new MindFusion.Drawing.LinearGradientBrush(nodeColor, nodeColor, );
  96. // 关键路径中的节点着色
  97. foreach (var item in listAllPath)
  98. {
  99. for (int i = , j = ; i < item.Count; i++)
  100. {
  101. j = i + ;
  102. var t = item[i];
  103. if (j < item.Count)
  104. {
  105. var vertexDes = t.Node.Tag as NodeTagAtrribute;
  106. var vertexrPre = item[j].Node.Tag as NodeTagAtrribute;
  107. string key = vertexrPre._WorkOrder.I_ID.ToString() + "-" + vertexDes._WorkOrder.I_ID.ToString();
  108. if (hsLink.Contains(key))
  109. {
  110. DiagramLink link = hsLink[key] as DiagramLink;
  111. link.Pen = penLine;
  112. link.Brush = penBrush;
  113. link.HeadPen = penLine;
  114. stackCriticalPathLink.Push(link);
  115. }
  116. }
  117. t.Node.Brush = nodeBrush;
  118. }
  119. }
  120. if (totalPath > )
  121. {
  122. this.labelMsg.Text = string.Format("共 {0} 条路径,关键路径 {1} 条,长度:{2}(天)", totalPath, this.listAllPath.Count(), pathLenth);
  123. }
  124. else
  125. {
  126. this.labelMsg.Text = "工序间未建立关系";
  127. }
  128.  
  129. }
  130.  
  131. /// <summary>
  132. /// 前驱节点,同时设置AOV 网节点的入度,出度
  133. /// </summary>
  134. /// <param name="v"></param>
  135. /// <param name="dlc"></param>
  136. private void GetPreNodes(Vertex v, DiagramLinkCollection dlc)
  137. {
  138. v.PreNodes = new List<DiagramNode>();
  139. foreach (DiagramLink item in dlc)
  140. {
  141. if (v.Node.Equals(item.Destination))
  142. {
  143. v.PreNodes.Add(item.Origin);
  144. v.InDegree++;
  145. }
  146. if (v.Node.Equals(item.Origin))
  147. v.OutDegree++;
  148. }
  149. }
  150.  
  151. /// <summary>
  152. /// 获取起止节点
  153. /// </summary>
  154. /// <param name="dlc"></param>
  155. /// <param name="listVertexs"></param>
  156. /// <param name="listEnds"></param>
  157. private void GetStartEndNodes(DiagramLinkCollection dlc, List<Vertex> listVertexs, List<Vertex> listEnds)
  158. {
  159. foreach (var item in listVertexs)
  160. {
  161. if (item.OutDegree == )
  162. listEnds.Add(item);
  163. }
  164. }

5. 节点路径 的搜索,这是本文要讲的关键,相关算法有:回溯法和非回溯法,具体代码如下:

  1. /// <summary>
  2. /// 在AOV 网结构中, 从结束节点开始,倒序搜索从结束节点到开始节点,所有的可达路径
  3. /// </summary>
  4. /// <param name="listVertex"></param>
  5. /// <param name="listEnd"></param>
  6. private void GetAllPath(List<Vertex> listVertex, List<Vertex> listEnd)
  7. {
  8. this.listAllPath.Clear();
  9. this.stackRecallVertex.Clear();
  10. for (int i = ; i < listEnd.Count; i++)
  11. {
  12. List<Vertex> listResult = new List<Vertex>();
  13. RecallSearchPath(listVertex, listEnd[i], listResult);
  14. //SearchPath(listVertex, listEnd[i]);
  15. }
  16. }

 5.1 回溯法

  1. /// <summary>
  2. /// 回溯搜索从结束节点到开始节点的所有可达路径
  3. /// </summary>
  4. /// <param name="listVertex"></param>
  5. /// <param name="vertex"></param>
  6. /// <param name="listResult"></param>
  7. private void RecallSearchPath(List<Vertex> listVertex, Vertex vertex, List<Vertex> listResult)
  8. {
  9. List<DiagramNode> preNodes = vertex.PreNodes;
  10. listResult.Add(vertex);
  11. // 没有前驱节点,即到达开始节点
  12. if (preNodes.Count == )
  13. {
  14. // 保存搜索的路径
  15. listAllPath.Add(listResult);
  16. // 回溯前判断
  17. if (this.stackRecallVertex.Count > )
  18. {
  19. // 从栈顶弹出一个回溯点,并进行回溯
  20. var vPre = this.stackRecallVertex.Pop();
  21. var vDes = listVertex.Find(p => p.Node.Equals(vPre.DesNodes));
  22. int index = listResult.IndexOf(vDes);
  23. // 复制回溯点前的所有路径节点到新的集合
  24. List<Vertex> listTemp = new List<Vertex>();
  25. for (int i = ; i <= index; i++)
  26. {
  27. listTemp.Add(listResult[i]);
  28. }
  29. RecallSearchPath(listVertex, vPre, listTemp);
  30. }
  31. }
  32. if (preNodes.Count > )
  33. {
  34. // 有大于一个分支,将所有分支压入回溯栈,并从栈顶弹出一个分支进行搜索
  35. foreach (var item in preNodes)
  36. {
  37. var v = listVertex.Find(p => p.Node.Equals(item));
  38. v.DesNodes = vertex.Node;
  39. this.stackRecallVertex.Push(v);
  40. }
  41. var vTop = this.stackRecallVertex.Pop();
  42. RecallSearchPath(listVertex, vTop, listResult);
  43. }
  44. if (preNodes.Count == )
  45. {
  46. // 没有分支,直接进行搜索遍历,直到开始节点
  47. var v = listVertex.Find(p => p.Node.Equals(preNodes[]));
  48. RecallSearchPath(listVertex, v, listResult);
  49. }
  50. }

5.2  非回溯法

  1. private void SearchPath(List<Vertex> listVertex, Vertex vertex)
  2. {
  3. // 栈,用于保存前驱顶点
  4. Stack<Vertex> stackVer = new Stack<Vertex>();
  5. // 保存一次完整搜索的顶点路径
  6. List<Vertex> listResult = new List<Vertex>();
  7. // 入栈
  8. stackVer.Push(vertex);
  9. while (stackVer.Count > )
  10. {
  11. // 从栈顶弹出一个顶点,以此顶点,开始搜索
  12. var vPre = stackVer.Pop();
  13. List<DiagramNode> preNodes = vPre.PreNodes;
  14. listResult.Add(vPre);
  15. if (preNodes.Count >= )
  16. {
  17. // 前驱顶点有1个以上(即未到达开始顶点处),将所有前驱顶点压入栈
  18. foreach (var item in preNodes)
  19. {
  20. var v = listVertex.Find(p => p.Node.Equals(item));
  21. v.DesNodes = vPre.Node;
  22. stackVer.Push(v);
  23. }
  24. }
  25. else
  26. {
  27. // 没有前驱顶点,即搜索到达开始顶点处,将本次搜索的完整路径保存下来
  28. // 保存搜索的路径
  29. listAllPath.Add(listResult);
  30. // 判断栈内顶点数,若不为空, 获取栈的头部顶点的后继顶点的索引,并从索引0开复制到此索引处的顶点
  31. if (stackVer.Count > )
  32. {
  33. var vDes = listVertex.Find(p => p.Node.Equals(stackVer.Peek().DesNodes));
  34. int index = listResult.IndexOf(vDes);
  35. // 从索引0开复制到此索引处的顶点
  36. List<Vertex> listTemp = new List<Vertex>();
  37. for (int i = ; i <= index; i++)
  38. {
  39. listTemp.Add(listResult[i]);
  40. }
  41. // 更新搜索路径中的顶点
  42. listResult = listTemp;
  43. }
  44. }
  45. }
  46.  
  47. }

6. 运行效果

相关资料:

MindFusion官网组件:https://www.mindfusion.eu/winforms-ui-dock-control.html

MindFusion 官网API 文档:https://www.mindfusion.eu/onlinehelp/flowchartnet/index.htm

关键路径算法:https://www.jianshu.com/p/1857ed4d8128

回溯算法:https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E6%B3%95

AOV 网:http://www.cnblogs.com/KennyRom/p/6120039.html

MindFusion 中节点关键路径的遍历的更多相关文章

  1. [Leetcode] Construct binary tree from inorder and postorder travesal 利用中序和后续遍历构造二叉树

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:  You may assume th ...

  2. 前、中、后序遍历随意两种是否能确定一个二叉树?理由? && 栈和队列的特点和区别

    前序和后序不能确定二叉树理由:前序和后序在本质上都是将父节点与子结点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树. 由二叉树的中序和前序遍历序列 ...

  3. 读取xml文件中节点

    /// <summary> /// /// </summary> /// <param name="xmlpath">节点路径</para ...

  4. Cocos2D中节点Z序的计算规则

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...

  5. Machine.config 文件中节点<machineKey>的强随机生成

    Machine.config 文件中节点<machineKey>的强随机生成 <machineKey>这个节允许你设置用于加密数据和创建数字签名的服务器特定的密钥.ASP.NE ...

  6. 集群中节点(Node)与单机数据库的区别

    集群中节点(Node)与单机数据库的区别: 区别项 集群中节点(Node) 单机数据库 只能使用0号数据库 是 都可以使用

  7. Java中list如何利用遍历进行删除操作

    转: Java中list如何利用遍历进行删除操作 2018年03月31日 10:23:41 Little White_007 阅读数:3874   Java三种遍历如何进行list的便利删除: 1.f ...

  8. Python中,os.listdir遍历纯数字文件乱序如何解决

    Python中,os.listdir遍历纯数字文件乱序如何解决 日常跑深度学习视觉相关代码时,常常需要对数据集进行处理.许多图像文件名是利用纯数字递增的方式命名.通常所用的排序函数sort(),是按照 ...

  9. xpath的数据和节点类型以及XPath中节点匹配的基本方法

    XPath数据类型  XPath可分为四种数据类型:  节点集(node-set)  节点集是通过路径匹配返回的符合条件的一组节点的集合.其它类型的数据不能转换为节点集.  布尔值(boolean)  ...

随机推荐

  1. SVN使用方法

    用了一年多SVN了,突然想起来对SVN操作做一个总结,以免有些操作不常用而忘记,也希望可以帮到其他人. 准备工作: 在使用SVN时首先就是要在服务器安装SVN管理端(VisualSVN),在电脑上安装 ...

  2. UML作业第三次:分析《书店图书销售管理系统》

    分析图书销售管理系统 一.概览 PlantUML类图语法学习小结 <书店图书销售管理>的类图元素 绘制类图脚本程序 绘制的类图 二.PlantUML类图语法 1.类之间的关系绘制 示例: ...

  3. 锚点的animate使用过程中定位不准确的问题小记

    源码: $('html, body, .S').animate({ scrollTop: $('.a1').offset().top - 133}, { duration: 1500, easing: ...

  4. 增删改查js

    -----------------------------------------------------一---------------------------------------------- ...

  5. 关于PHP 缓冲区: ob_star , ob_get_contents

    PHP ob_star ob_get_contents 细说   作者:田园花香  关于PHP 缓冲区 ob_start: 打开输出缓冲区,当缓冲区激活时,所有来自PHP程序的非头文件信息均不会发送, ...

  6. hadoop的一点小常识(1.0环境)

  7. Python的内置方法

    一 isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 class Foo(object) ...

  8. XSS学习(三)挖掘思路

    HTML标签之间 <div id="body"> [输出点] </div> payload:<script>alert(1)</scrip ...

  9. Fish 下报错 Unsupported use of '||'. In fish, please use 'COMMAND; or COMMAND'.

    在用fish激活virualenv虚拟环境时,使用命令: source ./venv/bin/activate 报错 ./venv/bin/activate (line 23): Unsupporte ...

  10. 使用redis作为Return存储方式

    Return组件可以理解为SaltStack系统对执行Minion返回后的数据进行存储或者返回给其他程序,它支持多种存储方式,比如MySQL.Redis.Memcache等,通过Return我们可以对 ...