草庐IT

从零开始画自己的DAG作业依赖图(二)--分层布局算法

华为云官方博客 2023-03-28 原文

概述

当我们把设计稿和技术选型定下来之后,接下来就要开始着手画这个依赖图了。依赖图的组成最简单的就是节点Node 和节点之间的连线。这一节我们要处理的就是节点位置信息的处理。为了确定节点的位置信息,首先要给节点分层,分层的信息取决于节点之间的依赖关系。

问题分析

当前我们默认图是从上到下布局方式,节点分层,最容易想到的就是拓扑排序,通过BFS 宽度优先遍历,计算每个节点的步长。

自顶向下BFS

如上图,我们如果是普通的BFS,我们会发现D 节点应该是第二层,实际上D应该是第三层,所以,实际上每个节点应该取最大的步长,实现如下

function calcLayer(nodes){
	var queue = [];
	var maxLayer = 1;
	var nodesT =  nodes;
        //  入度为0 的点放入队列
	for (var i = 0; i < nodesT.length; i++) {
		if (nodesT[i].inputNode.length === 0) {
			nodesT[i].layer = 1;
			queue.push(nodesT[i]);
		}
	}
	while(queue.length > 0){
		var tNode = queue.shift();
		if (tNode.outputNode && tNode.outputNode.length > 0) {
			for (var j = 0; j < tNode.outputNode.length; j++) {
				var outputNodeIndex = getNodeIndex(tNode.outputNode[j], nodesT);
				if(outputNodeIndex < 0) continue;
				if (nodesT[outputNodeIndex].layer === -1) {
					nodesT[outputNodeIndex].layer = tNode.layer + 1;
				}else{
					nodesT[outputNodeIndex].layer = Math.max(nodesT[outputNodeIndex].layer,tNode.layer + 1);
				}
                // 更新节点层次,选择最大值
				maxLayer = Math.max(maxLayer, nodesT[outputNodeIndex].layer);
				queue.push(nodesT[outputNodeIndex]);
			}
		}
	}
}

至此分层基本完成,但是发现另外一种情况,如下:

如果是按照刚才那种分发,入度为0 的节点必然在第一层,其实这种,我们可能更希望 G在第三层,F 在第二层,例如下图展示的

这样展示,图会更紧凑一点,观察图可知,在链路中间的节点,它的层级就是固定的,例如D节点,但是一些没有上游节点的,例如G,F 的位置确实可以多种选择的。在观察,我们可以知道,如果从E往上BFS,我们会发现,G,F节点就是我们需要的层次了,所以,这时候,我们需要自底向上再进行一次BFS,得到新的层级,并且用自底向上的结果去矫正自上而下的结果,这一点很关键。

自底向上BFS

function bottomToTop (nodes){
	var queue = [];
	var maxLayer = 1;
	for (var i = 0; i <nodes.length; i++) {
		if (nodes.outputNode.length === 0) {
			nodes[i].layer = 1;
			queue.push(nodes[i]);
		}
	}
	while(queue.length > 0){
		var tNode = queue.shift();
		if (tNode.inputNode && tNode.inputNode.length > 0) {
			for (var j = 0; j < tNode.inputNode.length; j++) {
				var inputNodeIndex = getNodeIndex(tNode.inputNode[j], nodes);
				if(inputNodeIndex < 0) continue;
				if (nodes[inputNodeIndex].layer === -1) {
					nodes[inputNodeIndex].layer = tNode.layer + 1;
				}else{
					nodes[inputNodeIndex].layer = Math.max(nodes[inputNodeIndex].layer,tNode.layer + 1);
				}
				maxLayer = Math.max(maxLayer, nodes[inputNodeIndex].layer);
				queue.push(nodes[inputNodeIndex]);
			}
		}
	}
        // 计数从下到上的,这里要转换成从上到下
	for(var i = 0; i < nodes.length; i++){
		nodes[i].layer = maxLayer + 1 - nodes[i].layer;
	}

}

修正结果集

