文章目录
本系列文章共有十一篇:
冒泡排序及优化详解
快速排序及优化详解
插入排序及优化详解
希尔排序及优化详解
选择排序及优化详解
归并排序及优化详解
堆排序详解
计数排序及优化详解
桶排序详解
基数排序及优化详解
十大排序算法的比较与性能分析
一、快速排序基础
快速排序也是一种较为基础的排序算法,其效率比上篇文章介绍的冒泡排序算法有大幅提升。因为使用冒泡排序时,一趟只能选出一个最值,有n个元素最多就要执行n - 1趟比较。而使用快速排序时,一次可以将所有元素按大小分成两堆,也就是平均情况下需要logn轮就可以完成排序。
快速排序的思想是:每趟排序时选出一个基准值,然后将所有元素与该基准值比较,并按大小分成左右两堆,然后递归执行该过程,直到所有元素都完成排序。
快速排序的步骤如下:
1>先从数列中取出一个数作为基准数。
2>分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3>再对左右区间重复第二步,直到各区间只有一个数。
1.1 排序过程图示
假如有[4,7,2,8,1]五个元素,下面就以常见的“挖坑法”来演示一次快速排序的过程:
首先选定基准元素key,并记住这个位置index,这个位置相当于一个“坑”。并且设置两个指针left和right,指向数列的最左和最右两个元素:
接下来,从right指针开始,把指针所指向的元素和基准元素做比较。如果比key大,则right指针向左移动;如果比key小,则把right所指向的元素填入坑中。在当前数组元素中,1<4,所以把1填入基准元素所在位置,也就是坑的位置。这时候,元素1本来所在的位置成为了新的坑。同时,left向右移动一位:
接下来,我们切换到left指针进行比较。如果left指向的元素小于key,则left指针向右移动;如果元素大于key,则把left指向的元素填入坑中。在当前数列中,7>4,所以把7填入index的位置。这时候元素7本来的位置成为了新的坑。同时,right向左移动一位。
继续上面的过程,8>4,元素位置不变,right左移:
2<4,用2来填坑,left右移,切换到left:
当left和right指针重合时,把之前的key元素,也就是4放到index的位置。此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束:
1.2 快速排序实现
用代码来实现上述过程,示例如下:
static void quickSort(int[] arr, int startIndex, int endIndex) {
/*当startIndex大于等于endIndex时,不再递归*/
if (startIndex >= endIndex) {
return;
}
/*得到基准元素位置*/
int keyIndex = divide(arr, startIndex, endIndex);
/*递归处理基准的左右两部分*/
quickSort(arr, startIndex, keyIndex - 1);
quickSort(arr, keyIndex + 1, endIndex);
}
static int divide(int[] arr, int startIndex, int endIndex) {
/*取第一个位置的元素作为基准元素*/
int key = arr[startIndex];
int left = startIndex;
int right = endIndex;
/*坑的位置,初始等于key的位置*/
int index = startIndex;
/*当right大于等于left时,执行循环*/
while (right >= left){
/*right指针从右向左进行比较*/
while (right >= left) {
if (arr[right] < key) {
/*最右边的元素覆盖原来坑中的值*/
arr[left] = arr[right];
/*坑的位置改变,变成最右边元素对应的索引*/
index = right;
/*left索引右移,因为原来的值已被right索引的值所覆盖*/
left++;
break;
}
/*最右边的元素参与比较,无论是否参与与坑中元素的交换,都左移,不再参与下一轮的比较*/
right--;
}
/*left指针从左向右进行比较*/
while (right >= left) {
if (arr[left] > key) {
arr[right] = arr[left];
index = left;
right--;
break;
}
left++;
}
}
/*将基准元素放在index位置,也就是大小元素放在其前后的中间位置*/
arr[index] = key;
return index;
}
二、快速排序优化
2.1 三数取中
该方法指的是选取基准值时,不再取固定位置(如第一个元素、最后一个元素)的值,因为这种固定取值的方式在面对随机输入的数组时,效率是非常高的。但是一旦输入数据是有序的,使用固定位置取值,效率就会非常低。因此此时引入了三数取中,即在数组中随机选出三个元素,然后取三者的中间值做为基准值。
2.2 插入排序
当待排序序列的长度分割到一定大小(如 < 10)后,使用插入排序。
2.3 相等元素聚集
在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割。
三、稳定性、复杂度和适用场景
3.1 稳定性
在使用快速排序时,每次元素分堆都要选择基准因子。此时,基准因子两边都有可能出现和基准因子相同的元素,如序列[1,3,2,4,3,4,6,3],如果选择了array[4]作为基准因子,那么array[1]和array[7]势必会被分到基准因子的同一侧,序列的稳定性被破坏。所以,快速排序是一种不稳定的排序算法。
3.2 时间复杂度
快速排序的时间复杂度是O(nlogn)。
3.3 适用场景
快速排序的适用场景是:待排序序列元素较多,并且元素较无序。