快速排序及其优化
Posted 秃头让我们变强
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速排序及其优化相关的知识,希望对你有一定的参考价值。
什么是快速排序?
先上一个百科上的定义:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
什么意思呢?其实就是每次选择一个元素,作为基准元素,依次为边界,把所有小于基准元素的放在一边,所有大于基准元素的放在另一边。重复此操作,知道整组数有序。这就是用到一种叫做"分治"的思想。把一个大问题,拆分成多个小问题,然后求出每个子问题的解,最后就是这个大问题的解。
下面我们来看一组图,一起了解下。
双边循环
还有一点迷糊?下面我们再来一个具体的例子看一看。
一般的,我们选择第一个数字作为基准元素(选择哪一个数字没有限制),然后给定两个指针i和j,指针i从左往右遍历元素,指针j从右往左遍历元素。我们让j先走,当遇到比3小的数,则停止移动。然后i开始移动,当遇到比3大的数,则停止移动。然后让i和j所指向的数交换。
第一轮,j先开始向左移动,7 > 3,继续移动,2 > 3,停止移动。然后i开始向右移动,3 = 3,继续移动,1 < 3,继续移动,4 > 3,停止移动。
然后i和j指向的数交换位置。
j接着向左移动,一直到2,i和j重合
i和j重合的位置,就是基本元素的位置。所以我们让3和2交换位置
第一轮结束后,我们得到两个部分,{2,1}和{8,5,6,4,7}。然后我们再分别在这两个集合中选择各自的基准元素,然后重复上面的步骤即可。
接下来我们看看,双边循环的实现代码
public static void main(String[] args) {
int[] arrays = {3,1,4,8,5,6,2,7};
quickSort(arrays,0,7);
Arrays.stream(arrays).forEach( i -> System.out.println(i));
}
/**
* 快速排序
* @param arr 待排序数组
* @param startIndex 开始位置
* @param endIndex 结束位置
*/
public static void quickSort(int[] arr,int startIndex,int endIndex){
if (startIndex >= endIndex){
return;
}
int pivotIndex = getPivotIndex(arr,startIndex,endIndex);
quickSort(arr,startIndex,pivotIndex-1);
quickSort(arr,pivotIndex+1,endIndex);
}
/**
* 获取基准元素的位置
* @param arr 待排序数组
* @param startIndex 开始位置
* @param endIndex 结束位置
*/
public static int getPivotIndex(int[] arr,int startIndex,int endIndex){
int pivot = arr[startIndex]; // 选取第一个作为基准元素
int left = startIndex;
int right = endIndex;
while (left != right){
// right指针从右往左移动,直到当前元素小于基准元素停止
while (right > left && arr[right] > pivot ){
right --;
}
// left指针从左往右移动,直到当前元素大于基准元素停止
while (left < right && arr[left] <= pivot ){
left ++;
}
// 交换位置
if (left < right){
int num = arr[left];
arr[left] = arr[right];
arr[right] = num;
}
}
// left和right重合,基准元素和重合位置交换
arr[startIndex] = arr[left];
arr[left] = pivot;
return left;
}
单边循环
何为单边循环?单边循环只有一个指针,这个指针的左边代表小于基准元素的区域。然后从左到右遍历元素,当遇到小于基准的元素的数时,把指针向右移动一位,然后把指针指向的元素和当前元素交换位置。
下面我们来看图了解一下,offset即指针,黄色代表基准元素。
遍历到1,小于基准元素,offset右移一位,然后offset指向的元素和当前元素交换位置
接着向右遍历,一直到2,2<3,offset右移一位,然后offset指向的元素和当前元素交换位置
最后,让基准元素和offset指向的元素交换
后面的就不再叙述了,重复此步骤就可以了。
接下来我们看看代码实现
public static void main(String[] args) {
int[] arrays = {3, 1, 4, 8, 5, 6, 2, 7};
quickSort(arrays, 0, 7);
Arrays.stream(arrays).forEach(i -> System.out.println(i));
}
/**
* 快速排序
*
* @param arr 待排序数组
* @param startIndex 开始位置
* @param endIndex 结束位置
*/
public static void quickSort(int[] arr, int startIndex, int endIndex) {
if (startIndex >= endIndex) {
return;
}
int pivotIndex = getPivotIndex(arr, startIndex, endIndex);
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
/**
* 获取基准元素的位置
*
* @param arr 待排序数组
* @param startIndex 开始位置
* @param endIndex 结束位置
*/
public static int getPivotIndex(int[] arr, int startIndex, int endIndex) {
int pivot = arr[startIndex]; // 选取第一个作为基准元素
int offset = startIndex;
// 从左往右遍历元素
for (int i = startIndex + 1;i <= endIndex;i++){
// 如果当前元素小于基准元素,则offset向右移动一位,然后把offset指向的元素和当前元素交换位置
if (arr[i] < pivot){
offset ++;
int num = arr[offset];
arr[offset] = arr[i];
arr[i] = num;
}
}
// 遍历完毕,把基准元素和offset最后指向的元素交换位置
arr[startIndex] = arr[offset];
arr[offset] = pivot;
return offset;
}
关于基准元素的选择
通常情况下,快速排序的时间复杂度是O(nlogn),但是如果在下面这种情况下,时间复杂度就变成了O(n²)。
像这种倒序的极端情况下,以第一个数作为基准元素的方法,就会导致时间复杂度变为O(n²)。
该如何解决呢?我们可以随机选择一个数作为基准元素,然后和第一个数交换位置。这样可以降低O(n²)的概率。
以上是关于快速排序及其优化的主要内容,如果未能解决你的问题,请参考以下文章