获得2个有序列表并集的最有效算法

时间:2013-03-18 06:16:15

标签: c++ algorithm list

我需要找到2个降序有序列表(list1和list2)的并集,其中是union  将是两个列表中的每个元素没有重复。假设列表元素是整数。我是 使用大O表示法来确定解决此问题的最有效算法。我知道的很重要  第一个的符号,但我不知道第二个的大O符号。有人可以告诉我 第二个算法的大O符号所以我可以决定实现哪种算法?如果有人知道 比其中一个更好的算法,你能帮我理解吗?提前谢谢。

Here are my two algorithms. . .

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Algorithm #1: O(N * log base2 N)

Starting at the first element of list1, 
while(list1 is not at the end of the list) {
    if(the current element in list1 is not in list2)    // Binary Search -> O(log base2 N)
        add the current element in list1 to list2
    go to the next element in list1 }

list2 is now the union of the 2 lists

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Algorithm #2: O(?)

Starting at the first elements of each list,
LOOP_START:
    compare the current elements of the lists
    whichever element is greater, put into a 3rd list called list3
    go to the next element in the list whose element was just inserted into list3
    branch to LOOP_START until either list1 or list2 are at the end of their respective list
insert the remaining elements from either list1 or list2 into list3 (the union)

list3 now contains the union of list1 and list2

8 个答案:

答案 0 :(得分:8)

第二个是O(n + m),而第一个是O(n log(m)+ m)。因此,第二个明显更好。

答案 1 :(得分:8)

这是我对情况的评估

  • 您的第一个算法在 n log n 时间运行:您正在对第一个列表中的每个元素进行二进制搜索,对吗?
  • 你的第二个算法并不完全:如果两个列表中的元素相等,你就不说该怎么做。但是,给定处理相等元素的正确逻辑,您的第二个算法就像merge sort的合并部分:它将以线性时间运行(即N)。从某种意义上说,它是最优的,你不能做得更好:你不能合并两个有序列表而不至少查看两个列表中的每个元素。

答案 2 :(得分:1)

使用以下算法,您可以将两个列表合并为O(n + m)。

[抱歉,为了简单起见,我使用了python,但每种语言的算法都相同]

请注意,该算法还会维护在结果列表中排序的项目。

def merge(list1, list2):
    result = []
    i1 = 0;
    i2 = 0;
    #iterate over the two lists
    while i1 < len(list1) and i2 < len(list2):
        #if the current items are equal, add just one and go to the next two items
        if list1[i1] == list2[i2]:
            result.append(list1[i1])
            i1 += 1
            i2 += 1
        #if the item of list1 is greater than the item of list2, add it and go to next item of list1
        elif list1[i1] > list2[i2]:
            result.append(list1[i1])
            i1 += 1
        #if the item of list2 is greater than the item of list1, add it and go to next item of list2
        else:
            result.append(list2[i2])
            i2 += 1
    #Add the remaining items of list1
    while i1 < len(list1):
        result.append(list1[i1])
        i1 += 1
    #Add the remaining items of list2
    while i2 < len(list2):
        result.append(list2[i2])
        i2 += 1
    return result

print merge([10,8,5,1],[12,11,7,5,2])

输出:

[12, 11, 10, 8, 7, 5, 2, 1]

答案 3 :(得分:0)

复杂性分析:

假设列表1的长度为N,列表2的长度为M

算法1:
听起来令人难以置信的风险,我会接受,据我所知这个算法的复杂性是N * M而不是NlogM

