[Lc7_分治-快排] 分治 | 颜色分类 | 快速排序

server/2025/3/9 19:56:33/

目录

分治

1.颜色分类

题解

2. 快速排序

题解


分治

分治思想就如同它的名字一样:分而治之

  • 将一个大问题 划分成若干个相同或者相识的子问题。
  • 然后在将子问题在划分成若干个相同或者相识的子问题,运用递归,直到划分到不能在划分。
  • 然后 解决子问题,子问题都解决完了,大问题也就被解决完了。
  • 快速排序和归并排序就用的分治思想。

  • 以前我们学快速排序 是在数组中 选择一个基准元素,然后将数组分成左右两个区间,左区间比基准元素小,右区间比基准元素大。
  • 然后 递归的去左区间和右区间 再选择基准值继续排序,这种做法是将数组分成了两份。
  • 但是 对于重复元素非常多的数组  即使使用快速排序也会超时。

因此这里我们学习新的划分方法,将数组划分成三份。

还是选一个基准元素将数组划分成三份

  • 左区间元素都比基准元素小。
  • 中间区间元素和基准元素相同。
  • 右区间元素都比基准元素大。

因为 中间都是等于key的一定就是在最终位置,所以接下来递归还是 只考虑左区间和右区间。


1.颜色分类

题目链接:75. 颜色分类

题目分析:

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

题解

说起来这道题并不是真正的分治快速排序,而是把数组按照一定规则划分成三块。

  • 当把这道题解决后,快排写的就非常简单。
  • 这道题就相当于选择一个基准元素1,把小于1的放左边,大于1的放右边,等于1的放中间。
  • [<1] [=1] [>1]

双指针可以将数组分成两块,具体怎么分,可见双指针系列第一道题移动零([Lc(1)双指针_1] 移动零 | 复写零 | 快乐数)

  • 这里我们需要三个指针将数组分成三块!

每个指针的作用:

  • i指针:遍历整个数组
  • left:标记 0 区域的最右侧
  • right:标记 2 区域的最左侧

三个指针将数组分成4份:

  • [0,left] :全都是0
  • [left+1,i-1]:全都是1
  • [i,right-1]:待扫描的元素
  • [rigth+1,n-1]:全都是2

接下来就讨论nums[i]是0或1或2应该怎么办?

当nums[i]为0的时候,要把0加入到左边区域,left指向的是 0 最右侧区域,此时left+1,然后将 i 位置和 left+1 元素交换,然后i+1。

  • 还有一种极端情况 i 就在 left+1的为位置,并且正好是0。但是这种处理方法和上面一样。
  • 当nums[i]为1的时候,i 指针往后走就行了
  • 当nums[i]为2的时候,我们要将2加入到右边区间,也就是加入到 right - 1 的位置。

交换 i 位置和 right -1 位置的元素。但是此时需要注意的是 交换给 i 位置的元素是待扫描的元素,因此 i 指针不能往后走!

class Solution {
public:void sortColors(vector<int>& nums) {//三指针int left=-1,i=0;int n=nums.size(),right=n;
//初始化:left和right 都要初始化在 数组之外while(i<right){if(nums[i]==0)swap(nums[++left],nums[i++]);else if(nums[i]==1)i++;else if(nums[i]==2)swap(nums[--right],nums[i]);}}
};

2. 快速排序

题目链接:912. 排序数组

题目描述:

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

题解

  1. 在数组中选择一个基准元素key
  2. 根据key将数组分成左右区间,左区间元素小于等于key,右区间元素大于key。
  3. 这个key就处于排序的最终位置。然后在将左区间排排序,右区间排排序

重复上述过程。整体就有序了。时间复杂度(nlogn)

但是如果数组都是 重复元素此时选择基准元素间 将数组分成左右两区间就不行了。时间复杂度退化成O(n^2)

所以我们学习一个更优的做法,将 数组分三块 思想来实现快速排序

  1. 我们先选一个基准元素key,将数组分成三块。
  2. 左区间小于key,中间区间等于key,右区间大于key。
  3. 中间区间已经在排序后的最终位置,所以只用去去左区间排序,右区间排序。

重复上述过程,整体就有序了。

数组如何分三块和颜色分类一模一样。

  • 定义一个i 指针 扫描数组
  • left指针 指向左区间小于key的最右侧
  • right指针 指向右区间大于key的最左侧。(left 和 right 最开始都要 置空)

然后分情况讨论就好了。

即使数组全部都是重复元素,我们经过一次数组划分,整个数组都是中间区间,左右区间不存在,也不用在递归下去了,直接结束。时间复杂度O(n)

优化:用随机的方式选择基准元素

之前常用的三数取中,还是不够随机。

  • 要想 时间复杂度逼近O(nlogn) (有 logN 层,每层的划分操作 耗时趋近于 N) 就要用随机的方式选择基准元素。
  • 随机选择就是让数组中每个元素被选择的概率相同,然后返回被选择的元素。
  • 通过 随机基准元素,以此来避免极端情况,来维持 递归树的 logN 层
  • 使用产生随机数的函数 rand(),让生成的随机数%这个区间的长度,然后加上left,

是在这个区间内的随机数的下标,然后返回对应下标的元素。

C++的随机数种子埋法

