完全披露:我将此作为hackerrank.com challenge的一部分撰写。
作为我的算法的一部分,我必须获取对的列表并生成集合列表。 每对代表两个节点之间的链接,每个Set代表彼此链接的所有节点(直接和间接)。
这些对是列表的形式,因为这是它们的生成方式,我不想浪费时间将它们重新装入元组中。此外,无可辩驳的模式消除了大部分开销。
import Data.List
import Data.Maybe
import qualified Data.IntSet as IS (fromList)
mkgroups :: [[Int]] -> [IntSet]
mkgroups = foldl' add2gc []
where
-- if a group is not found, create a new group
add2gc [] is = [IS.fromList is]
-- for the current group,
add2gc (g:gc) ~is@[i1, i2]
-- if either mates in group, add to group.
| i1 `IS.member` g = merge2gc i2 g gc
| i2 `IS.member` g = merge2gc i1 g gc
-- otherwise try next group
| otherwise = g : add2gc gc is
-- merge other inmate to appropriate group
merge2gc i g gc
-- in original group, return original group
| i `IS.member` g = g:gc
| otherwise = case part (IS.member i) gc of
-- in any other group, merge that group with this.
(Just g',gs) -> (IS.union g g') : gs
-- otherwise add innmate to this group
_ -> (IS.insert i g):gc
我需要一个能够返回第一个匹配项和所有非匹配实体列表的函数,而不是partition
。使用此函数而不是分区将算法从O(n ^ 2)更接近O(n * log n)分摊(并使我的运行时间降低15%)
part p as = go as []
where
go [] ps = (Nothing, ps)
go (a:as) ps = if p a
then (Just a, ps ++ as)
else go as (a:ps)
我还尝试为作业选择最佳的数据结构。最初这些组是[[Int]]
并且实际上工作得很好(因为移动到[IntSet]
仅使运行时间提高了30%)。
尽管如此,我还是需要更快。我的最后一次测试是在8秒时超时,我无法看到测试背后的输入数据。它可能会遇到最坏情况的路径,甚至只是一大堆数据。我已经尝试了我所知道的所有事情来加快速度,即使使用我的知识,内部输入列表中总会有2个元素使一个匹配无可辩驳。目前,我正在尝试使用分析数据重建我的本地GHC安装和软件包,以尝试深入分析代码。
SO Haskell大师有什么东西可以看到或想到我还没有尝试过吗?
编辑:我认为我目前拥有的算法是对我所考虑的目标的合理解释,尽管它在设置插入时遭受了大量复制。因此,感谢@fizruk等。 al。,我使用标准库中的Data.Graph
重新实现。
import Data.Tree as T
import Data.Graph as G
mkgroups :: Int -> [(Int,Int)] -> Forest Vertex
mkgroups n = filter ((>1) . length . T.flatten) . G.components . G.buildG (0,n)
这很好地完成了工作,并且运行得更快(大约4倍)。感谢所有帮助过的人。我决定给@ phil_20686答案,因为在阅读了图书馆资料后,他的回答似乎与图书馆实际上的答案最接近。
答案 0 :(得分:2)
union-find算法被设计为在一个密切相关的问题上快速血腥。 Hackage search表明有几个包实现了这个算法,你可能有兴趣检查一下。但是,正如下面的评论所讨论的那样,措辞密切相关"在这种情况下,实际上是负载处理:union-find解决了一个稍微困难的问题,因此实际上并不是最佳的。也就是说,如果图中有两个节点,联合查找结构将能够最快地告诉您它们是否属于同一个连接组件。
答案 1 :(得分:1)
构建适当的数据结构后,您可以使用广度优先或深度优先搜索在线性时间内解决此问题。
然而,在时间或内存中构建数据结构不一定是平凡的。例如。你可以将ever元素放入一对多图(一个用于传入,一个用于传出)。然后,只需转到相应的键,然后获取所有连接的节点,就可以轻松实现BFS。如果您将其从地图中删除,则可以确保您不会重复自己。
由于地图以值(节点)的数量在线性时间内构建,并且BFS在边数上是线性的,因此性能将为O(E + V)。