快速排序及其优化

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 = {31485627};    quickSort(arrays, 07);    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²)的概率。

以上是关于快速排序及其优化的主要内容,如果未能解决你的问题,请参考以下文章

快速排序基准元的选取及其优化

快速排序(递归和非递归)及其优化

[ 数据结构 -- 手撕排序算法第五篇 ] 快速排序 <包含hoare法,挖坑法,前后指针法> 及其算法优化

Java快速排序的非递归实现

数据结构之八大排序算法(C语言实现)

细说冒泡排序及其五种优化算法