【问题标题】:Linear merging for lists in PythonPython中列表的线性合并
【发布时间】:2011-08-30 01:41:01
【问题描述】:

我正在处理Google's Python class exercises。其中一个练习是这样的:

给定两个按升序排序的列表,创建并返回按排序顺序排列的所有元素的合并列表。您可以修改传入的列表。理想情况下,解决方案应该在“线性”时间内工作,对两个列表进行一次遍历。

我想出的解决方案是:

    def linear_merge(list1, list2):
      list1.extend(list2) 
      return sorted(list1)

它通过了测试功能,但给出的解决方案是这样的:

    def linear_merge(list1, list2):
      result = []
      # Look at the two lists so long as both are non-empty.
      # Take whichever element [0] is smaller.
      while len(list1) and len(list2):
        if list1[0] < list2[0]:
          result.append(list1.pop(0))
         else:
          result.append(list2.pop(0))

      # Now tack on what's left
      result.extend(list1)
      result.extend(list2)
      return result

作为解决方案的一部分包括以下内容:

注意:上面的解决方案有点可爱,但不幸的是 list.pop(0) 是 标准 python 列表实现的时间不是恒定的,所以 以上不是严格的线性时间。另一种方法使用 pop(-1) 来 从每个列表中删除最末端的元素,构建一个解决方案列表 是倒退的。然后使用 reversed() 将结果放回正确的位置 命令。该解决方案在线性时间内有效,但更丑陋。

为什么这两种解决方案如此不同?是我遗漏了什么,还是它们过于复杂?

