有一些信息有助于理解插入排序。
首先,i
永远不要大于len(s)
。事实上,它也永远不等于它。所做的是range(1, len(s))
产生一个可以迭代的不可变序列。您将在以下语句下拥有什么(让我们假设len(s) = 3
):
for i in range(1, len(s)):
...
是这样的:
for i in (1, 2):
...
您可以验证自己:
>>> list(range(1, 3))
>>> [1, 2]
因此,您将迭代两次,第一次使用i = 1
,第二次使用i = 2
。
其次,它有助于提醒自己,您正在排序的列表本质上由两部分组成:已排序的部分 ( list[0:i-1]
) 和尚未排序的部分 ( [list[i:]
)。您试图在每次迭代中使用插入排序来实现的是找到一个将当前值放入排序列表的位置。我们很快就会谈到这一点。
第三,该算法可以被认为是完成两个截然不同的任务。第一个是遍历列表中的所有成员并确保列表已排序。这就是外循环关注的部分(当然,内循环参与实际检查,但它有助于简化)。另一种是为列表中的元素找到适当的位置,以便对列表进行排序。即,如果你有一个 list letters = ['a', 'b', 'd', 'c']
,如果要按升序对列表进行排序,'c'
显然应该去letters[2]
并且'd'
需要占据's 以前的位置。'c'
这就是内部while
循环所做的部分。
实际上,while 循环(和s[j+1] = val
语句)如何确保列表被排序是非常聪明的。首先,它确保我们没有过度扩张(j >= 0
条件)。也就是说,我们没有选择s[-1]
元素,在 Python 中它将是列表中的最后一个元素,而其他语言,如 Java,则ArrayIndexOutOfBounds
例外。然后,它会问:我持有的数字是否低于之前的数字?如果是这样,这意味着列表没有排序,我们需要将较大的第一个位置移到列表的末尾(如果你愿意的话,向右移动)。请注意,我们用来比较其他元素的元素保持不变。我们将它保存在val
变量中。因此,我们不必担心在移动值时通过覆盖它而意外丢失它。
现在,一旦我们将较大的值向右移动,我们就会递减j
并再次问同样的问题:at 的值是否s[j]
大于我们所拥有的值val
?我们继续这样做,直到我们找到低于我们所拥有的价值val
,或者我们达到s[0]
但没有找到更低的价值。这意味着我们持有的是列表中最小的数字。无论哪种方式,我们都会跳出while
循环并用我们拥有的值覆盖s[j+1]
,这样我们就不会失去val
.
要查看它在实践中的外观,请考虑一个列表:[2, 3, 4, 5, 1]
. 假设我们迭代直到达到 number 1
,此时我们进入 while 循环,因为5 > 1
。采取的步骤是:
2, 3, 4, 5, 1 # val = 1, s[j] = 5, j = 3 5 > 1
2, 3, 4, 5, 5 # val = 1, s[j] = 4, j = 2 4 > 1
2, 3, 4, 4, 5 # val = 1, s[j] = 5, j = 1 3 > 1
2, 3, 3, 4, 5 # val = 1, s[j] = 5, j = 0 2 > 1
2, 2, 3, 4, 5 # val = 1, s[j] = 5, j = -1 break out of while
1, 2, 3, 4, 5 # val = 1, s[j] = 5, j = -1 put val into s[0]
基本上就是这样。遍历循环,检查当前值之前的值是否低于我们所持有的值,如果不是,则将这些值向右移动以为我们的值腾出空间。你有它 - 插入排序。
编辑:如果您仍然很难了解它是如何工作的,那么有一个非常好的代码审查可视化解释。