当前位置: 首页 > news >正文

排序【各种题型+对应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.dtcms.com/a/286247.html

相关文章:

  • 线程控制:互斥与同步
  • IDEA高效开发:Database Navigator插件安装与核心使用指南
  • Python趣味算法:抓交通肇事犯(车牌号谜题解析)
  • nginx定制http头信息
  • 腾讯云云服务器深度介绍
  • 面试150 克隆图
  • 通缩期的 “反脆弱” 研发:新启航逆势投入 30% 营收突破 3D 白光干涉测量技术
  • 深孔加工的方法及检测方法探究 —— 激光频率梳 3D 轮廓检测
  • 29、鸿蒙Harmony Next开发:深浅色适配和应用主题换肤
  • 计算机网络基础:从协议到通信全解析(大致框架)
  • 基于 WinForm 与虹软实现人脸识别功能:从理论到实践
  • VisualXML全新升级 | 新增BusLoad计算
  • python控制linux命令反馈
  • 二刷 黑马点评 附近商户
  • 如何更改 SQLserver 数据库存储的位置 想从C盘换到D盘
  • Delphi EDI 需求分析与对接指南
  • Springboot3整合Elasticsearch8(elasticsearch-java)
  • 智和信通赋能:北京某高校校园网交换机全维度智能管控
  • 洛谷 P10112 [GESP202312 八级] 奖品分配-普及/提高-
  • 基于SpringBoot 投票系统 【源码+LW+PPT+部署】
  • Gemini Function Calling 和 Qwen3 Embedding和ReRanker模型
  • 40.限流规则
  • 用线性代数推导码分多址(CDMA)
  • 第16章 基于AB实验的增长实践——验证想法:AB实验实践
  • 【Python】一些PEP提案(三):with 语句、yield from、虚拟环境
  • 智象科技赋能金融、证券行业 IT 运维
  • Fluent许可有效期查询
  • 堆的实现,堆排序,咕咕咕
  • 几款开源的安全监控与防御工具分享
  • C++---cout、cerr、clog