【问题标题】:My implementation of merging two sorted lists in linear time - what could be improved?我在线性时间内合并两个排序列表的实现 - 有什么可以改进的?
【发布时间】:2011-05-09 13:30:55
【问题描述】:

来自 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的解决方案了吗?还是因为算法的性质?

【问题讨论】:

  • 我明白这是一个“问题”,您应该以某种方式解决它。然而,事实上,如果这是一个“真正的”任务,我只会使用 extend() 和 sort()。确实会是 O(NlgN) 而不是 O(N),但是在 python 中不进行逐项操作所获得的速度优势是相当惊人的。
  • Python 的排序也针对天真的排序不擅长的许多常见情况进行了很好的优化:您可能会得到比这更好的结果。 (我不知道它是否专门针对“对两个已排序的附加列表进行排序”进行了优化。)
  • 顺便说一句,当您将此类非答案标记为解决方案时,我不会再花时间详细回答您的问题。它甚至不是一个答案(指向没有原始内容的其他答案的链接属于评论)——他链接的解决方案中没有任何内容实际上显示了如何在 Python 中以一种干净、通用的方式执行上述算法。
  • +1 表示问题 -10 表示所选答案以某种方式计算为 -1。去算数学。
  • @Glenn Maynard 如果我当时有代表,我会发表评论。另外,请阅读他的问题,他不是在要求某人为他做这件事,而是在询问有关此问题的一般性问题。所以我会坚持我所说的,即[所选答案] (stackoverflow.com/questions/464342/…) 我链接到的帖子回答了他的问题。它没有在代码中这样做,但它确实回答了它。

标签: python algorithm


【解决方案1】:

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

编辑:

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

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

【讨论】:

  • 没错,除非你能证明这很重要,否则不要为这些小事操心。
  • @Ranieri:当你学习的时候,最重要的就是让小东西出汗。组合和排序将教你一些关于标准库的知识,但几乎没有关于语言的知识。如果有人要求学习实现一个链表,告诉他们“别担心,用std::list”对他们没有任何好处。
  • @Glenn Maynard 我认为拉涅利明白这一点。看看他对原始问题的评论。不同的人展示不同的治疗方法并讨论差异是非常有帮助的。我链接到的帖子很棒,这里也有一些有趣的帖子。虽然有一个非常好的,但我想,我再也看不到了。奇怪。
  • 根据注释,引用:# 注意:上面的解决方案有点可爱,但不幸的是 list.pop(0) # 在标准 python 列表实现中不是恒定时间,所以 # 上面不是严格的线性时间。 # 另一种方法是使用 pop(-1) 从每个列表中删除最后的元素,构建一个向后的解决方案列表。 # 然后使用 reversed() 将结果以正确的顺序放回原处。 # 解决方案在线性时间内有效,但更丑陋。
【解决方案2】:

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

如果传递了任何迭代器,而不仅仅是列表,这将有效。

这种方法还通过了一项更有用的测试:它在通过无限或接近无限的迭代器时表现良好,例如。 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)

【讨论】:

  • +1。比我的好多了。避免索引和创建不必要的第三个列表。
  • +1 算法适用于任何可迭代对象,包括磁带驱动器,这是我第一次使用它的方法 :-)
  • 不处理 list1=[] 或 list2=[] 的情况,返回一个空列表,但是在这种情况下当然不需要进行排序...
【解决方案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)]

【讨论】:

    【解决方案4】:

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

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

    pythons sorted 函数不是线性的吗?

    【讨论】:

    • 我刚刚发现 pythons sorting algo 实际上是 nlog(n) 。所以这行不通
    【解决方案5】:

    这是我在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】:

      另一个生成器:

      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】:

        我同意其他答案,即扩展和排序是最直接的方法,但是如果您必须合并,这会更快一些,因为它不会在每次迭代时对 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

        【讨论】:

        • EAFP 发挥到了极致——我喜欢它。
        • 更好的是(1)把while True:放在try:里面(2)最后,避免复制:不要return merged_list + twiddly_bits,做merged_list.extend(twiddly_bits); return merged_list
        • @John Machin 谢谢。 (2) 是半明显的。我不得不考虑 (1),然后意识到我正在为每次迭代设置一个 try。我不会再错过那个了。再次感谢。
        【解决方案8】:

        根据这里的注释:

        # 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

        append 是 O(1),reverse 是 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
        

        【讨论】:

          【解决方案9】:

          此解决方案在线性时间内运行,无需编辑 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:]
          

          【讨论】:

            猜你喜欢
            • 2023-03-12
            • 2021-08-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-30
            • 2018-07-14
            • 1970-01-01
            • 2021-12-04
            相关资源
            最近更新 更多