排序算法之高效排序:快速排序,归并排序,堆排序详解

排序算法之高效排序:快速排序、归并排序、堆排序详解

  • 前言
  • 一、快速排序(Quick Sort)
    • 1.1 算法原理
    • 1.2 代码实现(Python)
    • 1.3 性能分析
  • 二、归并排序(Merge Sort)
    • 2.1 算法原理
    • 2.2 代码实现(Java)
    • 2.3 性能分析
  • 三、堆排序(Heap Sort)
    • 3.1 算法原理
    • 3.2 代码实现(C++)
    • 3.3 性能分析
  • 四、三种高效排序算法的对比与适用场景
  • 总结

前言

相较于上一期我讲的冒泡、选择、插入等基础排序,快速排序、归并排序和堆排序凭借更优的时间复杂度,成为处理大规模数据排序任务的首选方案。本文我将深入剖析这三种高效排序算法的原理、实现细节、性能特点及适用场景,助力你掌握它们在实际开发中的应用技巧。

一、快速排序(Quick Sort)

1.1 算法原理

快速排序由托尼・霍尔(Tony Hoare)于 1959 年提出,是一种基于分治思想的排序算法。其核心步骤如下:

选择基准值:从数组中选取一个元素作为基准值(通常选择第一个、最后一个或中间元素)。

分区操作:将数组分为两个子数组,使得左边子数组的所有元素都小于等于基准值,右边子数组的所有元素都大于基准值。

递归排序:对左右两个子数组分别递归地进行快速排序。

通过不断重复上述步骤,最终使整个数组达到有序状态。例如,对于数组[5, 3, 8, 6, 2],若选择5作为基准值,经过分区操作后,数组变为[3, 2, 5, 6, 8],然后分别对[3, 2][6, 8]进行递归排序,最终得到有序数组[2, 3, 5, 6, 8]
00

1.2 代码实现(Python)

def quick_sort(arr):if len(arr) <= 1:return arrpivot = arr[len(arr) // 2]left = [x for x in arr if x < pivot]middle = [x for x in arr if x == pivot]right = [x for x in arr if x > pivot]return quick_sort(left) + middle + quick_sort(right)

上述代码中,首先判断数组长度,若小于等于 1 则直接返回。接着选取基准值,通过列表推导式将数组分为小于、等于、大于基准值的三个部分,最后递归地对左右子数组进行排序并合并。

1.3 性能分析

时间复杂度

平均情况下,快速排序的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,其中n为数组元素个数。

最坏情况下(如数组已有序且每次选择的基准值为最大或最小元素),时间复杂度退化为 O ( n 2 ) O(n^2) O(n2)

空间复杂度:快速排序的空间复杂度主要取决于递归调用栈的深度。平均情况下,空间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) ;在最坏情况下,递归深度达到n,空间复杂度为 O ( n ) O(n) O(n)

稳定性:快速排序是不稳定的排序算法,因为在分区过程中,相同元素的相对顺序可能会发生改变。

二、归并排序(Merge Sort)

2.1 算法原理

归并排序同样基于分治思想,它将一个数组分成两个大致相等的子数组,分别对两个子数组进行排序,然后将排好序的子数组合并成一个最终的有序数组。具体步骤如下:

分解:将待排序数组不断平均分成两个子数组,直到子数组长度为 1(单个元素可视为有序)。

排序:对每个子数组进行排序(可使用其他排序方法,通常也是递归地使用归并排序)。

合并:从最底层开始,将两个有序的子数组合并成一个更大的有序数组,不断向上合并,直至得到整个有序数组。

例如,对于数组[8, 4, 2, 1, 7, 6, 3, 5],先分解为多个子数组,再依次排序并合并,最终得到有序数组[1, 2, 3, 4, 5, 6, 7, 8]
01

2.2 代码实现(Java)

