快速排序算法详解(原理,时间复杂度,实现代码)

这篇具有很好参考价值的文章主要介绍了快速排序算法详解(原理,时间复杂度,实现代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

快速排序算法详解(原理、实现和时间复杂度)

快速排序是对冒泡排序的一种改进,由 C.A.R.Hoare(Charles Antony Richard Hoare,东尼·霍尔)在 1962 年提出。

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

快速排序的原理

排序算法的思想非常简单,在待排序的数列中,我们首先要找一个数字作为基准数(这只是个专用名词)。为了方便,我们一般选择第 1 个数字作为基准数(其实选择第几个并没有关系)。接下来我们需要把这个待排序的数列中小于基准数的元素移动到待排序的数列的左边,把大于基准数的元素移动到待排序的数列的右边。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动,直到各个分区只有一个数时为止。

这是典型的分治思想,即分治法。下面我们对一个实际例子进行算法描述,讲解快速排序的排序步骤。

快速排序详解

以 “6 1 2 7 9 3 4 5 10 8” 的待排序的数列为例进行排序

接下来开始移动元素。怎么移动呢?其实冒泡排序也涉及对元素的移动,但是那样移动起来很累,比如把最后一个元素移动到第 1 个,就需要比较 n-1 次,同时交换 n-1 次,效率很低。其实,只需把第 1 个元素和最后一个元素交换就好了,这种思想是不是在排序时可以借鉴呢?之前说快速排序就是对冒泡排序的一个改进,就是这个原因。

快速排序的操作是这样的:首先从数列的右边开始往左边找,我们设这个下标为 i,也就是进行减减操作(i–),找到第 1 个比基准数小的值,让它与基准值交换;接着从左边开始往右边找,设这个下标为 j,然后执行加加操作(j++),找到第 1 个比基准数大的值,让它与基准值交换;然后继续寻找,直到 i 与 j 相遇时结束,最后基准值所在的位置即 k 的位置,也就是说 k 左边的值均比 k 上的值小,而 k 右边的值都比 k 上的值大。

假如用 “6 1 2 7 9 3 4 5 10 8” 这个初始序列来进行快速排序

分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即=10),指向数字。
快排算法,排序算法,算法,数据结构
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j–),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。
快排算法,排序算法,算法,数据结构

快排算法,排序算法,算法,数据结构
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:
6 1 2 5 9 3 4 7 10 8
快排算法,排序算法,算法,数据结构
快排算法,排序算法,算法,数据结构
到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:

6 1 2 5 4 3 9 7 10 8

第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下:

3 1 2 5 4 6 9 7 10 8
快排算法,排序算法,算法,数据结构
快排算法,排序算法,算法,数据结构

快排算法,排序算法,算法,数据结构
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

左边的序列是“3 1 2 5 4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧

如果你模拟的没有错,调整完毕之后的序列的顺序应该是:

2 1 3 5 4

OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下:

1 2 3 4 5 6 9 7 10 8

对于序列“9 7 10 8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下

1 2 3 4 5 6 7 8 9 10

到此,排序完全结束。细心的同学可能已经发现,快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。下面上个霸气的图来描述下整个算法的处理过程。
快排算法,排序算法,算法,数据结构
这是为什么呢?

快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。我们后面还会遇到“二分”思想,到时候再聊。先上代码,如下

快速排序代码实现

其实快速排序有一个比较简单的思想,就是递归。对于每一趟排序都是一样的思想,只不过需要进行排序的数组的范围越来越小了,使用递归实现这种排序最适合不过了。

import com.sun.deploy.util.StringUtils;

public class tets {
    
    public static void main(String[] args) {

        int[] data = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};

        System.out.println("排序之前:\n" + java.util.Arrays.toString(data));

        quickSort(data, 0, data.length - 1);

        System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
    }


    public static void quickSort(int[] data, int low, int high) {
        int i, j, temp, t;
        if (low > high) {
            return;
        }
        i = low;
        j = high;
        //temp就是基准位
        temp = data[low];
        System.out.println("基准位:" + temp);

        while (i < j) {
            //因为所排列顺序是递增,而且每次哨兵排查结束在于左右哨兵是否相遇,所以右边哨兵负责寻找大于基准的数,左边哨兵寻找小于基准的数
            // ,如若需要改变快排后的结果顺序只需要改变左右哨兵对于基准数的大小判断
            //先看右边,依次往左递减 在未和左边哨兵相遇前寻找大于或者等于基准的数
            while (temp <= data[j] && i < j) {
                j--;
            }
            //再看左边,依次往右递增 在未和右边哨兵相遇前寻找小于或者等于基准的数
            while (temp >= data[i] && i < j) {
                i++;
            }
            //如果满足条件则交换  左右哨兵未相遇的情况下寻找到满足条件的数
            if (i < j) {
                System.out.println("交换:" + data[i] + "和" + data[j]);
                t = data[j];
                data[j] = data[i];
                data[i] = t;
                System.out.println(java.util.Arrays.toString(data));
            }
        }
        //最后将基准位与i和j相等位置的数字交换 左右哨兵相遇
        System.out.println("基准位" + temp + "和i、j相遇的位置" + data[i] + "交换");
        data[low] = data[i];
        data[i] = temp;
        System.out.println(java.util.Arrays.toString(data));

        //此时基准数左边都比基准数小,右边都比基准数大,故而基准数位置不变,基准数左右两边重新进行快排
        //递归调用左半数组
        quickSort(data, low, j - 1);
        //递归调用右半数组
        quickSort(data, j + 1, high);
    }
}

