【算法系列 | 5】深入解析排序算法之——快速排序

news2024/11/16 18:05:51

序言

你只管努力,其他交给时间,时间会证明一切。

文章标记颜色说明:

  • 黄色:重要标题
  • 红色:用来标记结论
  • 绿色:用来标记一级论点
  • 蓝色:用来标记二级论点

决定开一个算法专栏,希望能帮助大家很好的了解算法。主要深入解析每个算法,从概念到示例。

我们一起努力,成为更好的自己!

今天第5讲,讲一下排序算法的快速排序(Quick Sort)

1 基础介绍

排序算法是很常见的一类问题,主要是将一组数据按照某种规则进行排序。

以下是一些常见的排序算法:

  1. 冒泡排序(Bubble Sort)

  2. 插入排序(Insertion Sort)

  3. 选择排序(Selection Sort)

  4. 归并排序(Merge Sort)

  5. 快速排序(Quick Sort)

  6. 堆排序(Heap Sort)

一、快速排序介绍

1.1 原理介绍

快速排序(Quick Sort)是一种常用的排序算法,也是一种基于分治思想的排序算法。

快速排序的基本思想是选取一个基准元素,将数组分成两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素,然后对左右两部分分别递归进行排序,最终得到一个有序数组。

下面来详细介绍一下快速排序的原理和实现过程:

  1. 选择基准元素

    快速排序的第一步是选择一个基准元素,一般情况下是选择数组的第一个元素或最后一个元素作为基准元素。选择基准元素的目的是将数组划分成两个部分使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。

  2. 划分数组

    在选择了基准元素之后,需要将数组划分成两部分。具体方法是使用两个指针 i 和 j 分别从数组的左右两端开始扫描,当 i 指向的元素大于等于基准元素时,停止移动,当 j 指向的元素小于等于基准元素时,停止移动,然后交换 i 和 j 指向的元素,继续移动指针。当 i 和 j 相遇时,将基准元素与 i 指向的元素交换位置,这样就完成了一次划分。

  3. 递归排序

    划分数组之后,将左右两部分分别递归进行排序,直到每个部分只剩下一个元素或空数组为止。递归排序的过程中,需要重复执行以上两个步骤,即选择基准元素和划分数组。

  4. 合并数组

    在递归排序完成之后,需要将左右两部分合并成一个有序数组。由于左右两部分都已经有序,可以使用归并排序的思想来合并数组。

示例讲解 

下面举一个例子来说明快速排序的过程:

  1. 原数组:[3, 5, 1, 9, 7, 2, 8, 4, 6]
  2. 选择基准元素 3,分成左半部分 [1, 2] 和右半部分 [5, 9, 7, 8, 4, 6]
  3. 对左半部分 [1, 2] 递归调用快速排序算法,得到有序数组 [1, 2]
  4. 对右半部分 [5, 9, 7, 8, 4, 6] 选择基准元素 5,分成左半部分 [4] 和右半部分 [9, 7, 8, 6]
  5. 对左半部分 [4] 递归调用快速排序算法,得到有序数组 [4]
  6. 对右半部分 [9, 7, 8, 6] 选择基准元素 9,分成左半部分 [7, 8, 6] 和右半部分 []
  7. 对左半部分 [7, 8, 6] 选择基准元素 7,分成左半部分 [6] 和右半部分 [8]
  8. 对左半部分 [6] 递归调用快速排序算法,得到有序数组 [6]
  9. 对右半部分 [8] 递归调用快速排序算法,得到有序数组 [8]
  10. 将左半部分 [6]、基准元素 7 和右半部分 [8] 拼接起来,得到有序数组 [6, 7, 8]
  11. 将左半部分 [4]、基准元素 5 和右半部分 [6, 7, 8, 9] 拼接起来,得到有序数组 [4, 5, 6, 7, 8, 9]
  12. 将左半部分 [1, 2]、基准元素 3 和右半部分 [4, 5, 6, 7, 8, 9] 拼接起来,得到有序数组 [1, 2, 3, 4, 5, 6, 7, 8, 9]