import java.util.Arrays;public class MergeSort {public static void mergeSort(int[] arr) {if (arr == null) {return;}int[] temp = new int[arr.length];mergeSort(arr, temp, 0, arr.length - 1);}private static void mergeSort(int[] arr, int[] temp, int left, int right) {if (left < right) {int mid = left + (right - left) / 2;mergeSort(arr, temp, left, mid);mergeSort(arr, temp, mid + 1, right);merge(arr, temp, left, mid, right);}}private static void merge(int[] arr, int[] temp, int left, int mid, int right) {System.arraycopy(arr, left, temp, left, right - left + 1);int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right) {if (temp[i] <= temp[j]) {arr[k++] = temp[i++];} else {arr[k++] = temp[j++];}}while (i <= mid) {arr[k++] = temp[i++];}while (j <= right) {arr[k++] = temp[j++];}}public static void main(String[] args) {int[] arr = {8, 4, 2, 1, 7, 6, 3, 5};mergeSort(arr);System.out.println(Arrays.toString(arr));}
}

在上述 Java 代码中,mergeSort方法作为入口,调用递归的mergeSort方法进行分解和排序,merge方法用于合并两个有序子数组。通过临时数组temp辅助完成合并操作,保证合并过程中数据的正确处理。

2.3 性能分析

时间复杂度:归并排序无论在最好、最坏还是平均情况下,时间复杂度均为 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,因为每次分解和合并操作的时间开销相对固定,总操作次数与 n log ⁡ n n \log n nlogn相关。

空间复杂度:归并排序在合并过程中需要使用额外的空间存储临时数据,空间复杂度为 O ( n ) O(n) O(n)

稳定性:归并排序是稳定的排序算法,在合并子数组时,相同元素的相对顺序不会发生改变。

三、堆排序(Heap Sort)

3.1 算法原理

堆排序利用了堆这种数据结构(大顶堆或小顶堆)的特性来实现排序。大顶堆的特点是每个父节点的值都大于或等于其子节点的值,小顶堆则相反。堆排序的主要步骤如下:

建堆:将待排序数组构建成一个大顶堆(升序排序时)或小顶堆(降序排序时)。

交换与调整:将堆顶元素(最大值或最小值)与堆的最后一个元素交换,然后对剩余元素重新调整堆结构,使其再次满足堆的性质。

重复操作:不断重复步骤 2,直到堆中只剩下一个元素,此时数组即为有序状态。

例如,对于数组[4, 6, 8, 5, 9],先构建大顶堆[9, 6, 8, 5, 4],然后将 9 与 4 交换,调整堆为[8, 6, 4, 5, 9],依次类推,最终得到有序数组[4, 5, 6, 8, 9]
02

3.2 代码实现(C++)

#include <iostream>
#include <vector>
using namespace std;// 调整堆结构
void heapify(vector<int>& arr, int n, int i) {int largest = i;int left = 2 * i + 1;int right = 2 * i + 2;if (left < n && arr[left] > arr[largest]) {largest = left;}if (right < n && arr[right] > arr[largest]) {largest = right;}if (largest != i) {swap(arr[i], arr[largest]);heapify(arr, n, largest);}
}// 堆排序
void heapSort(vector<int>& arr) {int n = arr.size();// 建堆for (int i = n / 2 - 1; i >= 0; --i) {heapify(arr, n, i);}// 交换与调整for (int i = n - 1; i > 0; --i) {swap(arr[0], arr[i]);heapify(arr, i, 0);}
}

上述 C++ 代码中,heapify函数用于调整堆结构,确保以i为根节点的子树满足堆的性质。heapSort函数先进行建堆操作,然后通过不断交换堆顶元素和堆的最后一个元素,并调整堆结构,实现排序功能。

3.3 性能分析

时间复杂度:堆排序的时间复杂度主要由建堆和调整堆两部分组成。建堆的时间复杂度为 O ( n ) O(n) O(n) ,调整堆的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,因此整体时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,且在最好、最坏和平均情况下均保持不变。

空间复杂度:堆排序在排序过程中只需要常数级别的额外空间,空间复杂度为 O ( 1 ) O(1) O(1)

稳定性:堆排序是不稳定的排序算法,因为在调整堆结构时,相同元素的相对顺序可能会被打乱。

四、三种高效排序算法的对比与适用场景

