排序算法之插入排序及其优化
插入排序
其他排序方法:选择排序、冒泡排序、归并排序、快速排序、插入排序、希尔排序、堆排序
思想
先将数组第一个元素作为一个排好序的序列,然后将数组中剩下的元素从左往右一个一个地按照大小插进此序列里,所插位置后面的元素都往后移一位,直到元素全部插完。
这个很好理解,就像我们玩扑克牌的时候,一张一张地摸牌,刚摸到手的一张牌,找到合适的位置后,插进去,右边的牌就要挪个位置。
图解
再次借用一下百科的图emmm:
性能
插入排序最好的情况就是你每次插入的元素都刚好是插在最后,那就只用进行n-1次比较就行了,时间复杂度为O(n);
最坏情况的时间复杂度显然是O(n2),平均时间复杂度也为O(n2)。
代码
在找出正确的插入位置时,先比较前后元素位置是否正确,正确的话就结束循环;不正确的话,前面的元素往后移一位,继续比较前一位。
有两种方式,一种就是每比较一次就互换位置,另一种就是先记住需要插入的元素,需要换位置的时候就先把前一位元素后移一位,等找到合适的位置在插入。
这时候有些人就会图方便(譬如我),选择第一种方式,这样代码写起来好看,也容易理解,但这样子效率会比第二种方式低。
所以还是推荐使用第二种方式。
# 插入排序
def insertionSort1(arr):
for i in range(1, len(arr)):
j = i
while j > 0:
if arr[j] < arr[j - 1]:
arr[j], arr[j - 1] = arr[j - 1], arr[j] #这里可以改进一下
j -= 1
else:
break
def insertionSort2(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i
while j > 0:
if arr[j - 1] > key:
arr[j] = arr[j - 1]
j -= 1
else:
break
if (i != j): arr[j] = key
优化
上面的排序算法也叫直接插入排序,那也就是说有不那么直接的插入排序咯。
直接插入排序是从后往前一个个地进行比较来找出插入位置的。学过查找算法的人都知道这样并不高效,我们可以利用其他查找方法来代替它。
优化一 二分插入排序
二分插入排序是指在简单插入排序的基础上用二分查找算法来找出插入位置的排序算法。
# 二分查找
# size 为有序部分大小
def binarySearch(arr, size, key):
# 最左下标为0, 最右下标为size-1
left, right = 0, size - 1
while left <= right:
# mid为中间元素的下标
mid = left + right >> 1
# 中间元素刚好与插入元素相同时,返回中间元素后一位的下标
if key == arr[mid]:
return mid + 1
elif key < arr[mid]:
# 查找左半边
right = mid - 1
else:
# 查找右半边
left = mid + 1
# 结束循环时,left必定等于right+1
# 最后一次查找如果是查找左半边,则key比arr[mid]小,key应该插入到mid的位置,而mid等于现在的right+1等于left
# 最后一次查找如果是查找右半边,则key比arr[mid]大,key应该插入到mid+1的位置,而mid+1等于现在的left
# 所以只需返回left
return left
# 二分插入排序(插入排序优化一)
def binarySort(arr):
for i in range(1, len(arr)):
# 需要插入的元素
key = arr[i]
# 利用二分查找来找到插入的位置
pos = binarySearch(arr, i, key)
j = i
# 插入位置后面的元素全部后移一位
while j > pos:
arr[j] = arr[j - 1]
j -= 1
# 插入元素
arr[j] = key
优化二 希尔排序
直接插入排序在找到插入位置时,需要一个一个地往后移动元素。有没有什么办法可以一下子移动多个呢?答案是不可以哈哈。
因为元素已经是有序的,新的元素插到中间,那其右边的元素必定都要往后移一位。
我们换个角度来想,有序不行,那就在元素还是无序的时候,跳着来移动。这就要提到希尔排序算法了。
希尔排序大概就是,选一组递减的整数作为增量序列。最小的增量必须为1:\(D_M>D_{M-1}>...>D_1=1\)
- 先用第一个增量把数组分为若干个子数组,每个子数组中的元素下标距离等于增量;
- 然后对每个子数组进行简单插入排序
- 再使用第二个增量,继续同样的操作,直到增量序列里的增量都使用过一次。
(增量为1时,其实就是对整个数组进行简单插入排序)
看图更容易理解吧:
(借用一下慕课的浙大数据结构课件。因为课件原本是ppt,而我只有pdf,所以颜色没有上齐,请将就将就emmm)
希尔排序快不快主要取决于我们怎么取增量序列,最常见的取法就是:\(D_M=\lfloor N/2 \rfloor, D_k=\lfloor D_{k+1}/2 \rfloor\)
# 希尔排序
# 增量序列为D(M)=N/2, D(k)=D(k+1)/2 向下取整
def shellSort(arr):
size = len(arr)
# 正整数右移一位相当于除以2且向下取整
step = size >> 1
while step > 0:
for i in range(step, size):
j = i
tmp = arr[j]
while j >= step:
if tmp < arr[j - step]:
arr[j] = arr[j - step]
j -= step
else:
break
arr[j] = tmp
step = step >> 1
这就是原始希尔排序,关于希尔排序的其他增量序列我会在下一篇文章再作介绍:希尔排序