快速排序,和归并排序一样,采用分而治之和递归的方式。
原理
快速排序的步骤为:
- 从待排序列表中选择一个轴(piovt:一般默认为选择index为0的元素,改进快速排序算法一般在轴的选择上作文章)。
- 划分大小区域(partitioning):对数组进行重新排序,使得值小于pivot的所有元素都在pivot之前,而值大于pivot的所有元素都在它之后(相同的值可以是任意一种)。 在此分区之后,枢轴处于其最终位置。 这称为分区操作。
- 递归调用1,2:递归地将上述步骤应用于具有较小值的元素的子列表,并分别应用于具有较大值的元素的子列表。
递归的基本情况是大小为零或一的数组,按定义按顺序排列,因此它们永远不需要排序。
代码实现
def quick_sort(a_list):
quick_sort_helper(a_list, 0, len(a_list) - 1)
def quick_sort_helper(a_list, first, last):
if last <= first:
return
split_point = partition(a_list, first, last)
quick_sort_helper(a_list, first, split_point - 1)
quick_sort_helper(a_list, split_point + 1, last)
def partition(a_list, first, last):
pivot_value = a_list[first]
left_mark = first + 1
right_mark = last
done = False
while not done:
while left_mark <= right_mark and a_list[left_mark] <= pivot_value:
left_mark += 1
while left_mark <= right_mark and a_list[right_mark] >= pivot_value:
right_mark -= 1
if right_mark < left_mark:
done = True
else:
temp = a_list[left_mark]
a_list[left_mark] = a_list[right_mark]
a_list[right_mark] = temp
temp = a_list[first]
a_list[first] = a_list[right_mark]
a_list[right_mark] = temp
return right_mark
代码解读
算法的重点在于partition函数,该函数的操作是在列表中将index值为0的元素通过比较,交换,放置在最终排序所在的位置。同时比它小的元素在它的左边,比它大的元素在它的右边。最后分别对左右的子列表进行递归计算。最后得到的列表就是排序好的列表。
总结
快速排序是一种不稳定的排序算法。 快速排序的数学分析表明,平均而言,该算法需要进行\(O(nlog_2n)\)比较以对n个项进行排序。 在最坏的情况下,它会进行\(O(n^2)\)比较,尽管这种情况很少见。
要分析quick_sort函数,请注意,对于长度为list的列表,如果分区始终出现在列表的中间,则会再次出现\(O(log_2n)\)分区。 为了找到分割点,需要根据枢轴值检查每个?项。 结果是\(O(nlog_2n)\)。 此外,在合并排序过程中不需要额外的内存。
不幸的是,在最坏的情况下,分裂点可能不在中间,并且可能非常偏向左侧或右侧,留下非常不均匀的分割。 在这种情况下,排序?项目列表分为排序0项目列表和? - 1项目列表。 然后排序? - 1列表分为大小为0的列表和大小为? - 2的列表,依此类推。 结果是\(O(n^2)\)排序,具有递归所需的所有开销。
三平均分区法
关于这一改进的最简单的描述大概是这样的:与一般的快速排序方法不同,它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
- 首先,它使得最坏情况发生的几率减小了。
- 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。