【恋上数据结构】快速排序

简介: 【恋上数据结构】快速排序

我的《恋上数据结构》源码(第1季 + 第2季):https://github.com/szluyu99/Data_Structure_Note

经典的十大排序算法!
在这里插入图片描述

前言

请==务必==看一下这个:排序算法前置知识+代码环境准备

当上面的内容都准备好以后,那就开始快速排序吧!

快速排序

执行流程:

  • ① 从序列中选择一个轴点元素(pivot)
    假设每次选择 0 位置的元素为轴点元素

    • ② 利用 pivot 将序列分割成 2 个子序列

    将小于 pivot 的元素放在pivot前面(左侧)
    将大于 pivot 的元素放在pivot后面(右侧)
    等于pivot的元素放哪边都可以

    • ③ 对子序列进行 ① ② 操作

    直到不能再分割(子序列中只剩下1个元素)

在这里插入图片描述
由图可见,快速排序的本质就是:

  • 逐渐将每一个元素都转换成轴点元素

轴点构造

对于序列 { 6~a~, 8~a~, 8~b~, 2, 6~b~, 4, 9, 5, 7 } 构造轴点

  • 首先将 0 位置的元素备份,作为轴点元素

在这里插入图片描述

  • 最终目标是构造出以轴点元素为中心的序列
    左半边是小于轴点元素序列
    右半边是大于轴点元素序列
    如下图:

在这里插入图片描述

那么如何构造呢?
begin 为序列首位置,

  • 从右往左扫描,寻找比轴点元素小的元素;
    找到以后,放到 begin 位置,begin++,然后反向寻找

    • 从左往右扫描,寻找比轴点元素大的元素;

找到以后,放到 end 位置,end--,然后反向寻找;

  • 当 begin == end,说明左右都扫描完了,此时将一开始备份的轴点元素放到中间

这个过程有点迷惑性,需要先从右往左找,找到以后再从左往右,不断循环这个过程,直到把所有比轴点元素小的元素放到左半部分把所有比轴点元素大的元素放到右半部分,才算完成这个轴点的构造。
在这里插入图片描述
在这里插入图片描述

构造轴点-代码实现

代码中如何实现从左往右扫描从右往左扫描的交替呢?看一眼代码就会恍然大悟。。。

/**
 * 构造出 [begin, end) 范围的轴点元素
 * @return 轴点元素的最终位置
 */
private int pivotIndex(int begin, int end){    
    // 备份begin位置的元素
    T pivot = array[begin];
    // end指向最后一个元素
    end--;
    while(begin < end){
        while(begin < end){    // 从右往左扫描
            if(cmp(pivot, array[end]) < 0){ // 右边元素 > 轴点元素
                end--;
            }else{ // 右边元素 <= 轴点元素
                array[begin++] = array[end];    
                break;
            }
        }
        while(begin < end){ // 从左往右扫描
            if(cmp(pivot, array[begin]) > 0){ // 左边元素 < 轴点元素
                begin++;
            }else{ // 左边元素 >= 轴点元素
                array[end--] = array[begin];
                break;
            }
        }
    }
    // 将轴点元素放入最终的位置
    array[begin] = pivot;
    // 返回轴点元素的位置
    return begin; // begin==end
}

构造轴点-优化

在轴点左右元素数量比较均匀的情况下,同时也是最好情况

  • T(n) = 2 ∗ T(n/2) + O(n) = O(nlogn)

如果轴点左右元素数量极度不均匀,最坏情况

  • T(n) = T(n − 1) + O(n) = O(n^2^)

为了降低最坏情况的出现概率,一般采取的做法是:

  • 随机选择轴点元素

可以在备份轴点元素前,将 begin 位置的元素与序列中的随机元素交换一下。

// 随机选择轴点元素, 将 begin 位置的元素与序列中的随机元素交换一下
swap(begin, begin + (int)Math.random()*(end - begin));

// 备份begin位置的元素
T pivot = array[begin];
.....

