深度解析快速排序的奥秘
快速排序,说难不难,说易也不易,今天本文就从各个方向和维度来谈谈让我们困扰已久的——快速排序。
1、思想
1、通过一趟排序使用一个中间的分界值将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。
如该序列:
使用第一个元素5作为分界值,进行序列的分隔,分隔为两个序列,左侧序列的值都小于右侧的值,结果如下:
2、然后再按此方法对这两部分数据分别进行重复性操作直至排好顺序,就实现了快速排序。因此整个排序过程可以递归进行,以此达到整个数据变成有序序列。
第二次操作如图:
对5两侧的序列再次进行操作:左侧以3为分界值对左侧序列进行分隔,右侧以6为分界值对右侧序列进行分隔。以此类推,将整个序列排好序。
2、排序流程
(1)首先设定一个分界值,通过该分界值将整个数组分成左右两部分
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边
此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值
(3)然后,左边和右边的数据可以独立排序。对于左侧新的数组数据,又可以取一个分界值,
将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也做类似处理
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序当左、右两个部分各数据排序完成后,整个数组的排序也就完成了
从这个排序流程中我们可以看到几个关键点:
1、分界值该如何定位:当然可以还按上面例子,将序列中的第一个元素作为分界值(也可以设为别的位置的值,看需求),将其赋给mid,就将该值进行了保存提取。
2、左侧和右侧数组序列该如何界定,然后再分别对两个序列进行排序:可以定义一个序列开头start和序列结尾end。
3、如何同时将右侧小于mid的值赋给左侧,左侧大于等于mid的值赋给右侧呢:可以使用双指针。例如:第一个元素已经赋给了mid,那么该位置left的值就可以进行修改了。在右侧挑一个小于mid值的数,赋到第一个元素位置left;较小值的位置right也可以修改了,在左侧挑一个大于mid值的数,赋到此位置right。这样就有两个指针left和right,配合完成左右值的交换。最后当两个指针left和right重合时完成,并将mid值放到重合的位置。
3、代码实现
获取分界值
# 定义分界值, 假定第1个元素为分界值.
mid = my_list[start]
初始化指针游标
# 定义指针游标, 表示左边的 和 右边的数据.
left = start
right = end
从右端开始搜巡小于分界值的值,所以当值大于等于分界值时只需移动右侧的指标,但是为了防止右侧的值都大于等于分界值,使得很快出现两指针重合,甚至right指针小于left指针的问题,设置条件left < right。
# 将大于或等于分界值的数据集中到数组右边,
while my_list[right] >= mid and left < right:
right -= 1
# 走这里, 说明(分界值)右边的数据比分界值小, 放左边.
my_list[left] = my_list[right]
刚开始就将右侧的较小值赋给了 mid值所在的位置(注意:my_list[right] >===== mid中的 ’=‘ ,因为该等号的出现保障了该算法成为了稳定算法)。这样就可在左边找一个较大的值来修改该位置了,如下
# 小于分界值的数据集中到数组的左边
while my_list[left] < mid and left < right:
left += 1
# 走这里, 说明(分界值)左边的数据比分界值大, 放右边.
my_list[right] = my_list[left]
注意:my_list[left] < mid此处与my_list[right] >= mid相呼应,没有等号。将数据分为了两波挺像二分法的。
再做一个循环,重复前面的操作,直至双指针重合:
while left < right:
操作
my_list[left] = mid
结束循环后,将mid的值给到该位置(left和right值相等)
后面的操作就是重复性的操作,就交给递归了,分别处理两边的数据
# 处理分界后, 左边的数据(即: 比分界值小的数据)
quick_sort(my_list, start, left - 1)
# 处理分界后, 右边的数据(即: 比分界值大的数据)
quick_sort(my_list, right + 1, end)
所以最终的代码实现为:
# 1. 定义函数, 实现: 插入排序.
def quick_sort(my_list, start, end): # start:0, end: 列表长度-1
# 1.1 递归出口, 只要start >= end, 说明排好顺序了, 程序结束.
if start >= end:
return
# 1.2 定义分界值, 假定第1个元素为分界值.
mid = my_list[start] # 默认: mid = my_list[0]
# 1.3 定义游标, 表示左边的 和 右边的数据.
left = start
right = end
# 1.4 通过循环, 获取到(列表)每个数据.
while left < right:
# 1.5 将大于或等于分界值的数据集中到数组右边,
while my_list[right] >= mid and left < right:
right -= 1
# 走这里, 说明(分界值)右边的数据比分界值小, 放左边.
my_list[left] = my_list[right]
# 1.6 小于分界值的数据集中到数组的左边
while my_list[left] < mid and left < right:
left += 1
# 走这里, 说明(分界值)左边的数据比分界值大, 放右边.
my_list[right] = my_list[left]
# 1.7 循环结束后, 说明: 分界值的位置找到了.
my_list[left] = mid
quick_sort(my_list, start, left - 1)
# 处理分界后, 右边的数据(即: 比分界值大的数据)
quick_sort(my_list, right + 1, end)
测试数据:
if __name__ == '__main__':
my_list = [11, 33, 22, 55, 44]
print(f'排序前: {my_list}')
# 排序
quick_sort(my_list, 0, len(my_list) - 1)
print(f'排序后: {my_list}')
结果:
最后感觉有用的话,不要忘记点赞和收藏欧!