快速排序的特点及性能
快速排序是在冒泡排序的基础上改进而来的,冒泡排序每次只能交换相邻的两个元素,而快速排序是跳跃式的交换,交换的距离很大,因此总的比较和交换次数少了很多,速度也快了不少。

但是快速排序在最坏情况下的时间复杂度和冒泡排序一样,是 O(n2),实际上每次比较都需要交换,但是这种情况并不常见。我们可以思考一下如果每次比较都需要交换,那么数列的平均时间复杂度是 O(nlogn),事实上在大多数时候,排序的速度要快于这个平均时间复杂度。这种算法实际上是一种分治法思想,也就是分而治之,把问题分为一个个的小部分来分别解决,再把结果组合起来。

快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中会消耗一定的空间,在一般情况下的空间复杂度为 O(logn),在最差的情况下,若每次只完成了一个元素,那么空间复杂度为 O(n)。所以我们一般认为快速排序的空间复杂度为 O(logn)

快速排序是一个不稳定的算法,在经过排序之后,可能会对相同值的元素的相对位置造成改变。

快速排序基本上被认为是相同数量级的所有排序算法中,平均性能最好的。文章来源地址https://www.toymoban.com/news/detail-640155.html

到了这里,关于快速排序算法详解(原理,时间复杂度,实现代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 希尔排序及其时间复杂度(图文详解)

    😾 博客主页: 爱吃bug的猿 🚀 博客专栏: 数据结构,C语言初阶进阶全流程讲解 😽😽😽 如果喜欢博主的文章,可以给博主点波赞和关注加速博主更新 希尔排序里的一部分和插入排序极其相似,了解插入排序及其复杂度(动图讲解)可点击此处 希尔排序分为两部分:预排序

    2024年02月13日
    浏览(36)
  • 常见的排序算法及时间空间复杂度

    排序算法是计算机科学中的基本算法之一,它用于将一组数据按照某种顺序进行排列。下面是一些常见的排序算法,以及它们的思想和时间空间复杂度,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1. 冒泡排序 (Bubble Sort) - 思

    2024年02月07日
    浏览(39)
  • 排序算法的时间复杂度存在下界问题

    对于几种常用的排序算法,无论是归并排序、快速排序、以及更加常见的冒泡排序等,这些排序算法的时间复杂度都是大于等于O(n*lg(n))的,而这些排序算法存在一个共同的行为,那就是这些算法在对元素进行排序的时候,都会进行同一个操作,也就是对数组中取出文件,然后

    2024年02月21日
    浏览(41)
  • 时间复杂度为 O(nlogn) 的排序算法

    归并排序 归并排序遵循 分治 的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后合并这些子问题的解来建立原问题的解,归并排序的步骤如下: 划分 :分解待排序的 n 个元素的序列成各具 n/2 个元素的两个子序列,将长数组的排序

    2024年02月07日
    浏览(33)
  • 时间复杂度为 O(n) 的排序算法

    大家好,我是 方圆 。本文介绍线性排序,即时间复杂度为 O(n) 的排序算法,包括桶排序,计数排序和基数排序,它们都不是基于比较的排序算法,大家重点关注一下这些算法的适用场景。 桶排序 桶排序是分治策略的一个典型应用。它通过设置一些具有大小顺序的桶,每个桶

    2024年02月21日
    浏览(41)
  • 插入,选择,堆,快速排序算法思想与复杂度

    目录 插入排序 思想 算法步骤 代码 复杂度 选择排序 思想 算法步骤 代码 复杂度 堆排序  思想 算法步骤 代码 复杂度  快速排序  思想 算法步骤 代码 复杂度 稳定性 插入排序是一种简单直观的排序算法。它的工作原理是将数组分为 已排序 和 未排序 两部分,然后依次将未

    2024年02月15日
    浏览(37)
  • 三路快排(基于三指针单趟排序的快速排序)+快排时间复杂度再分析

    目录  一.前言 二. 三路快排 😍算法思想: 😍算法实现步骤: 😍三指针单趟排序的实现:​ 😍非递归快排完全体: 🤔与C标准库里的快排进行对比测试: 三.快排时间复杂度再分析 http://t.csdn.cn/mz8dg http://t.csdn.cn/mz8dg http://t.csdn.cn/1TqDp http://t.csdn.cn/1TqDp 😄关于快排的基本思想和实

    2023年04月15日
    浏览(43)
  • 时间复杂度为 O(n^2) 的排序算法

    大家好,我是 方圆 。对于小规模数据,我们可以选用时间复杂度为 O(n 2 ) 的排序算法,因为时间复杂度并不代表实际代码的执行时间,而且它也省去了低阶、系数和常数,仅代表的增长趋势,所以在小规模数据情况下, O(n 2 ) 的排序算法可能会比 O(nlogn) 的排序算法执行效率

    2024年02月07日
    浏览(46)
  • 初阶算法(1):通过简单的排序算法来认识时间复杂度

      第一章    初阶算法(1):通过简单的排序算法来认识时间复杂度   第二章   初阶算法(2):进行详细地介绍插入排序的细节和时间复杂度   第三章   初阶算法(3):二分法的讲解与实现(C语言),以及二分不止光在有序数组中的应用  目录 系列文章目录 前言 一

    2024年02月12日
    浏览(36)
  • 时间复杂度接近O(n)的三种排序算法

    桶排序,顾名思义,会用到“桶”,核心思想是将要排序的数据分到几个有 序的桶里,每个桶内的数据再单独进行排序。桶内排完序之后,再把每个桶内的数据按照顺序依次 取出,组成的序列就是有序的了。 桶排序对要排序数据的要求是非常苛刻的。 首先,要排序的数据需

    2024年02月14日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包