1.2 复杂度 

时间复杂度:

快速排序的平均时间复杂度为 O(nlogn),其中 n 表示要排序的数组的长度。

在最坏情况下,即每次选择的基准元素都是当前数组中最小或最大的元素,递归树的深度将达到 n,此时的时间复杂度为 O(n^2)。

但是,快速排序的平均时间复杂度远远优于最坏情况下的时间复杂度

快速排序的优势在于它是一种原地排序算法,即不需要额外的存储空间,只需要通过交换数组中的元素来实现排序。这使得快速排序在实际应用中表现出色,被广泛使用。

空间复杂度:

快速排序的空间复杂度为 O(logn) 至 O(n),其中 n 表示要排序的数组的长度。

在递归调用快速排序算法时,需要使用递归栈来保存每一层递归的状态。

在最坏情况下,即每次选择的基准元素都是当前数组中最小或最大的元素,递归树的深度将达到 n,此时的空间复杂度为 O(n)。

但是,在平均情况下,递归树的深度通常为 O(logn),因此空间复杂度为 O(logn)。

另外,快速排序是一种原地排序算法,即不需要额外的存储空间,只需要通过交换数组中的元素来实现排序。因此,快速排序的空间复杂度在最优情况下为 O(1)。

1.3使用场景

快速排序是一种高效的排序算法,在实际应用中被广泛使用。以下是一些快速排序的应用场景:

  1. 排序大规模数据:快速排序的时间复杂度为 O(nlogn),比其他常见的排序算法如冒泡排序、选择排序和插入排序等更快,因此适用于需要处理大规模数据的场景。

  2. 搜索数据:快速排序可以对数据进行排序,使得搜索数据时可以更快速地定位到目标数据,因此适用于需要频繁搜索数据的场景。

  3. 数据压缩:快速排序可以将相似的数据放在一起,从而提高数据的压缩率,因此适用于需要进行数据压缩的场景。

  4. 数据库查询:快速排序可以对数据库中的数据进行排序,从而提高查询效率,适用于需要频繁查询数据库的场景。

  5. 数组去重:快速排序可以将数组中相同的元素放在一起,从而方便去重操作,适用于需要进行数组去重的场景。

需要注意的是,在实际应用中,需要根据具体情况来选择合适的排序算法。

虽然快速排序的时间复杂度较低,但是在最坏情况下会出现时间复杂度为 O(n^2) 的情况,因此需要采用一些优化措施来避免最坏情况的出现。

1.4 优缺点 

优点:

快速排序是一种高效的排序算法,具有以下优点:

  1. 时间复杂度低:快速排序的平均时间复杂度为 O(nlogn),比其他常见的排序算法如冒泡排序、选择排序和插入排序等更快。

  2. 原地排序:快速排序是一种原地排序算法,即不需要额外的存储空间,只需要通过交换数组中的元素来实现排序。

  3. 分治思想:快速排序采用分治思想,将问题分解为若干个子问题进行求解,从而简化了问题的复杂度。

  4. 可以进行原地去重操作:快速排序可以将数组中相同的元素放在一起,从而方便去重操作。

缺点:

但是,快速排序也存在一些缺点:

  1. 最坏情况下的时间复杂度较高:在最坏情况下,即每次选择的基准元素都是当前数组中最小或最大的元素,递归树的深度将达到 n,此时的时间复杂度为 O(n^2)。

  2. 对于小规模数据排序效率低:当要排序的数据规模较小的时候,快速排序的效率不如其他简单的排序算法,如插入排序和冒泡排序等。

  3. 选择基准元素的难度:选择基准元素的方式会影响快速排序的性能,如果每次选择的基准元素都是当前数组中的最小或最大元素,将会导致快速排序的性能退化。

  4. 不稳定性:快速排序是一种不稳定的排序算法,即在排序过程中相同的元素可能会被交换位置,从而导致相同元素的相对位置发生变化。

