【问题标题】:Trying to understand insertion sort algorithm试图理解插入排序算法
【发布时间】:2023-03-07 14:00:01
【问题描述】:

我正在阅读一些关于 Python、数据结构以及算法分析和设计的书籍。我想真正了解编码的来龙去脉,成为一名高效的程序员。很难要求这本书澄清,因此我对stackoverflow的问题。我真的发现算法和递归具有挑战性......我在下面发布了一些代码(插入排序),我试图准确地理解正在发生的事情。一般来说,我了解应该发生什么,但我并没有真正了解如何以及为什么。

通过尝试分析 Python Idle 上的代码片段,我知道:

key (holds variables) = 8, 2, 4, 9, 3, 6

还有:

i (holds the length) = 7 ( 1, 2, 3, 4, 5, 6, 7)

我不知道为什么在第一行使用 1:range(1, len(mylist))。任何帮助表示赞赏。

mylist = [8, 2, 4, 9, 3, 6]

for j in range(1,len(mylist)):
    key = mylist[j]
    i = j
    while i > 0 and mylist[i-1] > key:
        mylist[i] = mylist[i - 1]
        i -= 1
        mylist[i] = key

【问题讨论】:

  • 维基百科文章on Insertion Sort 可能会阐明一些概念点。
  • 你可以在这里观看如何做到这一点:ocw.mit.edu/courses/electrical-engineering-and-computer-science/…(可能想跳过介绍到大约 28:00)
  • 这是一个错误的缩进插入排序。最后一条语句必须在 while 循环之外。
  • @rocksportrocker,这种方式仍然有效。在所有情况下,除了最后一种情况,值最终都会被下一次迭代覆盖。
  • 它仍然有效,但它不是插入排序......至少它是一个比它可能的慢的实现。

标签: python algorithm sorting insertion-sort


【解决方案1】:

让我试着分解一下。

首先考虑一个列表。它是“几乎”排序的。也就是说,前几个元素是排序的,但最后一个元素是不排序的。所以它看起来像这样:

[10, 20, 30, 50, 15]

显然,15 放错了位置。那么我们如何移动它呢?

    key = mylist[4]
    mylist[4] = mylist[3]
    mylist[3] = key

这将在 15 和 50 之间切换,所以现在列表如下所示:

[10, 20, 30, 15, 50]

但我们希望循环执行几次。为此,我们可以这样做:

while ???:
    key = mylist[i]
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

该循环将一次返回一个位置,交换两个元素。每次都会将乱序位置移回一个位置。但是我们怎么知道什么时候停止呢?

让我们再看看我们的清单和我们想要采取的行动:

[10, 20, 30, 50, 15]
[10, 20, 30, 15, 50]
[10, 20, 15, 30, 50]
[10, 15, 20, 30, 50]
# stop! we are sorted now!

但是和上次有什么不同呢?每次我们将第一个位置向后移动,都是因为 15 小于左边的元素,这意味着它没有排序。当这不再正确时,我们应该停止移动。但我们可以轻松应对:

key = mylist[i]
while key < mylist[i-1]:
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

好的,但是如果我们现在尝试对这个列表进行排序,就会发生这种情况:

[10, 20, 1] [10, 1, 20] [1, 10, 20] # 错误!!

此时发生了一些不好的事情。我们尝试检查是否 key

如果我们到达列表的开头,我们就不能进一步移动我们的枢轴/键,所以我们应该停下来。我们更新了 while 循环来处理这个问题:

key = mylist[i]
while i > 0 and key < mylist[i-1]:
    mylist[i] = mylist[i-1]
    mylist[i-1] = key
    i -= 1

所以现在我们有了一种对几乎已排序的列表进行排序的技术。但是我们如何使用它来对整个列表进行排序呢?我们一次对列表的一部分进行排序。

 [8, 2, 4, 9, 3, 6]

首先我们对前1个元素进行排序:

 [8, 2, 4, 9, 3, 6]

然后我们对前2个元素进行排序:

 [2, 8, 4, 9, 3, 6]

然后我们对前3个元素进行排序

 [2, 4, 8, 9, 3, 6]

以此类推

 [2, 4, 8, 9, 3, 6]
 [2, 4, 8, 9, 3, 6]
 [2, 3, 4, 8, 9, 6]
 [2, 3, 4, 6, 8, 9]

但是我们该怎么做呢?带有for循环

for j in range(len(mylist)):
    i = j
    key = mylist[i]
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        mylist[i-1] = key
        i -= 1 

但是我们可以跳过第一次,因为一个元素的列表显然已经排序了。

