快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序个项目要
(大O符号)次比较。在最坏状况下则需要
次比较,但这种状况并不常见。事实上,快速排序
通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成,在数据结构中算很重要的知识点,是基于分而治之的思想。快速排序一般情况下是面试的必备考点,很多面试需要手写快速排序。
算法:
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
- 从数列中挑出一个元素,称为“基准”(pivot)。
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最终的位置上去。
算法伪代码:
function quicksort(q)
var list less, pivotList, greater
if length(q) ≤ 1 {
return q
} else {
select a pivot value pivot from q
for each x in q except the pivot element
if x < pivot then add x to less
if x ≥ pivot then add x to greater
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
平均时间复杂度:
即使如果我们无法随机地选择基准数值,对于它的输入之所有可能排列,快速排序仍然只需要时间。因为这个平均是简单地将输入之所有可能排列的时间加总起来,除以
这个因数,相当于从输入之中选择一个随机的排列。当我们这样作,基准值本质上就是随机的,导致这个算法与随机数快速排序有一样的运行时间。
更精确地说,对于输入顺序之所有排列情形的平均比较次数,可以借由解出这个递归关系式可以精确地算出来。。
在这里,是分割所使用的比较次数。因为基准值是相当均匀地落在排列好的数列次序之任何地方,总和就是所有可能分割的平均。
这个意思是,平均上快速排序比理想的比较次数,也就是最好情况下,只大约比较糟39%。这意味着,它比最坏情况较接近最好情况。这个快速的平均运行时间,是快速排序比其他排序算法有实际的优势之另一个原因。
空间复杂度:
被快速排序所使用的空间,依照使用的版本而定。使用原地(in-place)分割的快速排序版本,在任何递归调用前,仅会使用固定的額外空間。然而,如果需要产生嵌套递归调用,它需要在他们每一个存储一个固定数量的信息。因为最好的情况最多需要
次的嵌套递归调用,所以它需要
的空间。最坏情况下需要
次嵌套递归调用,因此需要
的空间。
然而我们在这里省略一些小的细节。如果我们考虑排序任意很长的数列,我们必须要记住我们的变量像是left和right,不再被认为是占据固定的空间;也需要对原来一个
项的数列作索引。因为我们在每一个堆栈框架中都有像这些的变量,实际上快速排序在最好跟平均的情况下,需要
空间的比特数,以及最坏情况下
的空间。然而,这并不会太可怕,因为如果一个数列大部分都是不同的元素,那么数列本身也会占据
的空间字节。
非原地版本的快速排序,在它的任何递归调用前需要使用空间。在最好的情况下,它的空间仍然限制在
,因为递归的每一阶中,使用与上一次所使用最多空间的一半,且
。
它的最坏情况是很恐怖的,需要
空间,远比数列本身还多。如果这些数列元素本身自己不是固定的大小,这个问题会变得更大;举例来说,如果数列元素的大部分都是不同的,每一个将会需要大约为原来存储,导致最好情况是
和最坏情况是
的空间需求。
Java:
public class Application {
public static void qSort(int[] arr, int head, int tail) {
if (head >= tail || arr == null || arr.length <= 1) {
return;
}
int i = head, j = tail, pivot = arr[(head + tail) / 2];
while (i <= j) {
while (arr[i] < pivot) {
++i;
}
while (arr[j] > pivot) {
--j;
}
if (i < j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
++i;
--j;
} else if (i == j) {
++i;
}
}
qSort(arr, head, j);
qSort(arr, i, tail);
}
public static void main(String[] args) {
int[] arr = new int[]{1, 4, 8, 2, 55, 3, 4, 8, 6, 4, 0, 11, 34, 90, 23, 54, 77, 9, 2, 9, 4, 10};
qSort(arr, 0, arr.length - 1);
String out = "";
for (int digit : arr) {
out += (digit + ",");
}
System.out.println(out);
}
}
C语言:
//快速排序实现
#include "stdio.h"
#include "stdlib.h"
#include "time.h"//用于获取程序运行时间
void quick_sort(int s[],int l,int r)
{
if(l < r)
{
int i=l,j=r,x=s[l];
while(i<j)
{
while(i<j && s[j]>=x)//从右到左找到第一个小于x的数
j--;
if(i<j)
s[i++]=s[j];
while(i<j && s[i]<=x)//从左往右找到第一个大于x的数
i++;
if(i<j)
s[j--]=s[i];
}
s[i]=x;//i = j的时候,将x填入中间位置
quick_sort(s,l,i-1);//递归调用
quick_sort(s,i+1,r);
}
}
int main()
{
clock_t start,finish;
double totaltime;
start=clock();
/****下面为需要运行的主程序****/
int a[] = {1,8,44,77,35,65,78,12,25,455,20,15,45};
int length = sizeof(a)/sizeof(int);//求数组的长度
printf("原序列为: ");
for(int i=0;i<length;i++)
{
printf("%3d",a[i]);
}
quick_sort(a,0,length);
printf("\n排序后的序列为:");
for(int i=0;i<length;i++)
{
printf("%3d",a[i]);
}
/********************************/
finish=clock();
totaltime=(double)(finish-start)/CLOCKS_PER_SEC;
printf("\n程序运行的时间为: %.5f 秒",totaltime);
}