对于列表1 (O(N))中的每个元素,我们在列表2 (O(logM)中搜索它。这种算法的复杂性似乎是#39; O(NlogM)

但是,我们也在列表2中插入元素。应将此新元素插入适当的位置,以便列表2保持排序以进行进一步的二进制搜索操作。如果我们使用数组作为数据结构,那么插入将花费O(M)时间。

因此,算法的复杂度顺序为O(N*M)

可以进行修改,其中新元素插入列表2的末尾(列表不再有序),我们从索引0 to M-1执行二进制搜索操作而不是{ {1}}。在这种情况下,复杂性应为new size-1,因为我们将在O(N*logM)长度列表中执行N二进制搜索。

为了使列表再次排序,我们必须合并两个有序部分(0到M-1和M到newSize-1)。这可以在O(N + M)时间内完成(在合并类型的数组长度N + M中进行一次合并操作)。因此,该算法的净时间复杂度应为

M

空间复杂度O(NlogM + N + M) 不考虑原始列表空间,只考虑列表2中所需的额外空间。

算法2:
在每次迭代中,我们都在移动至少1指针前进。两个指针的总行程距离为O(max(N,M))。因此,最坏情况下的时间复杂度顺序为N + M,优于第一算法。

但是,这种情况下所需的空间复杂度更大(O(N+M))。

答案 4 :(得分:0)

这是另一种方法: 遍历两个列表,并将所有值插入集合中。 这将删除所有重复项,结果将是两个列表的并集。 两个重要的注意事项:你将失去数字的顺序。此外,它需要额外的空间。

时间复杂度:O(n + m)

空间复杂度:O(n + m)

如果您需要维护结果集的顺序,请使用LinkedHashMap的一些自定义版本。

答案 5 :(得分:0)

实际上,如果未对输入列表进行排序,则算法2不起作用。 要对数组进行排序,它的顺序为O(m * lg(m)+ n * lg(n))

您可以在第一个列表上构建哈希表,然后对于第二个列表中的每个项目,检查哈希表中是否存在此项目。这适用于O(m + n)。

答案 6 :(得分:0)

有一些事情需要指明:

  • 输入列表是否包含重复项?
  • 必须订购结果吗?

我会假设,使用std::list,您可以便宜地插入头部或尾部。

假设List 1有N个元素,List 2有M个元素。


算法1

它迭代列表1中列表2中搜索它的每个项目。

假设可能存在重复并且必须对结果进行排序,则搜索的最坏情况时间是列表2中没有列表1中的元素,因此它至少是:

  • O(N×M)。

要在正确的位置插入List 1的项目,您需要再次迭代List 2直到插入点。更糟糕的情况是列表1中的每个项目较小(如果从头开始搜索列表2)或更大(如果从末尾搜索列表2)。由于列表1的前一项已插入列表2中,因此第一项将有M次迭代,第二项将为M + 1,第三项将为M + 2,等等。对于最后一项,将进行M + N - 1次迭代。 item,平均每件M +(N - 1)/ 2。

类似的东西:

  • N×(M +(N-1)/ 2)

对于big-O表示法,常数因素无关紧要,所以:

  • N×(M +(N-1))

对于big-O表示法,非变量加法无关紧要,因此:

  • O(N×(M + N))

添加到原始O(N×M):

  • O(N×M)+ O(N×(M + N))
  • O(N×M)+ O(N×M + N 2

第二个等式只是为了使常数因子消除明显,例如2×(N×M),因此:

  • O(N×(M + N))
  • O(N 2 + N×M)

这两个是相同的,你最喜欢的。

可能的优化:

  • 如果不必排序结果,插入可以是O(1),因此情况更糟:

    • O(N×M)

  • 不要仅仅通过相等性测试列表2中的每个列表1项目,如果每个项目通过例如测试大于,当列表1的项目大于列表2的项目时,您可以在列表2中停止搜索;这不会减少更糟糕的情况,但会减少平均情况
  • 保持列表2迭代器指向列表1项的位置大于列表2的项目,以使排序插入O(1);在插入时确保保持从插入项开始的迭代器,因为虽然列表1是有序的,但它可能包含重复项;有了这两个,情况就会变得更糟:

    • O(N×M)

  • 对于下一次迭代,使用我们保留的迭代器在List 2的其余部分中搜索List 1的项目;这减少了更糟糕的情况,因为如果你到达列表2的末尾,你将只是“删除”列表1中的重复项;有了这三个,情况就会变得更糟:

    • O(N + M)

到目前为止,此算法与算法2之间的唯一区别是列表2已更改为包含结果,而不是创建新列表。


算法2

这是合并排序的合并。

您将遍历List 1的每个元素和List 2的每个元素一次,并且总是在列表的头部或尾部进行插入,因此更糟糕的情况是:

  • O(N + M)

如果有重复项,则会被丢弃。结果更容易订购而不是。


最终笔记

如果没有重复项,则可以在两种情况下优化插入。例如,对于双向链表,我们可以轻松检查列表1中的最后一个元素是否大于列表2中的第一个元素,反之亦然,并简单地连接列表。

这可以进一步推广到List 1和List 2的任何尾部。例如,在算法1中,如果在List 2中找不到List 1的项,我们可以连接List 2和List 1的尾部。算法2,这是在最后一步完成的。

更糟糕的情况是,当列表1的项目和列表2的项目交错时,不会减少,但是平均情况会再次降低,并且在很多情况下,这是一个在Real Life™中产生重大影响的重要因素。

我忽略了:

  • 分配时间
  • 算法中的空间差异
  • 二进制搜索,因为您提到了列表,而不是数组或树

我希望我没有犯任何明显的错误。

答案 7 :(得分:0)

我在之前的一个项目中实现了基于typescript(js)的2个对象数组的Union操作实现。数据太大,默认库函数如下划线或lodash并不乐观。经过一些大脑狩猎,我想出了以下基于二进制搜索的算法。希望它可以帮助某人进行性能调整。

就复杂性而言,该算法基于二进制搜索,最终将为O(log(N))。

基本上,代码需要两个无序的对象数组和一个要比较的键名,并且: 1)对数组进行排序 2)遍历第一个数组的每个元素并在第二个数组中删除它 3)将得到的第二个数组连接成第一个数组。

    private sortArrays = (arr1: Array<Object>, arr2: Array<Object>, propertyName: string): void => {
        function comparer(a, b) {
            if (a[propertyName] < b[propertyName])
                return -1;
            if (a[propertyName] > b[propertyName])
                return 1;
            return 0;
        }

        arr1.sort(comparer);
        arr2.sort(comparer);
    }

    private difference = (arr1: Array<Object>, arr2: Array<Object>, propertyName: string): Array<Object> => {

        this.sortArrays(arr1, arr2, propertyName);

        var self = this;

        for (var i = 0; i < arr1.length; i++) {
            var obj = {
                loc: 0
            };
            if (this.OptimisedBinarySearch(arr2, arr2.length, obj, arr1[i], propertyName))
                arr2.splice(obj.loc, 1);
        }

        return arr2;
    }

    private OptimisedBinarySearch = (arr, size, obj, val, propertyName): boolean => {
        var first, mid, last;
        var count;

        first = 0;
        last = size - 1;
        count = 0;

        if (!arr.length)
            return false;
        while (arr[first][propertyName] <= val[propertyName] && val[propertyName] <= arr[last][propertyName]) {
            mid = first + Math.floor((last - first) / 2);

            if (val[propertyName] == arr[mid][propertyName]) {
                obj.loc = mid;
                return true;
            }
            else if (val[propertyName] < arr[mid][propertyName])
                last = mid - 1;
            else
                first = mid + 1;
        }
        return false;
    }

    private UnionAll = (arr1, arr2, propertyName): Array<Object> => {
        return arr1.concat(this.difference(arr1, arr2, propertyName));
    } 

    //example
    var YourFirstArray = [{x:1},{x:2},{x:3}]
    var YourSecondArray= [{x:0},{x:1},{x:2},{x:3},{x:4},{x:5}]
    var keyName = "x";
    this.UnionAll(YourFirstArray, YourSecondArray, keyName)