需要根据具体情况来选择合适的排序算法,对于快速排序的缺点,可以通过一些优化措施来避免或缓解。

二、代码实现

2.1 Python 实现

下面是 Python 代码实现快速排序算法,并提供测试代码,对代码进行详细讲解:

def quick_sort(arr):
    """
    快速排序算法的实现函数

    Parameters:
        arr (list): 要排序的数组

    Returns:
        list: 排序后的数组
    """
    # 如果数组长度小于等于1,则直接返回
    if len(arr) <= 1:
        return arr

    # 选择基准元素
    pivot = arr[0]

    # 分割数组
    left = [x for x in arr[1:] if x <= pivot]
    right = [x for x in arr[1:] if x > pivot]

    # 递归调用快速排序算法,并将分割后的数组合并起来
    return quick_sort(left) + [pivot] + quick_sort(right)

以上是基本的快速排序算法的实现。

测试 

接下来提供一个测试代码,测试快速排序算法的正确性:

import random

# 生成随机数组
arr = [random.randint(0, 100) for _ in range(10)]
print("原始数组:", arr)

# 对数组进行快速排序
arr_sorted = quick_sort(arr)
print("排序后数组:", arr_sorted)

# 验证排序结果是否正确
assert arr_sorted == sorted(arr), "排序结果不正确"
print("排序结果正确")

测试代码中,首先生成一个包含 10 个随机整数的数组 arr,然后调用 quick_sort 函数对数组进行排序,并将排序后的数组存储在变量 arr_sorted 中。

接着,使用 Python 内置的 sorted 函数对原数组进行排序,将排序后的结果存储在变量 sorted_arr 中,并使用断言来验证快速排序的结果是否正确。如果排序结果正确,则输出 "排序结果正确",否则输出错误信息。

在测试代码中,我使用了 Python 内置的 sorted 函数来验证快速排序的结果是否正确,这是因为 sorted 函数是一种稳定且正确的排序算法,在验证结果时可以作为参照。

2.2Java实现

下面是快速排序的 Java 代码实现:

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {3, 5, 1, 9, 7, 2, 8, 4, 6};
        quickSort(arr, 0, arr.length - 1);
        System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }

    public static void quickSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }

        int pivot = partition(arr, left, right);
        quickSort(arr, left, pivot - 1);
        quickSort(arr, pivot + 1, right);
    }

    public static int partition(int[] arr, int left, int right) {
        int pivot = arr[left];
        int i = left, j = right;

        while (i < j) {
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            if (i < j) {
                arr[i++] = arr[j];
            }

            while (i < j && arr[i] <= pivot) {
                i++;
            }
            if (i < j) {
                arr[j--] = arr[i];
            }
        }

        arr[i] = pivot;

        return i;
    }
}

这个实现使用了两个函数,一个是 quickSort() 函数,用于进行递归调用,另一个是 partition() 函数,用于划分数组。下面对这两个函数进行详细讲解:

quickSort() 函数

public static void quickSort(int[] arr, int left, int right) {
    if (left >= right) {
        return;
    }

    int pivot = partition(arr, left, right);
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
}


这个函数使用递归的方式对数组进行排序。对于输入的数组和左右下标,首先判断左下标是否大于等于右下标,如果是,则直接返回。

否则,使用 `partition()` 函数将数组划分成两个部分,分别对左半部分和右半部分递归调用 `quickSort()` 函数。

partition() 函数

public static int partition(int[] arr, int left, int right) {
    int pivot = arr[left];
    int i = left, j = right;

    while (i < j) {
        while (i < j && arr[j] >= pivot) {
            j--;
        }
        if (i < j) {
            arr[i++] = arr[j];
        }

        while (i < j && arr[i] <= pivot) {
            i++;
        }
        if (i < j) {
            arr[j--] = arr[i];
        }
    }

    arr[i] = pivot;

    return i;
}