function fixLayer (nodesT, nodes){
	for(var i = 0; i < nodesT.length; i++){
		if(nodesT[i].inputNode.length === 0){
			var minL = maxLayer;
			var minT = maxLayer;
			if(nodesT[i].outputNode && nodesT[i].outputNode.length > 0){
				for(var j = 0; j < nodesT[i].outputNode.length; j++){
					var inputNodeIndex = getNodeIndex(nodesT[i].outputNode[j], nodes);
					var inputNodeIndexT = getNodeIndex(nodesT[i].outputNode[j], nodesT);
					if(inputNodeIndex < 0) continue;
					if(inputNodeIndexT < 0) continue;
                    //  注意,矫正的结果不能该节点比它子节点的层级还要高,这里要和它子节点做一次比较
					minL = Math.min(minL, nodes[inputNodeIndex].layer - 1);
					minT = Math.min(minT, nodesT[inputNodeIndexT].layer - 1);
				}
			}
			nodesT[i].layer = Math.min(minL,minT);
		}
	}

}

总结

这里主要用到了BFS,如果很熟悉这个算法的话,还是很简单的,同时要观察一些实际情况,做一些优化即可!

本文由华为云发布。

有关从零开始画自己的DAG作业依赖图(二)--分层布局算法的更多相关文章

  1. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  4. ruby-on-rails - 在所有延迟的作业之前 Hook - 2

    是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述

  5. ruby - 有什么方法可以告诉 sidekiq 一项工作依赖于另一项工作 - 2

    有什么方法可以告诉sidekiq一项工作依赖于另一项工作,并且在后者完成之前无法开始? 最佳答案 仅使用Sidekiq;答案是否定的。正如DickieBoy所建议的那样,您应该能够在依赖作业完成时将其启动。像这样。#app/workers/hard_worker.rbclassHardWorkerincludeSidekiq::Workerdefperform()puts'Doinghardwork'LazyWorker.perform_async()endend#app/workers/lazy_worker.rbclassLaz

  6. ruby-on-rails - Ruby/Rails 中的夏令时开始和结束日期 - 2

    我正在开发一个Rails应用程序,我需要在其中找到给定特定偏移量或时区的夏令时开始和结束日期。我基本上在我的数据库中保存了从用户浏览器接收到的时区偏移量(“+3”,“-5”),我想在它出现时修改它由于夏令时的变化。我知道Time实例变量有dst?和isdst方法,如果存储在它们中的日期在夏令时与否。>Time.new.isdst=>true但是使用它来查找夏令时的开始和结束日期会占用太多资源,而且我还必须为我拥有的每个时区偏移量执行此操作。我想知道更好的方法。 最佳答案 好的,基于你所说的和@dhouty'sanswer:您希望能够

  7. ruby-on-rails - phusion passenger 和 ruby​​ 1.9.1 已经开始工作了吗? - 2

    我有一台生产机器和一台开发机器,都运行ubuntu8.10并且都运行最新的phusionpassenger。当我在osx上的本地开发机器上使用ruby​​1.9.1时,我想知道外面的人是否已经在使用带有ruby​​1.9.1甚至1.9.2的phusionpassenger?如果是这样,请告诉我们您的设置!此外,有没有办法在apache上使用phusionpassenger同时运行ruby​​1.8.7(ree)和1.9.1?感谢您的指点,我在任何地方都找不到任何提示... 最佳答案 是的,从某些2.2.x版本开始就正式支持它,我不记

  8. ruby - Rails 3 - 我可以将开始日期设置为 date_select 方法吗? - 2

    date_select方法只能设置:start_year,但我想设置开始日期(例如3个月前的日期)(但没有这样的选项)。那么,我可以将开始日期设置为date_select方法吗?或者,要制作这样的选择框,我应该使用select_tag和options_for_select吗?或者,有什么解决办法吗?谢谢, 最佳答案 有可能……例如:start_year–设置年份选择的开始年份。默认为Time.now.year-5参见thisresource. 关于ruby-Rails3-我可以将开始日期

  9. ruby - 从特定索引开始迭代数组 - 2

    我想从特定索引开始遍历数组。我该怎么做?myj.eachdo|temp|...end 最佳答案 执行以下操作:your_array[your_index..-1].eachdo|temp|###end 关于ruby-从特定索引开始迭代数组,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/44151758/

  10. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

随机推荐