我在线性时间内合并两个排序列表的实现 - 可以改进什么?

时间:2010-11-13 15:23:47

标签: python algorithm

Fromg Google的Python类:

E. Given two lists sorted in increasing order, create and return a merged
list of all the elements in sorted order. You may modify the passed in lists.
Ideally, the solution should work in "linear" time, making a single
pass of both lists.

这是我的解决方案:

def linear_merge(list1, list2):
  merged_list = []
  i = 0
  j = 0

  while True:
    if i == len(list1):
        return merged_list + list2[j:]
    if j == len(list2):
        return merged_list + list1[i:]

    if list1[i] <= list2[j]:
        merged_list.append(list1[i])
        i += 1
    else:
        merged_list.append(list2[j])
        j += 1

首先,可以在这里使用无限循环吗?当我完成合并列表时,我是否应该使用break关键字中断循环,或者返回是否可以在这里?

我在这里看到过类似的问题,并且所有的解决方案看起来与我的非常相似,即非常类似于C。有没有更像python的解决方案?或者这是因为算法的性质?

9 个答案:

答案 0 :(得分:10)

This question比您可能需要的更详细地介绍了这一点。 ;)所选答案符合您的要求。如果我自己需要这样做,我会按照他或她的回答中描述的方式(将列表添加到一起,对新列表进行排序)来实现,因为它非常简单。

修改

我在下面添加了一个实现。我实际上在另一个答案中看到了这个似乎已被删除。我只是希望它不会被删除,因为它有一个我没有抓到的错误。 ;)

def mergeSortedLists(a, b):
    l = []
    while a and b:
        if a[0] < b[0]:
            l.append(a.pop(0))
        else:
            l.append(b.pop(0))
    return l + a + b

答案 1 :(得分:10)

这是一种发电机方法。您可能已经注意到,很多这些“生成列表”可以作为生成器函数完成。它们非常有用:它们不需要您在使用数据之前生成整个列表,将整个列表保存在内存中,您可以使用它们直接生成许多数据类型,而不仅仅是列表。

如果传递任何迭代器,而不仅仅是列表。

这种方法也通过了一个更有用的测试:它在传递无限或接近无限的迭代器时表现良好,例如。 linear_merge(xrange(10**9), xrange(10**9))

这两种情况下的冗余可能会减少,如果您想支持合并两个以上的列表,这将非常有用,但为了清楚起见,我没有在此处这样做。

def linear_merge(list1, list2):
    """
    >>> a = [1, 3, 5, 7]
    >>> b = [2, 4, 6, 8]
    >>> [i for i in linear_merge(a, b)]
    [1, 2, 3, 4, 5, 6, 7, 8]
    >>> [i for i in linear_merge(b, a)]
    [1, 2, 3, 4, 5, 6, 7, 8]
    >>> a = [1, 2, 2, 3]
    >>> b = [2, 2, 4, 4]
    >>> [i for i in linear_merge(a, b)]
    [1, 2, 2, 2, 2, 3, 4, 4]
    """
    list1 = iter(list1)
    list2 = iter(list2)

    value1 = next(list1)
    value2 = next(list2)

    # We'll normally exit this loop from a next() call raising StopIteration, which is
    # how a generator function exits anyway.
    while True:
        if value1 <= value2:
            # Yield the lower value.
            yield value1
            try:
                # Grab the next value from list1.
                value1 = next(list1)
            except StopIteration:
                # list1 is empty.  Yield the last value we received from list2, then
                # yield the rest of list2.
                yield value2
                while True:
                    yield next(list2)
        else:
            yield value2
            try:
                value2 = next(list2)

            except StopIteration:
                # list2 is empty.
                yield value1
                while True:
                    yield next(list1)

答案 2 :(得分:3)

为什么要停在两个名单上?

这是我的基于生成器的实现,它以线性时间合并任意数量的已排序迭代器。

我不确定为什么这样的东西不在itertools中......

