排序【各种题型+对应LeetCode习题练习】

目录

常用排序

快速排序

LeetCode 912 排序数组

归并排序

LeetCode 912 排序数组


常用排序

名称排序方式时间复杂度是否稳定
快速排序分治O(n log n)
归并排序分治O(n log n)
冒泡排序交换O(n²)
插入排序插入O(n²)
选择排序选择最值O(n²)
C++ STL sort快排+内省排序O(n log n)
C++ STL stable_sort归并排序O(n log n)

快速排序

快速排序的核心思想是分治法,所以我们先来了解什么是分治

分治(Divide and Conquer) 是一种常见的算法设计思想,核心是三步:
Divide(划分)
把一个大问题划分成若干个小问题,通常是递归进行的。
Conquer(解决)
把每个子问题单独解决,直到子问题足够小,可以直接解决。
Combine(合并)
将子问题的解合并,得到原问题的解。

快速排序就是一个经典的分治应用:

步骤具体操作
Divide(划分)从数组中选一个 基准值pivot,把数组划分成两部分:
左边:所有小于等于 pivot 的元素
右边:所有大于 pivot 的元素
Conquer(递归排序)对左子数组和右子数组分别递归快速排序
Combine(组合)排序完成后,把左右子数组 + pivot 合成一个完整的有序数组

快速排序 C++ 手写模板(双指针+原地交换)

void quickSort(vector<int>& nums, int left, int right) {if (left >= right) return;  // 递归终止条件int pivot = nums[left];     // 选择最左侧元素作为基准int i = left + 1, j = right;while (i <= j) {// 找到左边大于 pivot 的位置while (i <= j && nums[i] <= pivot) i++;// 找到右边小于 pivot 的位置while (i <= j && nums[j] > pivot) j--;if (i < j) swap(nums[i], nums[j]);  // 交换两个指针位置的值}swap(nums[left], nums[j]);  // 将 pivot 放到正确位置quickSort(nums, left, j - 1);  // 排左边quickSort(nums, j + 1, right); // 排右边
}

调用方式:

vector<int> nums = {4, 2, 7, 1, 5};
quickSort(nums, 0, nums.size() - 1);

LeetCode 912 排序数组

思路:
1. 选择一个基准值 pivot(通常选最左边的值)
2. 双指针 i / j 从两边往中间找:
i 找第一个大于 pivot 的数
j 找第一个小于等于 pivot 的数
如果 i < j,就交换
3. 最后把 pivot 放到分界点位置 → 即 j 位置
4. 递归地快排左边、右边子数组
 