这个函数用于划分数组。在函数内部,使用两个指针 i 和 j 分别从数组的左右两端开始扫描,

  1. 当 i 指向的元素大于等于基准元素时,停止移动,
  2. 当 j 指向的元素小于等于基准元素时,停止移动,然后交换 i 和 j 指向的元素,继续移动指针。
  3. 当 i 和 j 相遇时,将基准元素与 i 指向的元素交换位置,这样就完成了一次划分。
  4. 最后,将基准元素移动到正确的位置,并返回基准元素的下标。

需要注意的是,在划分数组时,要先从右边开始扫描,否则可能会导致数组越界的问题。

此外,在移动指针时,需要判断 i 和 j 是否相遇,否则可能会导致死循环的问题。

今天就到这里了,下期见~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【日志解析】【频率分析】ULP:基于正则表达式和本地频率分析进行日志模板提取

An Effective Approach for Parsing Large Log Files 文章目录 An Effective Approach for Parsing Large Log Files1 论文出处2 背景2.1 背景介绍2.2 针对问题2.3 创新点 3 主要设计思路3.1 预处理3.2 日志事件分组3.3 通过频率分析生成日志模板 4 实验设计4.1 准确性4.2 效率…

物联网Lora模块从入门到精通(八)Lora无线通信

一、前言 在某些环境下&#xff0c;无法通过有线传输数据&#xff0c;这时候我们需要使用Lora无线通信传输数据&#xff0c;Lora无线数据传输具有低功耗、距离长的特点&#xff0c;常用于工厂内等&#xff0c;需要Lora基站。 我曾做过距离测试&#xff1a;Lora模块距离测试-物联…

【Pytest实战】pytest 基本概念及使用大全

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。&#x1f60a; 座右铭&#xff1a;不想…

Spring6-02

JdbcTemplate JdbcTemplate是Spring提供的一个JDBC模板&#xff0c;是对JDBC的封装&#xff0c;简化了JDBC代码。当然也可以不用JdbcTemplate&#xff0c;可以让Spring集成其他的ORM框架&#xff0c;例如MyBatis、Hibernate等。接下来使用JdbcTemplate完成增删改查。 环境准备…

重写并自定义console.log()输出样式

0. 背景 笔者在开发的实践过程中对于控制台内容的输出情况有一些特殊的需求&#xff0c;然而&#xff0c;普通的console.log()函数不能提供很好的支持&#xff0c;因此需要探索一些自定义的实现方式&#xff0c;以便满足开发需求&#xff0c;一些开发需求如下&#xff1a; 输…

编译详细过程与交叉编译

GCC的编译过程&#xff1a; GCC编译分为四步&#xff0c;预处理、编译、汇编、链接。具体功能如上图所示&#xff0c;我们在稍微解释一下&#xff1a; 1.预处理&#xff1a; 实现过程&#xff1a;gcc -E xxx.c -o xxx.i 目的&#xff1a;我们的c程序中除了main函数以外&…

如何在Linux中使用read命令读取用户输入?——read命令实战

前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文是专栏【linux基本功-基础命令实战】的第64篇文章。 专栏地址&#xff1a;[linux基本功-基础命令专栏] &#xff0c; 此专栏是沐风晓月对Linux常用命令的汇总&#xff0c;希望对你有用。 今天我们一…

实现jvm内存溢出

那么我们如何来构建一个堆内存溢出呢&#xff1f;其实很简单&#xff0c;我们只要定义一个List对象&#xff0c;然后通过一个循环不停的往List里面塞对象。因为只要Controller不被回收&#xff0c;那么它里面的成员变量也是不会被回收的。这样就会导致List里面的对象越来越多&a…

Play wright自动化测试工具该如何更加完美地使用

目录 1.1 拦截网络请求 1.2 pytest 管理用例 1.3 PO模型 1.4 API 和 UI 自动化测试融合 1.5 数据驱动 1.6 动态挑选用例执行 1.6 Allure测试报告 1.7 持续集成 1.1 拦截网络请求 网络拦截&#xff1a; 无响应 pass 中止 route.abort("aborted") 放行 route…

