【问题标题】:Bubble sorting with one list comprehension带有一个列表理解的冒泡排序
【发布时间】:2019-12-12 11:48:56
【问题描述】:

我想看看是否可以转换一个BubbleSort函数,例如:

def BubbleSort(l):
    for i in range(len(l)-1):
        for j in range(len(l)-1-i):
            if (l[j]>l[j+1]):
                l[j],l[j+1]=l[j+1],l[j]
    return l

到单行列表理解,可能类似于:

def BubbleSort(l):
    return [something_goes_here for i in range(len(l)-1) for j in range(len(l)-1-i) if (l[j]>l[j+1])]

示例输入:

print(BubbleSort([1,5,-5,0,10,100]))

样本输出

[-5, 0, 1, 5, 10, 100]

【问题讨论】:

  • 好吧,既然您使用 nested for 循环,您的列表将包含 n^2+n/2(平均而言,过滤将保留这些值的特定部分),因此它不会打印这样的列表(不幸的是 Python 并没有真正实现列表单子,因为这样会更容易)。
  • @ScotHunter:这是正确的,但如果我们像使用冒泡排序一样迭代列表,最终它会归结为“交换”的数量。最坏的情况,冒泡排序不仅需要 O(n^2) 比较,而且还需要 O(n^2) 交换(交换列表需要这样)。
  • 我们可以将选择排序转换为等效的“列表理解”,尽管这也会效率低下。
  • 我们还要强调一下,列表推导旨在创建新列表,这对他们来说不是一个好的用例,只是为了强调您可能已经知道的内容,但是有人带着新的一对来回答这个问题眼睛可能不会。
  • @ParitoshSingh:正确,它至少需要一些有副作用的东西。即使有可能,从软件设计的角度来看也不好。

标签: python python-3.x algorithm sorting bubble-sort


【解决方案1】:
def bubblesort(l):
    [[l.insert(j+1, l.pop(j)) for j in range(i) if l[j] > l[j+1]] for i in range(len(l)-1,0,-1)]
    return l

我使用l.insert(j+1, l.pop(j)) 来交换列表中的元素。其余的只是嵌套循环。

【讨论】:

    【解决方案2】:

    使用列表推导隐藏 for 循环是一种欺骗,因为推导生成的结果不是排序列表。但是,如果您要这样做,您可能希望通过在条件中执行交换而不是作为输出值来避免构建 None 元素列表。

    例如:

    a = [1, 3, 2, 6, 5, 4]
    [_ for n in range(len(a),1,-1) for i in range(n-1) if a[i]>a[i+1] and a.__setitem__(slice(i,i+2),a[i:i+2][::-1])]
    

    隔离元素交换部分,这将给出:

    swap = lambda(a,i):a.__setitem__(slice(i,i+2),a[i:i+2][::-1])
    [_ for n in range(len(a),1,-1) for i in range(n-1) if a[i]>a[i+1] and swap(a,i)]
    

    与以下没有什么不同:

    for n in range(len(a),1,-1):
        for i in range(n-1):
            if a[i]>a[i+1]: 
               swap(a,i)     # same as a[i],a[i+1] = a[i+1],a[i]
    

    列表推导式只是编写 for 循环的另一种方式,实际上并没有返回排序结果。

    更符合列表理解的精神是在不影响原始列表的情况下实际返回排序结果。您可以使用理解中的临时列表来执行此操作,以执行元素交换并逐步返回保证在正确排序索引处的位置:

    a = [1, 3, 2, 6, 5, 4]
    s = [ b.pop(-1) for b in [list(a)] for n in range(len(a),0,-1) if not [_ for i in range(n-1) if b[i]<b[i+1] and b.__setitem__(slice(i,i+2),b[i:i+2][::-1])] ]
    print(s) # [1, 2, 3, 4, 5, 6]  
    

    除了b在内部用于管理交换和返回排序值之外,该方法与以前相同。因为保证的排序位置始终是b 中的最后一个位置,所以交换条件被颠倒了,因此b 在内部按降序排序,在每次迭代中取最后一项时以升序生成输出。

    请注意,所有这些解决方案都未能实现提前退出条件,该条件允许冒泡排序在已经排序的列表和元素接近其排序位置的列表上非常有效(即,当没有交换时停止)。迭代次数将始终为 N*(N+1)/2,无论元素的原始顺序始终给出 O(N^2) 的时间复杂度,而不是最坏的情况。

    【讨论】:

      【解决方案3】:

      基于副作用的解决方案如下所示:

      def bubblesort(l):
          [l.append(l.pop(0) if i == len(l) - 1 or l[0] < l[1] else l.pop(1)) for j in range(0, len(l)) for i in range(0, len(l))]
          return l
      

      这将对列表l 进行就地排序。

      基本思想是将l 视为输入和输出列表。然后可以通过将l 的第一个或第二个元素移动到末尾来模拟交换。必须将最后一个元素移到末尾而不进行任何比较才能获得新的列表列表。一次迭代的可视化示例 ([l.append(l.pop(0) if i == len(l) - 1 or l[0] &lt; l[1] else l.pop(1)) for i in range(0, len(l))]):

      1 3 2 6 5 4 |
        3 2 6 5 4 | 1
          3 6 5 4 | 1 2
            6 5 4 | 1 2 3
              6 4 | 1 2 3 5
                6 | 1 2 3 5 4
                  | 1 2 3 5 4 6
      

      在此示例中,| 表示原始列表的最后一个元素与已附加的第一个元素之间的分隔符。重复此过程len(l) 次保证整个列表都已排序。

      请注意,虽然此示例确实执行了冒泡排序,但它的运行时间是 O(n^3),因为我们需要在每个步骤中从列表中删除第一个或第二个元素,它在 O(n) 中运行。

      编辑:
      更容易看出这实际上是上述算法中的冒泡排序,如果我们这样重写样本迭代:

      | 1 3 2 6 5 4
      1 | 3 2 6 5 4
      1 2 | 3 6 5 4
      1 2 3 | 6 5 4
      1 2 3 5 | 6 4
      1 2 3 5 4 | 6
      1 2 3 5 4 6 |
      

      这里的分隔符表示列表的结尾,并使用列表的圆形视图。

      编辑 2:
      找到了一种更有效的方法来解决这个问题,即使用切片分配:

      def bubblesort(l):
          [l.__setitem__(slice(i, i + 2), (l[i:i + 2] if l[i] < l[i + 1] else l[i +  1:i - 1:-1])) for j in range(0, len(l)) for i in range(0, len(l) - 1)]
          return l
      

      【讨论】:

      • 如果它的时间复杂度为 O(n^3),它如何仍然是冒泡排序?
      • @גלעדברקן 为什么不应该这样呢? n 的附加乘数来自底层数据结构,这是相当次优的,而不是实际算法。通过将单个数组访问的成本从O(1) 增加到O(n),您可以对任何其他算法执行几乎相同的操作。它仍然是相同的算法,但更昂贵。
      • @גלעדברקן 我添加了一个解释,以便更容易看出这实际上是冒泡排序
      猜你喜欢
      • 2018-04-13
      • 2021-06-22
      • 1970-01-01
      • 2017-03-19
      • 2016-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多