我正在阅读一些关于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
答案 0 :(得分:12)
让我试着打破这个。
首先考虑一个清单。它“几乎”排序。也就是说,前几个元素是排序的,但最后一个元素没有排序。所以它看起来像这样:
[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] #ERROR !!
此时发生了一些不好的事情。我们试着检查是否关键&lt; mylist [i-1]但是当我们到达开头时,i = 0,这将检查列表的结尾。但是我们现在应该停止向左移动......
如果我们到达列表的开头,我们就无法进一步移动数据/键,所以我们应该停止。我们更新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, 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
答案 1 :(得分:6)
插入排序算法的工作原理是尝试在数组的开头建立一个增加长度的排序列表。我们的想法是,如果你从开头构建一个单元素的排序列表开始,然后是一个双元素列表,然后是一个三元素列表等,一旦你构建了一个n元素排序列表,你已经对整个数组进行了排序并完成了。
例如,给定数组
3 1 4
我们可以将它拆分为零元素排序列表和三元素未排序列表:
| 3 1 4
现在,我们在排序列表中添加3。由于该列表现在只有一个元素长,因此会自动排序:
3 | 1 4
现在,我们要在排序列表中添加1。如果我们只是在列表的末尾添加1,如下所示:
3 1 | 4
然后排序的列表不再排序。为了解决这个问题,插入排序代码的内部循环通过将1与之前的元素连续交换直到它处于正确的位置来工作。在我们的例子中,我们交换了1和3:
1 3 | 4
由于1现在位于数组的开头,因此我们不再需要移动它。这就是内循环在i > 0
时运行的原因;一旦新元素(i
)的索引位于数组的开头,那么它之前就没有任何东西可以更大了。
最后,我们通过在排序列表中添加4来更新数组。由于它处于排序位置,我们已经完成了:
1 3 4
我们的数组现在按顺序排列。
现在,问你原来的问题:为什么外循环从1开始?这是一个可爱的优化技巧。这个想法是任何单元素数组必须自动排序。这意味着算法可以从数组的第一个元素是单元素排序列表开始。例如,给定数组
2 7 1 8
插入排序算法可以尝试像这样分割这个数组,在前面放一个空的排序列表:
| 2 7 1 8
但稍微快一点的选择是将列表拆分为:
2 | 7 1 8
保证是安全的,因为任何单元素列表都会自动排序。
这实际上是作者对算法的优化。如果外部循环从零开始,该算法将完美地运行,但是他们只是决定在一个处启动它以避免不必要的循环迭代。
希望这有帮助!
答案 2 :(得分:2)
查看while
循环。它从i
开始,其值为1
,但随后i
减少。因此,在最后一行中,i
的最小值可以是0
,这是列表中的第一个元素。如果您从0
开始,i
将成为-1
,它在python中有效,但意味着最后一个元素。因此,范围必须以1
开头。
我想提一下,你要求插入排序。我不认为您的代码实现了插入排序。看起来更像是泡泡排序或类似的东西。
答案 3 :(得分:1)
原因是:
i = j
并且访问该mylist,如:
mylist[i - 1]
因此第一个值是0
。如果范围是从0
开始的,则会导致在-1
位置访问一个mylist。
答案 4 :(得分:1)
稍后设置i = j
,并且访问myList[i-1]
。因此,j
必须为j >= 1
。
已添加:设置j = 0
是逻辑上错误的,因为在循环myList[j-1]
被访问 - 这只是通过对代码进行静态分析(并且知道i = j) 。即使由于while i > 0
而在运行时期间不能发生这种情况,它至少也是毫无意义的。如果代码中出现myList[j-1]
表达式,那么肯定是j >= 1
。
答案 5 :(得分:1)
j-iteration将第j个元素插入到j之前的有序元素中。所以从j = 0开始没有意义。在j = 1的情况下,下面的子列表是myList[0:1]
,它是全部排序的,循环将myList[1]
插入子列表myList[0:2]
答案 6 :(得分:1)
查看动画的InsertionSort HERE