排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性适用场景
快速排序 O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( log ⁡ n ) O(\log n) O(logn)不稳定数据随机分布、对空间要求不高的场景;适合内部排序,常用于通用排序库
归并排序 O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( n ) O(n) O(n)稳定对稳定性有要求、外部排序(如处理大文件)、数据规模较大且内存充足的场景
堆排序 O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( n log ⁡ n ) O(n \log n) O(nlogn) O ( 1 ) O(1) O(1)不稳定对空间要求严格、需要在线性时间内找到最大 / 最小元素的场景,如优先队列实现

总结

快速排序、归并排序和堆排序作为高效排序算法,在不同的应用场景中发挥着各自的优势。快速排序凭借其简洁高效的特点,在多数常规排序任务中表现出色;归并排序以稳定的性能和适用于外部排序的特性,成为处理大规模数据的可靠选择;堆排序则因其对空间的高效利用和稳定的时间复杂度,在特定场景下展现出独特价值。下期博客中,我将带你探索更多高级排序算法与优化技巧,例如希尔排序、计数排序等,分析它们与快速排序、归并排序、堆排序的差异,以及在不同业务场景中的实际应用案例,帮助大家进一步拓宽排序算法的知识边界。

That’s all, thanks for reading!
创作不易,点赞鼓励;
知识无价,收藏备用;
持续精彩,关注不错过!

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

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

相关文章

Android开发——轮播图引入

Android开发——轮播图引入 一、前期准备与依赖引入二、配置启动类(AndroidManifest.xml)三、构造启动类(MainActivity.java)四、配置布局文件(activity_main.xml)五、最终效果与扩展方向一、前期准备与依赖引入 在开始引入轮播图功能前,需确保已正确搭建Android开发环境…

[逆向工程]C++实现DLL卸载(二十六)

[逆向工程]C实现DLL卸载&#xff08;二十六&#xff09; 引言 DLL注入&#xff08;DLL Injection&#xff09;是Windows系统下实现进程间通信、功能扩展、监控调试的核心技术之一。本文将从原理分析、代码实现、实战调试到防御方案&#xff0c;全方位讲解如何用C实现DLL注入&…

lesson01-PyTorch初见(理论+代码实战)

一、初识PyTorch 二、同类框架 PyTorchVSTensorFlow 三、参数 对比 四、PyTorch生态 四、常用的网络层 五、代码分析 import torch from torch import autogradx torch.tensor(1.) a torch.tensor(1., requires_gradTrue) b torch.tensor(2., requires_gradTrue) c tor…

STM32中的DMA

DMA介绍 什么是DMA? DMA&#xff08;Direct Memory Access&#xff0c;直接存储器访问&#xff09;提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通&#xff0c;而不需要依赖于CPU&#xff0c;在这个时间中&#xff0c;CPU对于内存…

聊聊JetCache的缓存构建

