1.基本思想
通过一趟排序将一个序列分割成两个独立的部分,其中一个子序列小于某个值,另一个子序列大于某个值,再对这两部分进行递归排序,从而实现整个序列排序
首先我们要选取一个关键值来对序列中的值进行比较从而分割,这里我们采用三数中值分割法,选取序列的开头、中位数、结尾的值,先进行排序,将三个值中最小的放在开头,次小的放在中间,最大的放在结尾,此时次小的值就是我们要找的关键值,称为枢轴。将次小值放在结尾前,一个指针从开头后向后,指向第一个小于枢轴的值,另一边从枢轴值向前,找到第一个大于枢轴的值,将这两个值交换位置,指针继续移动,再进行比较和交换,直到两个指针重合,所指的值是大于枢轴的,与枢轴交换位置。现在,枢轴左边的是小于枢轴的子序列,右边的是大于枢轴的子序列,将这两个序列继续进行快速查找操作。
当序列很小,小于某个截至范围(5-10)时,快速排序不如插入排序,此时我们使用插入排序
2.算法实现
public static <AnyType extends Comparable<? super AnyType>> void quicksort(AnyType[] a)
{
quicksort(a, 0, a.length - 1);
}
private static final int CUTOFF = 3;//截止范围
private static <AnyType extends Comparable<? super AnyType>> void quicksort(AnyType[] a, int left, int right)
{
if(left + CUTOFF <= right)
{
AnyType pivot = median3(a, left, right);//储存枢轴
int i = left;//左指针
int j = right - 1;//右指针
while(true)
{
while(a[++i].compareTo(pivot) < 0) {};//左指针移动,当移动到一个较大元素时停止,注意先加一再调用
while(a[--j].compareTo(pivot) > 0) {};//右指针移动,当移动到一个较小元素时停止,注意先减一再调用
if(i < j)
swapReferences(a, i, j);
else
break;
}
swapReferences(a, i, right - 1);//左右指针重合时,将枢轴放入数组中,左边为小数组,右边为大数组
quicksort(a, left, i - 1);//对小数组进行快速排序
quicksort(a, i + 1, right);//对大数组进行快速排序
}
else
insertion(a, left, right);
}
//三数中值分割法
private static <AnyType extends Comparable<? super AnyType>> AnyType median3(AnyType[] a, int left, int right)
{
int center = (left + right) / 2;//确定中值角标
//将left,center,right角标上的元素按从大到小顺序排列
if(a[center].compareTo(a[left]) < 0)
swapReferences(a, left, center);
if(a[right].compareTo(a[left]) < 0)
swapReferences(a, right, left);
if(a[right].compareTo(a[center]) < 0)
swapReferences(a, right, center);
//将center与right - 1交换位置
swapReferences(a, center, right - 1);
return a[right - 1];
}
//交换两个元素位置
public static <AnyType> void swapReferences(AnyType[] a, int index1, int index2)//不需要继承Comparable,没用到比较方法
{
AnyType tmp = a[index1];
a[index1] = a[index2];
a[index2] = tmp;
}
//面对小数组的插入排序
private static <AnyType extends Comparable<? super AnyType>> void insertion(AnyType[] a, int left, int right)
{
for(int p = left + 1; p <= right; p++)
{
AnyType tmp = a[p];
int j;
for(j = p ; j > left && tmp.compareTo(a[j - 1]) < 0;j--)
a[j] = a[j - 1];
a[j] = tmp;
}
}
3.算法分析
- 快速排序在整体性能上仍然是排序算法的王者
- 当序列很小时,使用递归所需的反复调用函数所增加的时间超过了时间复杂度降低所减少的时间,此时使用直接插入排序。CUTOFF是对“较多”和“较少”的分界线,没有严格的标准
- 在最坏情况下,快速排序的时间复杂度为O(n2),最好的情况下时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)
- 空间上快速排序主要是递归造成的栈空间的使用,最好时间复杂度为O(logn),最坏时间复杂度为O(n),平均时间复杂度为O(logn)
- 由于枢轴的比较和交换是跳跃进行的,因此快速排序是一种不稳定的算法