吃透排序算法之划分排序
排序算法不仅仅是无序到有序, 其中涉及了数组的遍历处理, 划分的模板运用, 甚至其中还设计到了数组到树的堆化处理. 可以说吃透了排序算法, 可以成为一个合格的算法初学者了.
本系列文章带领读者吃透排序算法, 掌握数组的处理方式, partition
模板, 数组的二叉树形式并进行堆化处理.
吃透排序算法之划分排序为本系列的划分排序介绍, 主要介绍几种高级的排序, 也是默认在各个语言内置排序中的算法, 本文主要涉及:
- 快速排序
- 归并排序
快速排序
快速排序顾名思义, 是一种时间复杂度和空间复杂度比较优秀的算法, 也因此可以被选入各个语言默认的排序算法. 原理类似于划分, 每次使得数组基准位置前面都小于基准数, 后面大于基准数.
这也是第一次接触划分和partition
, 一种比较好的去处理数组的方式, 通过控制l
和r
, 在了l<r
时进行划分.
def partition(l, r): # partition模板
if not l < r:
return
# m =
partition(l, m)
partition(m+1, r)
快速排序基准数的位置需要到算法完成才知道, 因此更多的处理就是在计算m
. 选定l
位置为基准数, 数组r
到l
找到一个小于基准数的位置和基准数交换, 之后从l
到r
找到一个大于基准数的位置和之前小于基准数的位置交换.
nums = [1, 3, 2, 5, 4, 6]
def partition(l, r):
if not l < r:
return
b = nums[l]
i, j = l, r
while i < j:
while i < j and nums[j] > b:
j -= 1
if i < j:
nums[i] = nums[j]
while i < j and nums[i] < b:
i += 1
if i < j:
nums[j] = nums[i]
nums[i] = b
m = i
partition(l, m)
partition(m+1, r)
partition(0, len(nums)-1)
print(nums)
归并排序
归并排序和快速排序都属于划分排序, 如果说快速排序在于划分, 归并排序就是在于合并了. 按照之前的划分算法, 归并的划分并不难. 归并排序将数组分割成两部分, 使两部分均有序, 再将两部分回去.
def partition(l, r):
if not l < r:
return
m = int((l+r)/2)
partition(l, m)
partition(m+1, r)
# 如何合并
引入一个思考, 在不借助额外空间的情况下, 如何移动数组. 举个例子[3,4,5,1,2]
如何移动成为[1,2,3,4,5]
? 一个比较好的方式就是反转反转反转. 数组前三个和后两个交换, 可以先反转为5,4,3
, 再反转2,1
, 之后整体反转为[1,2,3,4,5]
. 反转实现利用了双指针的技巧, 比较好想.
def reverse(p, q):
while p < q:
nums[p], nums[q] = nums[q], nums[p]
p += 1
q -= 1
reverse(i, k)
reverse(k+1, j)
reverse(i, j)
接下来去看看归并什么位置需要反转即可, l
找到一个比m+1
大的, r
也移动到比l
大的位置. 此时就是期望的反转位置, 调用反转即可.
nums = [1, 3, 2, 5, 4, 6]
def partition(l, r):
if not l < r:
return
m = int((l+r)/2)
partition(l, m)
partition(m+1, r)
i, j = l, m+1
while i < j <= r:
while i < j <= r and nums[i] < nums[j]:
i += 1
k = j
while i < j <= r and nums[j] < nums[i]:
j += 1
def reverse(p, q): #双指针反转
while p < q:
nums[p], nums[q] = nums[q], nums[p]
p += 1
q -= 1
reverse(i, k-1)
reverse(k, j-1)
reverse(i, j-1)
i += (j-1)-k+1
partition(0, len(nums)-1)
print(nums)
小结
本文介绍了快速排序的实现, 了解了归并排序的原理和实现, 引入了数组整体交换的问题, 并提出可以通过三次反转实现. 快速排序和归并排序同属于划分排序, 但侧重点不同, 快速排序侧重划分, 归并排序侧重合并.