插入排序
python实现
代码来自于
https://gist.github.com/czheo/7421d305bb2e5d3049ce48545646d6f4
def insertion_sort(lst):
i = 1
# invarient:
# We keep the left part of the list sorted:
# [ ..sorted.. i ..unsorted.. ]
while i < len(lst):
# "target" is the item that we want to
# insert back to the left sorted part
# for example:
# [4, 5, 7, 9, 6, 3, 1, 2]
# ^
# |
# i (target)
target = lst[i]
# shift everything > target left by 1 offset
# for the same example
# [4, 5, x, 7, 9, 3, 1, 2]
# ^ ^ ^
# | | |
# j x i
j = i - 1
while j >= 0:
if lst[j] > target:
# shift everything > target left by 1 offset
lst[j+1] = lst[j]
j -= 1
else:
break
# save target back at the position x, which is j + 1
lst[j+1] = target
i += 1
# Now the list looks like:
# [4, 5, 6, 7, 9, 3, 1, 2]
# ^
# |
# next i
简化后
def insertion_sort(lst):
i = 1 #注1
while i < len(lst):
target = lst[i]
j = i - 1
while j >= 0:
if lst[j] > target:
lst[j+1] = lst[j]
j -= 1 #注2
else:
break #注3
lst[j+1] = target
i += 1 #注4
解读
注1:i从1开始而不是从0开始,这是因为一个数一定是有序的。把待排序的第一个数当做一个有序数组,然后将后面的数不断插入已经排列好的有序数组中形成新的有序数组。
注2:把坐标i个数去和坐标i前的数做比较(所以是j=i-1)。坐标i前的数已经是有序数组(因为i是从第二个数,坐标为1的数开始的,每完成一次,之前的数都形成有序数组)。找到有序数组中比它大的数,则把那个数赋值到下一位(如果坐标i-2的数比target大,则坐标i-1的数也一定比target大,这是因为target是在和有序数组中的数从右向左比较,有序数组从右向左依次减小。因而不必担心赋值到下一位会不会把下一位本来的数值覆盖掉,因为这个数肯定已经被放到下下位了,而最终会放到坐标i上,而坐标i的数已经存储到了target中),这样一直找,直到找到有序数组中的某个数小于等于target,那么它后一位的数大于target,已经被放到再后面一位去了,这里(也就是在j+1的位置上)空了出来,把target放进去。
注3:见后。
注4:经由else造成的break的循环停止在这里暂不讨论,这里说明当j=0时的情况。
显然j=0是if语句总是正确才造成的,也就是说,target不断和有序数组进行对比,找到了有序数组的第1(坐标0)个数,都没有小于等于target的。那就把target放到坐标0上喽。(不用担心覆盖导致丢失的原因在注2)
冒泡排序
python实现
def bubble_sort(numbers):
for i in range(len(numbers),0,-1):
for j in range(0,i-1):
while j >= 0 and numbers[j] > numbers[j+1]:
numbers[j],numbers[j+1] = numbers[j+1],numbers[j]
j = j - 1
return numbers
解读
冒泡算法的思想是:
第一轮,从头开始,比较第j+1(坐标j)和第j+2(坐标j+1),如果前者大,就交换它们的位置。使得第j+2(坐标j+1)一定大。此时显然进入了循环,j=j-1导致j从0变为-1,下一次无法进入循环,重新进入
for j in range(0,i-1)
的循环,j取到1,即比较坐标1和坐标2的数,如果坐标2的数小,则进入循环,两个数交换位置,这时候较小的数到了位置1,还要和位置0的数比较大小,所以有j=j-1,比较坐标0和坐标1的大小。如果坐标2的数大于坐标1,则没有必要调换,也没必要将坐标2的数于坐标0进行比较,不进入循环,重新进入for j循环,j=2。。这样第一轮(j的最大值为i-1),坐标i-1与坐标i比较结束后,坐标i的数一定是最大了。
因而下一轮,不再需要把倒数第二个数(坐标i-2)和倒数第一个数(坐标为i-1)比较了,最终是把之前排序过的数组中的倒数第二大的数找出来,放在倒数第二位(坐标i-2)上。因此这一轮j的上限也就i-2。
我认为两者之间的区别
以已经顺序的数组进行研究会比较好理解。
比如同样对数组[3,4,5,6,7]排序。
如果使用插入排序法,在形成有序数组3,4之后,5只需要和4一比,就立刻明白自己不需要再去和3比了。也就是插入算法中注3处,else则直接break。
整个排序过程是这样的:
4与3一次比较,放在3后面(1)
5与4一次比较,放在4后面(2)
6与5一次比较,放在5后面(3)
7与6一次比较,放在6后面(4)
而如果使用冒泡排序,
第一轮j最大值为i-1
3,4一次比较,不交换位置
4,5一次比较,不交换位置,
5,6一次比较,不交换位置
6,7一次比较,不交换位置
第一轮结束,四次比较
第二j最大值为i-2
3,4一次比较,不交换位置
4,5一次比较,不交换位置
5,6一次比较,不交换位置
第二轮结束,三次比较
……
显然最后要经过4+3+2+1=10次比较。
所以说插入排序可能比冒泡排序简便。