【发布时间】:2016-03-06 17:07:56
【问题描述】:
因此,在 Hackerrank 的一个名为“无环图”的编程竞赛中出现了这个挑战,它基本上归结为计算“有向无环图”中每个节点可到达的节点数。例如,假设您有这样的图表:
[ 1 ] ---->[ 2 ]--->[ 4 ]--->[ 5 ]
[ 3 ] ------/
可达性计数(包括原始节点):
Node 1: 4
Node 2: 3
Node 3: 4
Node 4: 2
Node 5: 1
我的方法是带有记忆的“深度优先”遍历。环顾四周,但似乎运行时间无法进一步改善,因为在这样的情况下会发生过度计数:
[ 1 ] ---->[ 2 ]--->[ 4 ]--->[ 5 ]
[ 3 ] ------/--------/
第三个节点将计算第四个节点,即使第二个节点已经计算了第四个节点。更糟糕的是,我只在 JavaScript 中解决这些挑战。它是我的主要语言,我从突破它的界限中获得了快感。排行榜上还没有人用 JavaScript 解决过这个问题,但我认为这是可能的。比赛结束后,我成功通过了 24 个测试用例中的 13 个,代码如下:
function Solution( graph, nodes ) {
var memory = new Array( nodes + 1 )
, result = 0;
graph.forEach( ( a, v ) => DepthFirstSearch( graph, v, memory ) );
// challenge asks for an output variation, but the accurate
// reachability count of every node will be contained in "d.length".
memory.forEach( ( d, i ) => { if ( i && ( 2 * d.length ) >= nodes ) result++; } );
return result;
}
function DepthFirstSearch( graph, v, memory ) {
if ( memory[ v ] ) return memory[ v ];
var descendants = new Uint16Array( [ v ] );
graph[ v ].forEach( u => {
descendants = MergeTypedArrays(
DepthFirstSearch( graph, u, memory ),
descendants
);
} );
// make elements unique
// to avoid over counting
return memory[ v ] = Uint16Array.from( new Set( descendants ) );
}
function MergeTypedArrays(a, b) {
var c = new a.constructor( a.length + b.length );
c.set( a );
c.set( b, a.length );
return c;
}
// adjacency list
var graph = [
[], // 0
[ 2 ], // 1
[ 4 ], // 2
[ 2 ], // 3
[ 5 ], // 4
[] // 5
];
var nodes = 5;
Solution( graph, nodes );
所有大于 50kb 的输入都失败,可能是具有大量节点和边的输入(即 50,000 个节点和 40,000 个边)。由于无法识别或构思出更快、内存效率更高的算法,我完全不知道接下来要尝试什么。考虑过使 DFS 迭代,但我认为记忆数千个数组的内存消耗会相形见绌,这似乎是主要问题。对于 11 个失败的测试(与“超时”相反),我在 Hackerrank 上收到“调用中止”和“运行时错误”。还尝试了“bitSets”和“union”,但内存消耗变得更糟,因为 bitSets 数组需要足够大以存储最多 50,000 个数字。
约束:
1 ≤ n,m ≤ 5×10^4
1 ≤ a(i),b(i) ≤ n and a(i) ≠ b(i)
It is guaranteed that graph G does not contain cycles.
只是想明确一点,我不会因为通过所有测试而获得任何分数,因为这个挑战是锁定的,这是为了教育目的,主要是优化。我知道指向拓扑排序的相关SO帖子,但据我了解,拓扑排序仍然会过度依赖上述情况,因此不是一个可行的解决方案。如果我误解了,请赐教。提前感谢您的宝贵时间。
问题:如何进一步优化?有没有更有效的方法?
【问题讨论】:
-
你有更多关于测试用例的信息吗?他们有什么时间/记忆限制?老实说,50k 节点 40k 边是一个很小的图,即使是糟糕的实现也不应该有问题。
-
@MartinS 是的,JavaScript 最大执行时间为 10 秒,最大内存为 512mb。我只是在本地运行您的版本;堆栈溢出时的递归错误和迭代错误需要约 13 分钟 this 测试用例(第一个数字是节点数,旁边的数字是边数,这两个数字下方的行是实际的边缘)。挑战成功率为7.87%。我认为这比我们意识到的要多一些。我一直回到记忆,因为它做了很多重复的工作。
-
这些约束中的
a and b是什么1 ≤ n,m ≤ 5×10^41 ≤ a(i),b(i) ≤ n and a(i) ≠ b(i)? -
@MartinS 很抱歉没有指定,“a”和“b”指的是边缘。为了消除更多歧义,here 是挑战描述的屏幕截图。
-
谢谢你,这很有帮助:)
标签: javascript node.js algorithm graph