Performance 从头开始合并列表列表

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

我需要将已排序的列表合并到一个列表中(列表的数量可能会有所不同)。因为我刚接触Erlang,所以我不知道pretty函数
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个元素组成。然后:

  • 将输入列表拆分为两个子列表,每个子列表中包含m/2个列表
  • 将此算法递归地应用于它们中的每一个
  • 使用标准的2列表合并合并两个结果排序列表
  • 该算法的渐近复杂性实际上是O(n*m*ln(m)),因为: 1.拆分操作在每个拆分级别上都是O(m),因此可以忽略它。 2.合并操作在每个级别上都是O(m*n):在较高的(第一次拆分)级别上,我们需要合并两个n*m/2元素列表,每个元素都有O(n*m);在下一级(二次分割),我们需要进行两次独立的合并,每次合并两个n*m/4元素列表,也就是O(m*n),依此类推,直到m=2m=1 3.级别数显然是log2(m),因此产生的复杂性是O(n*m*ln(m))


    事实上,这个算法可以被认为只是稍微早一点“停止”分裂的一个变体(因此它有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]).