【问题讨论】:

    标签: python list sorting


    【解决方案1】:
    result = []
    while list1 and list2:
        result.append((list1 if list1[-1] > list2[-1] else list2).pop(-1))
    
    if len(list1):
        result += list1[-1::-1]
    if len(list2):
        result += list2[-1::-1]
    
    return result[-1::-1]
    

    @Abhijit 和 @intel 的解决方案并非在所有情况下都有效,因为它们没有反转原始列表的剩余部分。如果我们有list1 = [1, 2, 3, 5, 9, 11, 13, 17]list2 = [6, 7, 12, 15],那么他们的解决方案会给[5, 3, 2, 1, 6, 7, 9, 11, 12, 13, 15, 17] 我们想要[1, 2, 3, 5, 6, 7, 9, 11, 12, 13, 15, 17] 的地方。

    【讨论】:

      【解决方案2】:

      他们鼓励您考虑合并两个排序列表的实际方法(算法)。假设你有两摞纸,上面有名字,每摞按字母顺序排列,你想用它们做一个排序的摞。您不会只是将它们放在一起然后从头开始排序;那将是太多的工作。您可以利用每一堆已经排序的事实,因此您可以从一堆或另一堆中取出最先出现的一个,然后将它们放入新的堆栈中。

      【讨论】:

      • 好的,这是有道理的。我想他们会期望一个 n00b 想出那个似乎很奇怪。你能举个例子说明 result.extend(list1) result.extend(list2) 会实现什么吗?
      • pop 从列表中删除一个项目并返回它的值,因此您从每个列表中删除项目并将它们放入result。只要 both list1list2 包含任何内容,初始循环就会运行。当一个用完时,您只需将另一个留在其中的任何东西都粘贴到result 上;这就是extend 所做的。
      • 非常感谢,我想我现在明白了。
      • 很高兴我能帮上忙。我猜你是编程新手?这是一个很酷的世界,玩得开心!
      • 他们希望您作为 'a n00b' 提出这个想法,因为他们希望您能够思考并解决问题。如果你能发现pop(0) 不会在标准 Python 实现中持续运行,那么你肯定不缺乏思考能力。有了这种东西,试着从建模问题开始:拿一副牌,洗牌,把它切成两半,把每一半排序,然后把这两半放在一起排序——想想你是什么做。
      【解决方案3】:

      我最喜欢@Abhijit 方法。这是他的代码 sn-p 的一个稍微更 Pythonic/可读的版本:

      def linear_merge(list1, list2):
          result = []
      
          while list1 and list2:
              result.append((list1 if list1[-1] > list2[-1] else list2).pop(-1))
      
          return (result + list1 + list2)[-1::-1]
      

      借助内置的 python 功能,我们:

      1. 不需要明确检查列表是否为空 len 函数。
      2. 可以合并/追加空列表,结果将保持不变,因此无需显式检查。
      3. 我们可以组合多个语句(如果可读性允许),这有时会使代码更紧凑。

      【讨论】:

        【解决方案4】:

        如您所述,您的解决方案完美运行。那么为什么复杂呢?好吧,首先

        理想情况下,解决方案应该在“线性”时间内工作,制作单个 两个列表都通过。

        好吧,你没有明确地通过任何列表,但你正在调用sorted()。那么sorted() 会通过多少次列表呢?

        嗯,其实我也不知道。通常,排序算法的运行时间类似于 O(n*log(n)) 时间,但请查看 Python 文档中的以下引用:

        Python 中使用的 Timsort 算法可以有效地进行多种排序 因为它可以利用任何已经存在于 数据集。

        也许更了解 timsort 的人可以弄清楚。

        但他们在解决方案中所做的是利用他们知道他们有 2 个排序列表的事实。因此,他们不是从 sorted 的“零基础”开始,而是一个接一个地挑选元素。

        【讨论】:

          【解决方案5】:

          您的解决方案是 O(n log n),这意味着如果您的列表长 10 倍,则程序将花费(大约)30 倍的时间。他们的解决方案只需要 10 倍的时间。

          【讨论】:

          • 对于给定的示例(排序的子列表),我相信 Python 的 Timsort(类似于类似的合并排序)是 O(n)。
          • "如果您的列表是原来的 10 倍,那么程序将花费(大约)30 倍的时间。"不;随着列表大小无限制地增加,运行时间的比率将接近 10。您尝试使用的那种公式实际上只适用于 O(n^k) 类型的运行时间。
          【解决方案6】:

          从列表的末尾弹出,直到一个为空。我认为这是线性的,反向也是线性的。丑陋,但有解决办法。

          def linear_merge(list1, list2):
            # NOT return sorted (list1 + list2), as this is not linear
            list3 = []
            rem = []
            empty = False
          
            while not empty:
              # Get last items from each list, if they exist
              if len (list1) > 0:
                a = list1[-1]
              else:
                rem = list2[:]
                empty = True
          
              if len (list2) > 0:
                b = list2[-1]
              else:
                rem = list1[:]
                empty = True
          
              # Pop the one that's largest onto the new list
              if not empty:
                if a > b:
                  list3.append (a)
                  list1.pop ()
                else:
                  list3.append (b)
                  list2.pop ()
          
            # add the (reversed) remainder to the list
            rem.reverse ()
            list3 += rem
          
            # reverse the entire list
            list3.reverse ()
            return list3
          

          【讨论】:

            【解决方案7】:

            通过仍然丑陋的解决方案稍微改进(在 Python3.5 中):

            def linear_merge(list1: list, list2: list):
                result = []
            
                while len(list1) and len(list2):
                    result.append((list1 if list1[-1] > list2[-1] else list2).pop(-1))
            
                result += list1 if len(list1) else list2
            
                return result[-1::-1]
            

            【讨论】:

              【解决方案8】:
              def linear_merge(list1, list2):
                   a= list1 + list2
                   a.sort() 
                   return a
              

              【讨论】:

                猜你喜欢
                • 2016-06-23
                • 1970-01-01
                • 1970-01-01
                • 2016-08-13
                • 1970-01-01
                • 2010-11-11
                • 1970-01-01
                • 2017-09-05
                • 1970-01-01
                相关资源
                最近更新 更多