class Solution {
public:void quickSort(vector<int>& nums, int left, int right) {if (left >= right) return;int pivot = nums[left];     // 选最左边为 pivotint i = left + 1, j = right;while (i <= j) {while (i <= j && nums[i] <= pivot) i++; while (i <= j && nums[j] > pivot) j--;  if (i < j) swap(nums[i], nums[j]);      }swap(nums[left], nums[j]);  quickSort(nums, left, j - 1);  quickSort(nums, j + 1, right); }vector<int> sortArray(vector<int>& nums) {quickSort(nums, 0, nums.size() - 1);return nums;}
};

上面的代码的基准值 pivot 选择了最左边的值,如果提交到LeetCode,会有部分案例超出时间限制,比如nums = [3, 2, 2, 2, ..., 2]   这里的2有上千个,快速排序在该极端情况下退化成 O(n²) 
因为数组中大量元素与 pivot 相等,导致“递归深度很深”、“每次只处理一点点”,最终就退化成了冒泡排序,时间复杂度变为 O(n²)。

解决方案是改成随机选 pivot,防止极端退化
这种随机选择基准值位置的方法在全是 2 的子数组中能够高概率选中中间位置的 2 作为基准值,划分后左右子数组长度 ≈ n/2,时间复杂度平均为 O(n log n)

class Solution {
public:void quickSort(vector<int>& nums, int left, int right) {if (left >= right) return;// 随机选一个 pivot,避免最坏情况int randomIndex = rand() % (right - left + 1) + left;swap(nums[left], nums[randomIndex]);int pivot = nums[left];int i = left + 1, j = right;while (i <= j) {while (i <= j && nums[i] <= pivot) i++;while (i <= j && nums[j] > pivot) j--;if (i < j) swap(nums[i], nums[j]);}swap(nums[left], nums[j]);quickSort(nums, left, j - 1);quickSort(nums, j + 1, right);}vector<int> sortArray(vector<int>& nums) {srand(time(0)); quickSort(nums, 0, nums.size() - 1);return nums;}
};

但是上面的时间复杂度是平均为 O(n log n),可以使用三路快排,它针对大量重复值时更高效,即小于 pivot 的放左边,等于 pivot 的放中间,大于 pivot 的放右边
这种方法时间复杂度始终为 O(n log n)

class Solution {
public:void quickSort3Way(vector<int>& nums, int left, int right) {if (left >= right) return;// 随机选 pivot,放到最左边int randomIndex = rand() % (right - left + 1) + left;swap(nums[left], nums[randomIndex]);int pivot = nums[left];int lt = left;       // 小于 pivot 的最后位置int i = left + 1;    // 当前扫描的位置int gt = right;      // 大于 pivot 的起始位置while (i <= gt) {if (nums[i] < pivot) {swap(nums[i], nums[lt + 1]);lt++;i++;} else if (nums[i] > pivot) {swap(nums[i], nums[gt]);gt--;} else {  // nums[i] == pivoti++;}}swap(nums[left], nums[lt]);// 递归左右两边quickSort3Way(nums, left, lt - 1);quickSort3Way(nums, gt + 1, right);}vector<int> sortArray(vector<int>& nums) {srand(time(0));quickSort3Way(nums, 0, nums.size() - 1);return nums;}
};

上面代码将数组划分为三部分:
[left, lt-1](小于pivot)、[lt, gt](等于pivot)、[gt+1, right](大于pivot)

假设输入为 [3, 2, 2, 2, 2, 4, 1]
pivot = 2
lt 区(<2):[1]
= 区(=2):[2,2,2,2]
gt 区(>2):[3,4]
递归只需要再排 [1] 和 [3,4],大量相等的值不需要再递归,消除了重复元素对性能的影响。

下面来模拟三路快排的划分过程
数组为 [3, 2, 2, 2, 2, 4, 1]  假设 pivot = 2(随机选中了值为 2 的元素,放到了最左边位置)

快排前准备状态:

变量说明
pivot2基准值,随机选后放最左边
lt0< pivot 区右边界
i1当前正在看的元素位置
gt6> pivot 区左边界
nums[2, 3, 2, 2, 2, 4, 1]初始数组,pivot = 2 放在最左边

三路快排过程跟踪表

步骤i 指向值操作numsltgti
13> pivot → 与 gt=6 交换[2, 1, 2, 2, 2, 4, 3]051
21< pivot → 与 lt+1 交换[2, 1, 2, 2, 2, 4, 3] (1 与自己)152
32= pivot → i++[2, 1, 2, 2, 2, 4, 3]153
42= pivot → i++[2, 1, 2, 2, 2, 4, 3]154
52= pivot → i++[2, 1, 2, 2, 2, 4, 3]155
64> pivot → 与 gt=5 交换[2, 1, 2, 2, 2, 4, 3](不变)145

最后i=5 > gt=4  跳出循环
循环结束后处理 pivot
swap(nums[left], nums[lt]) → 把 pivot 放回等于区前端
交换前:[2, 1, 2, 2, 2, 4, 3]
交换后:[1, 2, 2, 2, 2, 4, 3]

最终分区情况

区域元素含义
< pivot 区[1]nums[0]
= pivot 区[2, 2, 2, 2]nums[1~4]
> pivot 区[4, 3]nums[5~6]

然后递归:
左边:quickSort3Way(nums, 0, 0) → 单个元素,无需处理
右边:quickSort3Way(nums, 5, 6) → 处理 [4, 3]

归并排序

归并排序是稳定排序的经典算法,时间复杂度稳定为 O(n log n),适用于大规模数据的排序。
归并排序采用的是分治法

归并排序流程
假设要排序的数组为 [38, 27, 43, 3, 9, 82, 10]

分解阶段:
将数组分成两半: [38, 27, 43] 和 [3, 9, 82, 10]
继续递归分解,直到每个子数组只剩一个元素。

合并阶段:
合并 [38] 和 [27],得到 [27, 38]
合并 [43] 和 [27, 38],得到 [27, 38, 43]
对右半部分继续类似的操作。

最终合并:
将两个有序的子数组 [27, 38, 43] 和 [3, 9, 10, 82] 合并成一个有序的数组 [3, 9, 10, 27, 38, 43, 82]

LeetCode 912 排序数组

思路:
1. 使用 归并排序 的方法:
递归地将数组分解为两部分。
合并时保持数组的顺序。
2. 合并时,比较左右两部分的元素,确保数组有序。

class Solution {
public:
void merge(vector<int>& nums, int left, int mid, int right) {int n1 = mid - left + 1;  // 左子数组的大小int n2 = right - mid;     // 右子数组的大小vector<int> leftArr(n1), rightArr(n2);  // 创建两个临时数组// 复制数据到临时数组for (int i = 0; i < n1; i++) leftArr[i] = nums[left + i];for (int i = 0; i < n2; i++) rightArr[i] = nums[mid + 1 + i];// 合并两个临时数组到原数组numsint i = 0;      // 左子数组索引int j = 0;      // 右子数组索引int k = left;   // 原数组起始索引while (i < n1 && j < n2) {  // 如果左子数组和右子数组还有元素if (leftArr[i] <= rightArr[j]) {nums[k] = leftArr[i];  // 左边小的放到原数组i++;} else {nums[k] = rightArr[j]; // 右边小的放到原数组j++;}k++;}// 复制剩余的元素while (i < n1) {   nums[k] = leftArr[i];i++;k++;}while (j < n2) {  nums[k] = rightArr[j];j++;k++;}
}void mergeSort(vector<int>& nums, int left, int right) {if (left >= right) return;int mid = left + (right - left) / 2;// 递归分割数组mergeSort(nums, left, mid);mergeSort(nums, mid + 1, right);// 合并两个有序子数组merge(nums, left, mid, right);}vector<int> sortArray(vector<int>& nums) {mergeSort(nums, 0, nums.size() - 1);return nums;}
};

nums = [5, 2, 3, 1] 

初始数组:[5,2,3,1]
调用 mergeSort(0,3)
├─ 计算 mid=1
├─ 调用 mergeSort(0,1)  // 处理 [5,2]
│  ├─ 计算 mid=0
│  ├─ 调用 mergeSort(0,0) → 终止([5])
│  ├─ 调用 mergeSort(1,1) → 终止([2])
│  └─ 合并 → [2,5]
├─ 调用 mergeSort(2,3)  // 处理 [3,1]
│  ├─ 计算 mid=2
│  ├─ 调用 mergeSort(2,2) → 终止([3])
│  ├─ 调用 mergeSort(3,3) → 终止([1])
│  └─ 合并 → [1,3]
└─ 合并 [2,5] 和 [1,3] → [1,2,3,5]


尚未完结

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

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

相关文章

鸿蒙与web混合开发双向通信

鸿蒙与web混合开发双向通信用runJavaScript和registerJavaScriptProxy web entry/src/main/resources/rawfile/1.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content&q…

unity Physics.RaycastNonAlloc

Physics.RaycastNonAlloc 是 Unity 中用于 3D 物理射线检测的高性能方法&#xff0c;它是 Physics.Raycast 的非分配版本。 方法签名 public static int RaycastNonAlloc(Ray ray, RaycastHit[] results, float maxDistance Mathf.Infinity, int layerMask DefaultRaycastLay…

数据库(five day finally)——物物而不物于物,念念而不念于念。(数据库到此结束!祝世间美好与各位不期而遇,善意常伴汝身!)

1.子查询&#xff08;1&#xff09;where 子查询①多行单列配合in和not in操作&#xff08;类似于数据范围查询&#xff09;例&#xff1a;显示工资与各个经理相同的雇员信息&#xff08;包含经理本身&#xff09;。select * from empwhere sal(select sal from emp where jobM…

【甲烷数据集】Sentinel-5P 卫星获取的全球甲烷数据集-TROPOMI L2 CH₄

目录 数据概述 传感器 & 卫星信息 监测目标:甲烷(CH₄) 数据产品内容 空间与时间覆盖 云筛选与协同观测 技术文档资源 数据下载 Python 代码绘制 CH4 数据 参考 数据概述 Sentinel-5 Precursor Level 2 Methane (TROPOMI L2 CH₄) 数据集是由欧洲哥白尼计划的 Sentinel…

【数据结构】单链表练习(有环)

1.判断是否是环形链表 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; bool hasCycle(struct ListNode *head) {struct ListNode *fast,*slow;fastslowhead;while(fast&&fast->next){fastfast->next->next;slowslow->next;if(fastslow)return tr…

VR 污水厂初体验:颠覆传统认知​

第一次戴上 VR 设备走进 VR 污水厂时&#xff0c;那种震撼的感觉至今难以忘怀。仿佛一瞬间&#xff0c;我被传送到了一个全新的世界&#xff0c;平日里只能在图纸或实地看到的污水厂&#xff0c;此刻就立体地呈现在眼前。脚下是纵横交错的管道&#xff0c;头顶巨大的处理设备有…

父类 div 自适应高度 子类如何撑满其高度

使用绝对定位 如果你想要子元素完全撑满父元素的高度&#xff0c;可以使用绝对定位。这种方法适用于当子元素需要完全覆盖父元素时。<div class"parent"><div class"child"><!-- 子类内容 --></div> </div>.parent {positio…

从0开始学习R语言--Day51--PH检验

在用cox回归做分析时&#xff0c;我们一般会得出各种变量在结局的风险影响&#xff08;HR大于1&#xff0c;就代表变量值增大&#xff0c;对应结局影响的风险就随之增大&#xff09;&#xff0c;但是这里有个坏处是&#xff0c;cox回归得到的是瞬时风险值&#xff0c;我们最多得…

Docker 网络原理

Linux 常见网络虚拟化 虚拟网卡:tun/tap虚拟网卡&#xff08;又称虚拟网络适配器&#xff09;&#xff0c;即用软件模拟网络环境&#xff0c;模拟网络适配器。在计算机网络中&#xff0c;tun 与 tap 是操作系统内核中的虚拟网络设备。不同于普通靠硬件网络适配器实现的设备&…

【通识】PCB文件

1. PCB文件的导入 在PORTEL99 PCB编辑器的文件菜单中选择导入先前绘制的CAD文件。导入成功后&#xff0c;编辑器将显示出元件封装的基本图形&#xff0c;为后续操作奠定基础。将需要抄板的PCB放置于扫描仪中随后启动扫描仪&#xff0c;之后启动AUTO CAD软件&#xff0c;之后插入…

分布式弹性故障处理框架——Polly(1)

1 前言之服务雪崩 在我们实施微服务之后&#xff0c;服务间的调用变得异常频繁&#xff0c;多个服务之前可能存在互相依赖的关系&#xff0c;当某个服务出现故障或者是因为服务间的网络出现故障&#xff0c;导致服务调用的失败&#xff0c;进而影响到某个业务服务处理失败&…

【机器学习深度学习】大模型推理速度与私有化部署的价值分析

目录 前言 一、主流推理框架速度对比 二、为什么 HuggingFace 框架更适合微调验证&#xff1f; 三、大模型私有化部署的必要性分析 ✅ 私有化部署的主要动因 1. 数据隐私与业务安全 2. 可控性与性能保障 ❌ 哪些情况不建议私有部署&#xff1f; 四、总结与选型建议 &…

elementui-admin构建

1、vue-element-admin vue-element-admin是基于element-ui 的一套后台管理系统集成方案。 功能&#xff1a;介绍 | vue-element-adminA magical vue adminhttps://panjiachen.github.io/vue-element-admin-site/zh/guide/# GitHub地址&#xff1a;https://github.com/PanJia…

深入排查:编译环境(JDK)与运行环境(JRE/JDK)不一致时的常见 Java 错误及解决方案

深入排查&#xff1a;编译环境&#xff08;JDK&#xff09;与运行环境&#xff08;JRE/JDK&#xff09;不一致时的常见 Java 错误及解决方案 在后端 Java 项目中&#xff0c;编译环境&#xff08;JDK&#xff09; 与 运行环境&#xff08;JRE/JDK&#xff09; 版本不一致&…

[JS逆向] 微信小程序逆向工程实战

博客配套代码与工具发布于github&#xff1a;微信小程序 &#xff08;欢迎顺手Star一下⭐&#xff09; 相关爬虫专栏&#xff1a;JS逆向爬虫实战 爬虫知识点合集 爬虫实战案例 逆向知识点合集 前言&#xff1a; 微信小程序对于很多尝试JS逆向的人群来说&#xff0c;都是一个…

基于5G系统的打孔LDPC编码和均匀量化NMS译码算法matlab性能仿真

目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.算法涉及理论知识概要 4.1打孔技术 4.2 均匀量化NMS译码 5.参考文献 6.完整算法代码文件获得 1.引言 在5G通信系统中&#xff0c;信道编码技术是保障高速率、高可靠性数据传输的核心支撑&#xff0c;而低…

基于Java标准库读取CSV实现天地图POI分类快速导入PostGIS数据库实战

目录 前言 一、天地图POI分类简介 1、数据表格 2、分类结构 二、从CSV导入到PG数据库 1、CSV解析流程 2、数据转换及入库 3、入库成果及检索 三、总结 前言 在之前的博客中&#xff0c;曾经对高德地图和百度地图的POI分类以及使用PostGIS数据库来进行管理的模式进行了详…

人-AI交互中的信息论不同于传统的信息论,其信息的增量≠不确定性的减量

在人机交互&#xff08;Human-AI Interaction, HAI&#xff09;领域&#xff0c;信息论的应用确实与传统的信息论有所不同。这种差异主要源于人机交互HAI中信息的复杂性、动态性以及人类认知的特点。1. 传统信息论的核心概念传统信息论由克劳德香农&#xff08;Claude Shannon&…

K8s 通过 Scheduler Extender 实现自定义调度逻辑

1. 为什么需要自定义调度逻辑 什么是所谓的调度? 所谓调度就是指给 Pod 对象的 spec.nodeName 赋值 待调度对象则是所有 spec.nodeName 为空的 Pod 调度过程则是从集群现有的 Node 中为当前 Pod 选择一个最合适的 实际上 Pod 上还有一个平时比较少关注的属性&#xff1a;…

7.19 换根dp | vpp |滑窗

lcr147.最小栈通过两个栈 维护实现class MinStack { public:stack<int> A, B;MinStack() {}void push(int x) {A.push(x);if(B.empty() || B.top() > x)B.push(x);}void pop() {if(A.top() B.top())B.pop();A.pop();}int top() {return A.top();}int getMin() {retur…