思考?与轴点相等的元素

如果序列中的所有元素都与轴点元素相等,利用上面的算法实现,轴点元素可以将序列分割成 2 个均匀的子序列
在这里插入图片描述

思考:把上面的代码中,cmp 位置的判断分别改为 ≤、≥ 会起到什么效果?
在这里插入图片描述
后果:

  • 轴点元素分割出来的子序列极度不均匀
  • 导致出现最坏时间复杂度 O(n^2^)

在这里插入图片描述

快速排序完整代码

/**
 * 快速排序
 */
public class QuickSort<T extends Comparable<T>> extends Sort<T> {

    @Override
    protected void sort() {
        sort(0, array.length);
    }
    /**
     * 对 [begin, end) 范围的元素进行快速排序
     */
    private void sort(int begin, int end){
        if(end - begin < 2) return;
        
        // 确定轴点位置 O(n)
        int mid = pivotIndex(begin, end);
        // 对子序列进行快速排序
        sort(begin, mid);
        sort(mid + 1, end);
    }
    /**
     * 构造出 [begin, end) 范围的轴点元素
     * @return 轴点元素的最终位置
     */
    private int pivotIndex(int begin, int end){
        // 随机选择轴点元素
        swap(begin, begin + (int)Math.random()*(end - begin));
        // 备份begin位置的元素
        T pivot = array[begin];
        // end指向最后一个元素
        end--;
        while(begin < end){
            while(begin < end){    // 从右往左扫描
                if(cmp(pivot, array[end]) < 0){ // 右边元素 > 轴点元素
                    end--;
                }else{ // 右边元素 <= 轴点元素
                    array[begin++] = array[end];    
                    break;
                }
            }
            while(begin < end){ // 从左往右扫描
                if(cmp(pivot, array[begin]) > 0){ // 左边元素 < 轴点元素
                    begin++;
                }else{ // 左边元素 >= 轴点元素
                    array[end--] = array[begin];
                    break;
                }
            }
        }
        // 将轴点元素放入最终的位置
        array[begin] = pivot;
        // 返回轴点元素的位置
        return begin; // begin==end
    }
    
}

生成 20000 个取值在[1, 10000] 的随机数进行排序:
在这里插入图片描述

复杂度与稳定性

快速排序的复杂度与稳定性

  • 最好、平均时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n^2^)
  • 由于递归调用的缘故,空间复杂度:O(logn)
  • 属于不稳定排序
相关文章
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
27 1
|
2月前
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序
|
2月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
|
4月前
|
存储 算法 搜索推荐
【初阶数据结构篇】冒泡排序和快速排序(中篇)
与直接插入排序法相比,比较次数一致,但冒泡排序的交换需要执行三次,而直接插入排序因为使用了tmp临时变量存储要插入的数据,只用执行一次,所以直接插入排序法效率明显更高。
35 1
|
5月前
|
搜索推荐
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
【数据结构常见七大排序(三)上】—交换排序篇【冒泡排序】And【快速排序】
|
6月前
|
算法
数据结构与算法-快速排序
数据结构与算法-快速排序
26 1
|
7月前
|
存储 搜索推荐 算法
[数据结构]————排序总结——插入排序(直接排序和希尔排序)—选择排序(选择排序和堆排序)-交换排序(冒泡排序和快速排序)—归并排序(归并排序)
[数据结构]————排序总结——插入排序(直接排序和希尔排序)—选择排序(选择排序和堆排序)-交换排序(冒泡排序和快速排序)—归并排序(归并排序)
[数据结构]-玩转八大排序(二)&&冒泡排序&&快速排序
[数据结构]-玩转八大排序(二)&&冒泡排序&&快速排序
|
6月前
|
算法 搜索推荐
数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)
数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)
44 0
|
6月前
|
搜索推荐
深入理解数据结构第五弹——排序(2)——快速排序
深入理解数据结构第五弹——排序(2)——快速排序
32 0