for j in range(1, len(mylist)):
    i = j
    key = mylist[i]
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        mylist[i-1] = key
        i -= 1 

一些没有什么区别的小改动让我们回到你原来的代码

for j in range(1, len(mylist)):
    key = mylist[j]
    i = j
    while i > 0 and key < mylist[i-1]:
        mylist[i] = mylist[i-1]
        i -= 1 
        mylist[i] = key

【讨论】:

    【解决方案2】:

    插入排序算法的工作原理是尝试在数组的开头建立一个长度递增的排序列表。这个想法是,如果你从一开始构建一个单元素排序列表开始,然后是一个二元素列表,然后是一个三元素列表,等等,一旦你构建了一个 n 元素排序列表,您已经对整个数组进行了排序并完成了。

    例如,给定数组

    3  1  4
    

    我们可以将其拆分为一个零元素排序列表和一个三元素未排序列表:

    | 3  1  4
    

    现在,我们将 3 添加到排序列表中。由于该列表现在只有一个元素长,因此会自动排序:

    3 | 1  4
    

    现在,我们要将 1 添加到排序列表中。如果我们像这样在列表末尾添加 1:

    3 1 | 4
    

    然后排序列表不再排序。为了解决这个问题,插入排序代码的内部循环通过不断地将 1 与它之前的元素交换,直到它处于正确的位置。在我们的例子中,我们交换 1 和 3:

    1 3 | 4
    

    由于 1 现在位于数组的开头,我们不需要再移动它了。这就是为什么内部循环在 i &gt; 0 时运行;一旦新元素的索引 (i) 位于数组的开头,在它之前就没有任何东西可以更大了。

    最后,我们通过将 4 添加到排序列表来更新数组。既然它处于排序位置,我们就完成了:

    1 3 4
    

    我们的数组现在已经排好序了。

    现在,对于您最初的问题:为什么外循环从 1 开始?这是一个可爱的优化技巧。这个想法是任何单元素数组都必须自动排序。这意味着该算法可以从说数组的第一个元素是一个单元素排序列表开始。例如,给定数组

    2  7  1  8
    

    插入排序算法可以尝试像这样拆分这个数组,在前面放一个空的排序列表:

    | 2  7  1  8
    

    但一个稍微快一点的选择是像这样拆分列表:

    2 | 7  1  8
    

    保证安全,因为任何单元素列表都会自动排序。

    这实际上是作者对算法的优化。如果外循环从零开始,该算法将运行得非常好,但他们只是决定从一开始,以避免不必要的循环迭代。

    希望这会有所帮助!

    【讨论】:

    • 这帮助很大;你用外行的话说,即使是穷人,想成为像我这样的程序员也能理解。
    【解决方案3】:

    看看while 循环。它以i 开始,其值为1,但随后i 减少。所以在最后一行中,i 的最小值可能是0,它是列表中的第一个元素。如果您以0 开头,i 将变为-1,这在 python 中是有效的,但意味着最后一个元素。因此范围必须以1 开头。

    我想提一下,您要求进行插入排序。我并不认为您的代码实现了插入排序。看起来很像冒泡排序或类似的东西。

    【讨论】:

      【解决方案4】:

      原因是:

      i = j
      

      并且该 mylist 的访问方式如下:

      mylist[i - 1]
      

      因此第一个值为0。如果范围从0 开始,则会导致在位置-1 访问mylist。

      【讨论】:

      • 但是在代码中有一个检查,以确保在进行此访问之前i &gt; 0。短路检查应确保在超出范围时永远不会读取该值。
      【解决方案5】:

      查看动画插入排序HERE

      【讨论】:

        【解决方案6】:

        稍后设置i = j,并访问myList[i-1]。所以,j 必须是 j &gt;= 1

        添加:设置j = 0 逻辑错误,因为在循环中访问myList[j-1] - 这只是通过对代码进行静态分析(并且知道i = j)。即使这在运行时因为while i &gt; 0 而不会发生,但至少是没有意义的。如果表达式myList[j-1] 出现在代码中,那么它肯定是j &gt;= 1

        【讨论】:

        • 但是在代码中检查以确保在执行此访问之前 i > 0。短路检查应确保值超出范围时永远不会被读取。
        【解决方案7】:

        第 j 次迭代将第 j 个元素插入到 j 之前的已排序元素中。所以从 j=0 开始是没有意义的。在 j=1 的情况下,下面的子列表是 myList[0:1],它始终是排序的,循环将 myList[1] 插入子列表 myList[0:2]

        【讨论】:

          猜你喜欢
          • 2023-03-10
          • 1970-01-01
          • 1970-01-01
          • 2019-04-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-06-20
          相关资源
          最近更新 更多