Hazel游戏引擎(013)Layers游戏的层级

文中若有代码、术语等错误&#xff0c;欢迎指正 文章目录 前言增加Layer后的主要类图项目相关代码项目流程效果 LayerStack类的错误 前言 此节目的 为完成008事件系统设计的第四步&#xff0c;将事件从Application传递分发给Layer层。 使引擎事件系统模块完整 Layer的理解 …

在VSCode下利用PlateFormIO开发Arduino的MicroROS遇到的一些问题

简介 我是按照鱼香ROS的教程【3.搭建PlateFormIO开发环境】进行的&#xff0c;但是在进行的过程中&#xff0c;遇到了一些问题&#xff0c;这里记录下来&#xff0c;供有同样问题的同学进行参考。其实只要你使用的板子的MCU是ESP32&#xff0c;都可以按照他这个教程进行操作。…

k8s实践之mysql集群搭建(十五)

先下载 k8s实践之mysql集群搭建资料 主从模式简介&#xff1a; 当master主服务器上的数据发生改变时&#xff0c;则将其改变写入二进制&#xff08;binlog&#xff09;事件日志文件中&#xff1b; slave从服务器会在一定时间间隔内对master主服务器上的二进制日志进行探测&am…

掌握Vue生命周期,让你的前端开发效率翻倍!

1 Vue实例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Vue实例</title><script src../vue.js></script> </head> <body><div id"root"><!-- v…

位图以及布隆过滤器

本文主要讲解哈希思想的实际应用&#xff0c;位图和布隆过滤器。 位图 讲解位图之前我们先来解答这样一道腾讯的面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。【腾讯】 很多人立马就想到了用…

Seata TCC 模式理论学习、生产级使用示例搭建及注意事项 | Spring Cloud55

一、前言 通过以下系列章节&#xff1a; docker-compose 实现Seata Server高可用部署 | Spring Cloud 51 Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52 Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53 Seata XA 模式理论学习、使用…

STL——set容器、map容器

初识STL **set容器/multiset容器****set容器——构造和赋值****set容器——大小和交换****set容器——插入和删除****set容器的查找和统计****set和multiset的区别****set的相关操作源码&#xff1a;****multiset的相关操作源码** **pair使用——pair队组的创建****set容器——…

FreeRTOS-定时器详解

✅作者简介&#xff1a;嵌入式入坑者&#xff0c;与大家一起加油&#xff0c;希望文章能够帮助各位&#xff01;&#xff01;&#xff01;&#xff01; &#x1f4c3;个人主页&#xff1a;rivencode的个人主页 &#x1f525;系列专栏&#xff1a;玩转FreeRTOS &#x1f4ac;保持…

2023全国计算机二级考试时间(全年各阶段考试时间安排)

2023全国计算机二级考试时间(全年各阶段考试时间安排) 2023年全国计算机二级考试时间分别为&#xff1a;3月25日至27日(上半年3月)、9月23日至25日(下半年9月)。 其中3月和9月开考全部级别全部科目&#xff0c;5月和12月考试开考一、二级全部科目&#xff0c;各省级承办机构可根…

RabbitMQ集群部署之镜像模式

RabbitMQ集群的普通模式中&#xff0c;一旦创建队列的主机宕机&#xff0c;队列就会不可用。不具备高可用能力。如果要解决这个问题&#xff0c;必须使用官方提供的镜像集群方案。 官方文档地址&#xff1a;https://www.rabbitmq.com/ha.html 1.镜像模式的特征 默认情况下&a…

离心式冷水机组

离心式冷水机组是利用电作为动力源&#xff0c;氟利昂制冷剂在蒸发器内蒸发吸收载冷剂水的热量进行制冷&#xff0c;蒸发吸热后的氟利昂湿蒸汽被压缩机压缩成高温高压气体&#xff0c;经水冷冷凝器冷凝后变成液体&#xff0c;经膨胀阀节流进入蒸发器再循环。从而制取7℃-12℃冷…