def merge(*sortedlists):

    # Create a list of tuples containing each iterator and its first value
    iterlist = [[i,i.next()] for i in [iter(j) for j in sortedlists]]

    # Perform an initial sort of each iterator's first value
    iterlist.sort(key=lambda x: x[1])

    # Helper function to move the larger first item to its proper position
    def reorder(iterlist, i): 
        if i == len(iterlist) or iterlist[0][1] < iterlist[i][1]:
            iterlist.insert(i-1,iterlist.pop(0))
        else:
            reorder(iterlist,i+1)

    while True:
        if len(iterlist):
            # Reorder the list if the 1st element has grown larger than the 2nd
            if len(iterlist) > 1 and iterlist[0][1] > iterlist[1][1]:
                reorder(iterlist, 1)

            yield iterlist[0][1]

            # try to pull the next value from the current iterator
            try:
                iterlist[0][1] = iterlist[0][0].next()
            except StopIteration:
                del iterlist[0]
        else:
            break

以下是一个例子:

x = [1,10,20,33,99]
y = [3,11,20,99,1001]
z = [3,5,7,70,1002]

[i for i in merge(x,y,z)]

答案 3 :(得分:2)

嗨,我刚做了这个练习,我想知道为什么不用,

def linear_merge(list1, list2):
  return sorted(list1 + list2)

pythons排序函数是线性的不是吗?

答案 4 :(得分:1)

我同意其他答案,扩展和排序是最简单的方法,但是如果你必须合并,这会更快一点,因为它不会在每次迭代时调用len,也不会执行边界检查。 Python模式,如果你可以这样称呼的话,就是避免测试一个罕见的情况并改为捕获异常。

def linear_merge(list1, list2):
    merged_list = []
    i = 0
    j = 0

    try:
        while True:
            if list1[i] <= list2[j]:
                merged_list.append(list1[i])
                i += 1
            else:
                merged_list.append(list2[j])
                j += 1
    except IndexError:
        if i == len(list1):
            merged_list.extend(list2[j:])
        if j == len(list2):
            merged_list.extend(list1[i:])
    return merged_list

修改 根据John Machin的评论进行了优化。在try之外移动while True并在例外情况下移动merged_list

答案 5 :(得分:1)

以下是previous question的实施方式:

def merge(*args):
    import copy
    def merge_lists(left, right):
        result = []
        while (len(left) and len(right)):
            which_list = (left if left[0] <= right[0] else right)
            result.append(which_list.pop(0))
        return result + left + right
    lists = [arg for arg in args]
    while len(lists) > 1:
        left, right = copy.copy(lists.pop(0)), copy.copy(lists.pop(0))
        result = merge_lists(left, right)
        lists.append(result)
    return lists.pop(0)

答案 6 :(得分:1)

另一个发电机:

def merge(xs, ys):
    xs = iter(xs)
    ys = iter(ys)
    try:
        y = next(ys)
    except StopIteration:
        for x in xs:
            yield x
        raise StopIteration
    while True:
        for x in xs:
            if x > y:
                yield y
                break
            yield x
        else:
            yield y
            for y in ys:
                yield y
            break
        xs, ys, y = ys, xs, x

答案 7 :(得分:0)

根据这里的说明:

# Note: the solution above is kind of cute, but unforunately list.pop(0)
# is not constant time with the standard python list implementation, so
# the above is not strictly linear time.
# An alternate approach uses pop(-1) to remove the endmost elements
# from each list, building a solution list which is backwards.
# Then use reversed() to put the result back in the correct order. That
# solution works in linear time, but is more ugly.    

和此链接http://www.ics.uci.edu/~pattis/ICS-33/lectures/complexitypython.txt

追加是O(1),反向是O(n)但是它也说pop是O(n)所以哪个是哪个?无论如何,我已经修改了使用pop(-1)的接受答案:

def linear_merge(list1, list2):
    # +++your code here+++
    ret = []
    while list1 and list2:
        if list1[-1] > list2[-1]:
            ret.append(list1.pop(-1))
        else:
            ret.append(list2.pop(-1))

    ret.reverse()

    return list1 + list2 + ret

答案 8 :(得分:0)

此解决方案以线性时间运行,无需编辑l1和l2:

def merge(l1, l2):
  m, m2 = len(l1), len(l2)
  newList = []
  l, r = 0, 0
  while l < m and r < m2:
    if l1[l] < l2[r]:
      newList.append(l1[l])
      l += 1
    else:
      newList.append(l2[r])
      r += 1
  return newList + l1[l:] + l2[r:]