序 本文主要研究一下JetCache的缓存构建 invokeWithCached com/alicp/jetcache/anno/method/CacheHandler.java private static Object invokeWithCached(CacheInvokeContext context)throws Throwable {CacheInvokeConfig cic context.getCacheInvokeConfig();CachedAnnoC…

c#队列及其操作

可以用数组、链表实现队列&#xff0c;大致与栈相似&#xff0c;简要介绍下队列实现吧。值得注意的是循环队列判空判满操作&#xff0c;在用链表实现时需要额外思考下出入队列条件。 设计头文件 #ifndef ARRAY_QUEUE_H #define ARRAY_QUEUE_H#include <stdbool.h> #incl…

开源项目实战学习之YOLO11:12.3 ultralytics-models-sam-encoders.py源码分析

👉 点击关注不迷路 👉 点击关注不迷路 👉 另外,前些天发现了一个巨牛的AI人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。感兴趣的可以点击相关跳转链接。 点击跳转到网站。 ultralytics-models-sam 1.sam-modules-encoders.pyblocks.py: 定义模型中的各…

STM32 | FreeRTOS 消息队列

01 一、概述 队列又称消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;队列可以在任务与任务间、中断和任务间传递信息&#xff0c;实现了任务接收来自其他任务或中断的不固定长度的消息&#xff0c;任务能够从队列里面读取消息&#xff0c;当队列中的消…

Java 安全漏洞扫描工具:如何快速发现和修复潜在问题?

Java 安全漏洞扫描工具&#xff1a;如何快速发现和修复潜在问题&#xff1f; 在当今的软件开发领域&#xff0c;Java 作为一种广泛使用的编程语言&#xff0c;其应用的规模和复杂度不断攀升。然而&#xff0c;随着应用的拓展&#xff0c;Java 应用面临的潜在安全漏洞风险也日益…

Python绘制克利夫兰点图:从入门到实战

Python绘制克利夫兰点图&#xff1a;从入门到实战 引言 克利夫兰点图&#xff08;Cleveland Dot Plot&#xff09;是一种强大的数据可视化工具&#xff0c;由统计学家William Cleveland在1984年提出。这种图表特别适合展示多个类别的数值比较&#xff0c;比传统的条形图更直观…

LVGL- Calendar 日历控件

1 日历控件 1.1 日历背景 lv_calendar 是 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;提供的标准 GUI 控件之一&#xff0c;用于显示日历视图。它支持用户查看某年某月的完整日历&#xff0c;还可以实现点击日期、标记日期、导航月份等操作。这个控件…

多指标组合策略

该策略(MultiConditionStrategy)是一种基于多种技术指标和市场条件的交易策略。它通过综合考虑多个条件来生成交易信号,从而决定买入或卖出的时机。 以下是对该策略的详细分析: 交易逻辑思路 1. 条件1:星期几和价格变化判断 - 该条件根据当前日期是星期几以及价格的变化…

BC 范式与 4NF

接下来我们详细解释 BC 范式&#xff08;Boyce-Codd范式&#xff0c;简称 BCNF&#xff09;&#xff0c;并通过具体例子说明其定义和应用。 一、BC范式的定义 BC范式&#xff08;Boyce-Codd范式&#xff0c;BCNF&#xff09;是数据库规范化理论中的一种范式&#xff0c;它比第…

基于 CSS Grid 的网页,拆解页面整体布局结构

通过以下示例拆解网页整体布局结构&#xff1a; 一、基础结构&#xff08;HTML骨架&#xff09; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"…

采购流程规范化如何实现?日事清流程自动化助力需求、采购、财务高效协作

采购审批流程全靠人推进&#xff0c;内耗严重&#xff0c;效率低下&#xff1f; 花重金上了OA&#xff0c;结果功能有局限、不灵活&#xff1f; 问题出在哪里&#xff1f;是我们的要求太多、太苛刻吗&#xff1f;NO&#xff01; 流程名称&#xff1a; 采购审批管理 流程功能…

全栈项目搭建指南:Nuxt.js + Node.js + MongoDB

全栈项目搭建指南&#xff1a;Nuxt.js Node.js MongoDB 一、项目概述 我们将构建一个完整的全栈应用&#xff0c;包含&#xff1a; 前端&#xff1a;Nuxt.js (SSR渲染)后端&#xff1a;Node.js (Express/Koa框架)数据库&#xff1a;MongoDB后台管理系统&#xff1a;集成在同…

NVMe简介6之PCIe事务层

PCIe的事务层连接了PCIe设备核心与PCIe链路&#xff0c;这里主要基于PCIe事务层进行分析。事务层采用TLP传输事务&#xff0c;完整的TLP由TLPPrefix、TLP头、Payload和TLP Digest组成。TLP头是TLP中最关键的部分&#xff0c;一般由三个或四个双字的长度&#xff0c;其格式定义如…

Python异常模块和包

异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”, 也就是我们常说的BUG 例如&#xff1a;以r方式打开一个不存在的文件。 f open(‘python1.txt’,‘r’,encoding‘utf-8’) 当我们…

汇编:循环程序设计

一、 实验要求 熟练掌握循环程序设计的基本方法熟练掌握单片机外部存储空间的访问方法 二、 实验设计 1.整体思路 先初始化一些寄存器和数据存储位置&#xff0c;然后调用两个子程序Procedure1和Procedure2&#xff0c;分别从SRC复制数据到DEST&#xff0c;一个从开头到末尾&…

典籍知识问答模块AI问答bug修改

一、修改流式数据处理问题 1.问题描述&#xff1a;由于传来的数据形式如下&#xff1a; event:START data:350 data:< data:t data:h data:i data:n data:k data:> data: data: data: data: data:嗯 data:&#xff0c; 导致需要修改获取正常的当前信息id并更…