  srand((unsigned int)time(nullptr));
//后 调用 rand(),即是 随机数了

完整代码:

双指针法

#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n = nums.size();srand((unsigned int)time(nullptr));QuickSort(nums.data(), 0, n-1); // 关键修正1:传递vector的裸指针return nums;}void QuickSort(int* a, int begin, int end) {if(begin >= end) return;int keyi = PartSort(a, begin, end); // 关键修正2:分区逻辑调整QuickSort(a, begin, keyi-1);QuickSort(a, keyi+1, end);}int PartSort(int* a, int left, int right) {int pivot_idx = rand() % (right - left + 1) + left; // 关键修正3:正确随机范围swap(a[pivot_idx], a[left]);int pivot = a[left];// 双指针法分区int i = left, j = right;while (i < j) {while (i < j && a[j] >= pivot) j--; // 从右找小while (i < j && a[i] <= pivot) i++; // 从左找大if (i < j) swap(a[i], a[j]);}swap(a[left], a[i]); // 基准归位return i;}
};

三指针法

class Solution {
public:vector<int> sortArray(vector<int>& nums){int n=nums.size();//种 随机数 种子srand((unsigned int)time(nullptr));QuickSort(nums,0,n-1);//使用 nums.data() 获取vector的裸指针return nums;}void QuickSort(vector<int>& nums,int l,int r){if(l>=r) return;//递归 终止条件//数组分三块int key=nums[rand()%(r-l+1)+l];int i=l,left=l-1,right=r+1;//置空 之外//通过 设置参数的合规性 来实现分治while(i<right)//当 还存在 未处理的时候{if(nums[i]<key)swap(nums[++left],nums[i++]);else if(nums[i]==key)i++;elseswap(nums[--right],nums[i]);}//递归//通过栈,先进后出,来实现对于 重复性工作 化大为小的计算QuickSort(nums,l,left);//左边界--左指针位置QuickSort(nums,right,r);}
};

http://www.ppmy.cn/server/173745.html

相关文章

【大学生体质】智能 AI 旅游推荐平台(Vue+SpringBoot3)-完整部署教程

智能 AI 旅游推荐平台开源文档 项目前端地址 ☀️项目介绍 智能 AI 旅游推荐平台&#xff08;Intelligent AI Travel Recommendation Platform&#xff09;是一个利用 AI 模型和数据分析为用户提供个性化旅游路线推荐、景点评分、旅游攻略分享等功能的综合性系统。该系统融合…

三次握手与四次挥手

三次握手 三次握手&#xff08;Three-way Handshake&#xff09;其实就是指建立一个TCP连接时&#xff0c;需要客户端和服务器总共发送3个数据包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备 三…

八点八数字科技:AI数字人引领智慧文旅新时代

在数字化浪潮席卷全球的今天&#xff0c;八点八数字科技凭借其领先的AI数字人技术&#xff0c;正在为文旅行业带来一场前所未有的变革。作为全球领先的IP智能体服务商&#xff0c;八点八数字科技通过自主研发的AI数字人全息舱、数字人一体机等创新产品&#xff0c;为智慧文旅注…

docker 若依plus cloud 部署 oss配置失败

这里是说明我遇到的问题 检查思路 1、获取列表的时候报错 原因&#xff1a;获取列表他会先拿你图片列表里面的service字段去创建容器。 创建的流程是用service当key去redis里面查询你的配置 所以&#xff0c;如果你能保证你service 里面的key的配置是对的&#xff0c;那就…

JJJ:linux sysfs相关

文章目录 1.sysfs&#xff08;属性&#xff09;文件的创建、读、写1.1 创建流程1.2 open流程1.3 read流程 2.补充2.1 sysfs下常见目录介绍2.2 属性相关2.2.1 简介2.2.2 attribute文件的创建 2.3 sysfs目录如何创建的 1.sysfs&#xff08;属性&#xff09;文件的创建、读、写 1…

Python自学指南:从入门到进阶(第一天)

Python作为一门简洁、易读且功能强大的编程语言&#xff0c;深受初学者和专业开发者的喜爱。无论你是编程新手&#xff0c;还是有一定编程经验想学习新语言&#xff0c;Python都是一个绝佳的选择。本文将为你提供一份详细的Python自学指南&#xff0c;帮助你从入门到进阶。 --…

【图像阈值分割、区域分割、边缘分割】

图像阈值分割、区域分割、边缘分割 目录 图像阈值分割、区域分割、边缘分割目标知识点1. 图像分割概述2. 阈值分割&#xff08;Thresholding&#xff09;3. 基于区域的分割&#xff08;Region-based Segmentation&#xff09;4. 基于边缘的分割&#xff08;Edge-based Segmenta…

python数据分析课实验4

import matplotlib matplotlib.use(TkAgg) import matplotlib.pyplot as plt #创建绘图对象 plt.figure(figsize(6,4)) #绘制一条红色的线型为":"的水平直线 #y:float型&#xff0c;表示水平线在y轴位置&#xff0c;默认值0。linestyle:指定直线的样式&#xff0c;可…