Performance 从头开始合并列表列表
我需要将已排序的列表合并到一个列表中(列表的数量可能会有所不同)。因为我刚接触Erlang,所以我不知道pretty函数Performance 从头开始合并列表列表,performance,algorithm,list,merge,erlang,Performance,Algorithm,List,Merge,Erlang,我需要将已排序的列表合并到一个列表中(列表的数量可能会有所不同)。因为我刚接触Erlang,所以我不知道pretty函数list:merge/1。所以我实现了自己的merge/1函数。它的复杂性是O(m*n)(m-列表的数量,n-列表中元素的平均数量),我使用尾部递归。以下是我的功能: -module( merge ). -export( [ merge/1 ] ). merge( ListOfLists ) -> merge( ListOfLists, [] ). m
list:merge/1
。所以我实现了自己的merge/1
函数。它的复杂性是O(m*n)(m-列表的数量,n-列表中元素的平均数量),我使用尾部递归。以下是我的功能:
-module( merge ).
-export( [ merge/1 ] ).
merge( ListOfLists ) ->
merge( ListOfLists, [] ).
merge( [], Merged ) ->
lists:reverse( Merged );
merge( ListOfLists, Merged ) ->
[ [ Hfirst | Tfirst ] | ListOfLists_Tail ] = ListOfLists,
% let's find list, which has minimal value of head
% result would be a tuple { ListWithMinimalHead, Remainder_ListOfLists }
{ [ Hmin | Tmin ], ListOfLists_WithoutMinimalHead } =
lists:foldl(
fun( [ Hi | Ti ] = IncomingList, { [ Hmin | Tmin ], Acc } ) ->
case Hi < Hmin of
true ->
% if incoming list has less value of head then swap it
{ [ Hi | Ti ], [ [ Hmin | Tmin ] | Acc ] };
false ->
{ [ Hmin | Tmin ], [ IncomingList | Acc ] }
end
end,
{ [ Hfirst | Tfirst ], [] },
ListOfLists_Tail ),
% add minimal-valued head to accumulator, and go to next iteration
case Tmin == [] of
true ->
merge( ListOfLists_WithoutMinimalHead, [ Hmin | Merged ] );
false ->
merge( [ Tmin | ListOfLists_WithoutMinimalHead ], [ Hmin | Merged ] )
end.
0.153秒给我留下了深刻的印象。vs 21.676秒。我的功能运行极慢
我认为使用匿名函数会降低性能,但是去掉fun
并没有帮助
你能告诉我,我犯的主要错误在哪里吗?或者为什么模块列表中的函数要快得多
谢谢不同之处在于算法的复杂性。 如果我没有弄错的话,您的算法是O(m^2*n),其中n是内部列表的长度,m是输入列表中内部列表的数量。 这是因为您的函数有效地遍历了内部列表的整个列表,以生成结果列表的一个元素。因此,对于您的测试示例,运行时间与C1*N^3成比例(在本例中,C1是某个常数<1) 然而,通常预排序列表的合并操作具有O(n)的复杂性,其中n是所有列表的总长度。因此,对于您的测试用例,复杂性应该是O(n*m),即它应该与C2*n^2成比例 事实上,正如您所看到的,当测试中的N增加10倍时,实现生成结果所需的时间将增加860倍,而“list:merge/1”合并输入所需的时间仅增加53倍。根据实际输入大小和“形状”,比率会有所不同,但总体趋势仍然是N^3 vs N^2 标准的“list:merge/1”并没有那么简单:(“merge/1”只是调用“mergel/1”),但事实上,即使是一个简单的、未优化的、非尾部递归的“just merge The head list with merged tail”也比您的实现好得多:
merge2([]) ->
[];
merge2([Ls|Lss]) ->
merge2(Ls,merge2(Lss), []).
merge2([], Ls, Acc) ->
lists:reverse(Acc) ++ Ls;
merge2(Ls, [], Acc) ->
lists:reverse(Acc) ++ Ls;
merge2([H1|Ls1], [H2|_] = Ls2, Acc) when H1 =< H2 ->
merge2(Ls1, Ls2, [H1|Acc]);
merge2(Ls1, [H2|Ls2], Acc) ->
merge2(Ls1, Ls2, [H2|Acc]).
merge2([])->
[];
合并2([Ls|Lss])->
合并2(Ls,合并2(Lss),[])。
合并2([],Ls,Acc)->
列表:反向(Acc)+Ls;
合并2(Ls,[],Acc)->
列表:反向(Acc)+Ls;
当H1=
合并2(Ls1,Ls2,[H1 | Acc]);
合并2(Ls1[H2|Ls2],Acc)->
合并2(Ls1,Ls2,[H2 | Acc])。
因此,正如实践中经常出现的情况:任何优化的第一步都是查看算法
UPD:事实上,我的例子也是O(m^2*n)——在复杂性方面并不比你的好。我们在这里可能需要的是“分而治之”的方法,它应该提高竞争力,使之达到O(m*n*ln(n))
UPD2:对先前更新的更正和澄清:
“分而治之”是指以下算法:
假设我们的输入列表中有m个排序列表,每个列表由n个元素组成。然后:
事实上,这个算法可以被认为只是稍微早一点“停止”分裂的一个变体(因此它有ln(m)而不是ln(m*n)),当n=1(而你的第一个算法实际上变成了)时,它就变成了完整的合并排序了。谢谢@Ed'ka!我理解你关于复杂性的建议。我还找到了Illustart的例子。将这样的列表:[[1,1,1…],[2,2,2…],[3,3,3…]与我的算法合并将非常无效。此外,我发现O(mnln(n))的实现很小
merge3(ListOfLists)->list:sort([X | | Lst Hmm…)或仅仅merge4(ListOfLists)->list:sort(list:append(ListOfLists))。
@stem基于排序的方法是O(mnln(mn)),它肯定比O(m^2*n)好得多,但比O(mn*ln(m))稍差。对于排序,我们只是忽略(不使用)事实上,输入列表已经被排序。是的,@Ed'ka,你是对的。另外,我认为,为了降低复杂性,“分而治之”合并的每一步都可以并行化。在Erlang上这样做非常简单。当然,需要考虑列表的大小(如果很小,则执行顺序算法)。
merge2([]) ->
[];
merge2([Ls|Lss]) ->
merge2(Ls,merge2(Lss), []).
merge2([], Ls, Acc) ->
lists:reverse(Acc) ++ Ls;
merge2(Ls, [], Acc) ->
lists:reverse(Acc) ++ Ls;
merge2([H1|Ls1], [H2|_] = Ls2, Acc) when H1 =< H2 ->
merge2(Ls1, Ls2, [H1|Acc]);
merge2(Ls1, [H2|Ls2], Acc) ->
merge2(Ls1, Ls2, [H2|Acc]).