【算法系列篇】分治-快排

news2024/11/28 20:45:48

在这里插入图片描述

文章目录

  • 前言
  • 什么是分冶
  • 1.颜色分类
    • 1.1 题目要求
    • 1.2 做题思路
    • 1.3 Java代码实现
  • 2. 排序数组
    • 2.1 题目要求
    • 2.2 做题思路
    • 2.3 Java代码实现
  • 3.数组中的第k个最大元素
    • 3.1 题目要求
    • 3.2 做题思路
    • 3.3 Java代码实现
  • 4. 最小的k个数
    • 4.1 题目要求
    • 4.2 做题思路
    • 4.3 Java代码实现
  • 总结

前言

我相信看到这里很多人都学过八大排序了吧,其中快速排序是一种非常高效的排序方式,那么今天我们将会使用快速排序的算法来解决实际生活中的某些问题。

什么是分冶

分治算法是一种算法设计策略,它将大问题分解成更小的子问题,并通过解决子问题来解决原始问题。分治算法的基本思想是将问题分解成若干个规模较小但结构与原问题相似的子问题,然后递归地解决这些子问题,最后再将子问题的解合并得到原问题的解。

一般而言,分治算法可以分为三个步骤:

  1. 分解(Divide):将原问题划分成若干个规模较小且相互独立的子问题,通常通过递归方式实现。

  2. 解决(Conquer):递归地解决子问题。如果子问题的规模足够小,无需继续分解,直接求解并返回结果。

  3. 合并(Merge):将子问题的解合并成原问题的解。这一步骤通常涉及对子问题解的操作,以得到原问题的解。

分治算法的典型应用包括排序算法(如快速排序和归并排序)、查找算法(如二分查找)、图算法(如最大子数组和、最短路径问题)等。

分治算法的优点在于它能够高效地解决某些复杂问题,尤其适用于可以被划分为多个子问题的情况。通过将问题分解为更小的子问题,分治算法可以减少问题的规模,简化问题的解决过程。

然而,需要注意的是,并非所有问题都适合采用分治算法。在使用分治算法时,需要保证子问题相对独立且可以有效地解决。此外,分治算法在涉及大量递归调用时可能会带来额外的开销,因此在设计算法时需要注意递归深度与性能之间的平衡。

我们今天使用的快速排序的算法则是很好的利用了分冶将大事化小的思想来解决问题的,将整个数组分为若干小区间来进行排序,最终得到我们想要的结果。

1.颜色分类

https://leetcode.cn/problems/sort-colors/

1.1 题目要求

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

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

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

示例 1:

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

示例 2:

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

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i] 为 0、1 或 2

进阶:

你能想出一个仅使用常数空间的一趟扫描算法吗?

class Solution {
    public void sortColors(int[] nums) {

    }
}

1.2 做题思路

前面学习的快速排序,每一趟排序过程会以一个数为基准,使最终结果这个基准值的左边小于等于这个基准值,右边部分都是大于这个基准值,所以这个题目我们同样可以使用这种快排的思想,以1为基准,然后用 i 来遍历数组,left 指针以及 left 指针左边都是0,right 指针以及 right 指针右边部分都是2。left 一开始的位置指向 -1,right 指针指向 n(数组大小),当 i 所指向的数据小于 1 的时候,就先将 left++ ,然后将left 所指的内容与 i 所指的内容交换位置,交换结束之后,i++;如果 i 所指向的内容等于 1 的之后,直接i++;如果 i 指向的内容大于 1 ,则先需要将 right–,然后交换right 与 i 所指向的内容,但是这里交换完成之后,i 不能++,因为与 right 指向的内容交换位置之后,i 所指向的内容是 i 没有遍历过的,如果 i++,那么这个数字将会被跳过。
在这里插入图片描述

1.3 Java代码实现

class Solution {
    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }

    public void sortColors(int[] nums) {
        int n = nums.length;
        int left = -1,right = n,i = 0;
        while(i < right) {
            if(nums[i] < 1) swap(nums,++left,i++);
            else if(nums[i] == 1) i++;
            else swap(nums,--right,i);
        }
    }
}

在这里插入图片描述

2. 排序数组

https://leetcode.cn/problems/sort-an-array/

2.1 题目要求

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

示例 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 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104
class Solution {
    public int[] sortArray(int[] nums) {

    }
}

2.2 做题思路

这道题就很简单明了,直接将数组进行升序排序,我们可以使用分冶的思想,讲整个数组分为 n 个部分,然后在这 n 个小部分中使用快排的思想进行排序,需要注意的是,如果数组趋于有序的话,快速排序的时间复杂度会下降到 O(N^2) ,所以我们可以对快速排序进行优化,优化的方式有很多:三数取中等等,这里我们使用的方式是随机取基准值的方法吗,这样能使快排的时间复杂度基本趋于 O(N*logN)。

2.3 Java代码实现

class Solution {
    public int[] sortArray(int[] nums) {
        qsort(nums,0,nums.length-1);
        return nums;
    }

    private void qsort(int[] nums, int l, int r) {
        if(l >= r) return; //递归结束的条件
        int left = l-1,right = r + 1, i = l;
        //在[l,r]区间内,随机取一个数作为基准值
        int key = nums[new Random().nextInt(r - l + 1) + l];
        while(i < right) {
            if(nums[i] < key) swap(nums,++left,i++);
            else if(nums[i] == key) i++;
            else swap(nums,--right,i);
        }
        //当将基准值排序到最终位置之后,还需要将基准位置左右两边部分继续排序
        qsort(nums,l,left);
        qsort(nums,right,r);
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
}

在这里插入图片描述

3.数组中的第k个最大元素

https://leetcode.cn/problems/kth-largest-element-in-an-array/

3.1 题目要求

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104
class Solution {
    public int findKthLargest(int[] nums, int k) {

    }
}

3.2 做题思路

要想找到数组中的第k个最大元素,我们能想到的还是将数组进行排序,然后从大到小找到第k个元素。这道题目可以使用堆排序,创建出大小为 k 的小根堆。但是我们不使用堆排序的方法,而是使用分冶-快排的方法来解决。如何使用快排的方式来解决呢?同样是先找一个元素作为基准值,进行快排,将数组分为 a——小于基准值的部分、b——等于基准值的部分和c——大于基准值的部分,因为要找到第 k 个最大的元素,所以首先我们需要在大于基准的部分中找这个元素是否存在,如果 c 部分的长度大于等于 k ,则说明这个部分中存在第 k 大的元素,然后我们在这个部分中继续寻找;如果 c 的长度小于 k ,并且 b + c 的长度大于等于 k,那么我们可以直接返回 b 部分的元素,因为 c 部分的长度小于 k ,所以这个第 k 大的元素存在于 b 部分,而 b 部分都是等于基准值的部分,可以直接返回;如果前面两种情况都不存在,那么这个第 k 大的元素就在 a 部分,我们需要在 a 部分中找到第 k - b - c 大的元素,这个操作跟前面的递归操作类似。

在这里插入图片描述

3.3 Java代码实现

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return qsort(nums,0,nums.length-1,k);
    }

    private int qsort(int[] nums, int l, int r, int k) {
        int key = nums[new Random().nextInt(r - l + 1) + l];
        int left = l-1, right = r + 1, i = l;
        while(i < right) {
            if(nums[i] < key) swap(nums,++left,i++);
            else if(nums[i] == key) i++;
            else swap(nums,--right,i);
        }
        //c表示大于key的部分,b表示等于key的部分,剩下的部分就是小于key的部分
        int c = r - right + 1;
        int b = right - left - 1;
        if(c >= k) return qsort(nums,right,r,k);
        else if(b + c >= k) return key;
        else return qsort(nums,l,left,k - b - c);
    }

    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

在这里插入图片描述

4. 最小的k个数

https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/

4.1 题目要求

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

  • 0 <= k <= arr.length <= 10000
  • 0 <= arr[i] <= 10000
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {

    }
}

4.2 做题思路

因为这道题目没有要求要按照元素的大小顺序返回,所以我们可以模仿上面的第 k 个最大元素的思路进行分冶-快排的算法,对数组进行简单的排序,并且将数组分为:a——小于基准值的部分,b——等于基准值的部分,c——大于基准值的部分。
如果a > k,则需要在 a 部分中继续递归,找到最小的 k 个数;如果a <= k,但是 a + b >= k ,因为 b 部分都是相等的数据,所以可以直接返回;如果前面两种情况都不符合的话,就还需要在 c 部分中继续进行排序,直到在 c 部分中找到 第k - a -b小的元素,然后该位置之前的部分就是我们需要的最小的 k 个数。

4.3 Java代码实现

class Solution {
    public int[] getLeastNumbers(int[] nums, int k) {
        qsort(nums,0,nums.length-1,k);
        int[] ret = new int[k];
        for(int i = 0; i < k; i++) ret[i] = nums[i];
        return ret;
    }

    private void qsort(int[] nums, int l, int r, int k) {
        int key = nums[new Random().nextInt(r - l + 1) + l];
        int left = l - 1, right = r + 1,i = l;
        while(i < right) {
            if(nums[i] < key) swap(nums,++left,i++);
            else if(nums[i] == key) i++;
            else swap(nums,--right,i);
        }
        int a = left - l + 1,b = right - left - 1;
        if(a > k) qsort(nums,l,left,k);
        else if(a + b >= k) return;
        else qsort(nums,right,r,k - a - b);
    }

    private void swap(int[] nums, int i, int j) {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
}

在这里插入图片描述

总结

通过本篇博客,我们深入了解了分治算法以及其在快速排序算法中的应用。快速排序是一种高效的排序算法,它利用了分治策略,将大问题逐步分解为规模较小的子问题,并通过递归地解决和合并子问题来完成整个排序过程。

快速排序算法的核心思想是选择一个基准元素,将待排序数组分割成两个子数组,一个小于等于基准的子数组和一个大于基准的子数组。然后,递归地对两个子数组进行排序,最后合并得到最终的有序数组。

快速排序算法具有以下优点:

  1. 高效性:快速排序算法的平均时间复杂度为O(nlogn),在实际应用中表现出良好的性能。它通过不断地将数组划分为较小的子数组进行排序,从而减少了比较和交换的次数。

  2. 原地排序:快速排序算法可以在原数组上进行排序,不需要额外的辅助空间。这对于内存受限的环境来说具有重要意义。

然而,快速排序算法也存在一些注意事项和局限性:

  1. 对于初始数组的选择敏感:快速排序算法的性能高度依赖于选择的基准元素。最理想的情况是选择一个能够将数组划分成大小相似的子数组的基准元素,以避免出现最坏情况的时间复杂度。

  2. 递归深度:在快速排序算法中,递归调用的深度取决于划分操作的方式和基准元素的选择。当数组中存在大量重复元素时,可能会导致递归深度增加,影响算法的性能。

总结而言,快速排序算法是一种高效、原地排序的算法,通过分治策略实现了对待排序数组的快速排序。它在实践中被广泛使用,具有较好的性能。然而,需要根据具体问题选择合适的基准元素,并考虑递归深度对算法性能的影响。

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

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

相关文章

前端面试题合集(一)

前端面试题合集 1.js异步方案2.文件上传如何限制文件类型3. 说出 与的区别4.多维数组如何降维5.如何给一个按钮绑定两个onclick事件 1.js异步方案 js异步方法分为两种&#xff0c;分别为defer和async,如果没有写其中一种的话代码从上到下同步执行&#xff0c;遇到脚本代码之后…

埋头干活不会汇报,别说 996 就算 007 也没用!

​ 见字如面&#xff0c;我是军哥&#xff01; 经调研发现 80% 的程序员认为工作汇报就是形式主义&#xff0c;无聊至极&#xff0c;但是我要和你说&#xff0c;做好工作汇报非常重要&#xff0c;这直接关系到你在这家公司能否快速成长和晋升加薪&#xff0c;而且要告诉你一件扎…

vue3:18、Pinia持久化(pinia-plugin-persistedstate)

安装插件 npm i pinia-plugin-persistedstate main.js中引入 import { createApp } from vue import { createPinia } from pinia import App from ./App.vue import piniaPluginPersistedstate from pinia-plugin-persistedstate // createApp(App).use(CreatePinia()).mou…

Day58|leetcode 739. 每日温度、496.下一个更大元素 I

今天开始单调栈&#xff01; leetcode 739. 每日温度 题目链接&#xff1a;739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;单调栈&#xff0c;你该了解的&#xff0c;这里都讲了&#xff01;LeetCode:739.每日温度_哔哩哔哩_bilibili 题目概述 …

Unity——脚本与导航系统

Unity内置了一个比较完善的导航系统&#xff0c;一般称为Nav Mesh&#xff08;导航网格&#xff09;&#xff0c;用它可以满足大多数游戏中角色自动导航的需求。 一、导航系统相关组件 Unity的导航系统由以下几个部分组成&#xff1a; Nav Mesh。Nav Mesh与具体的场景关联&…

【postgresql 基础入门】数据库服务的管理,启动、停止、状态查看、配置加载、重启都在这里

数据库服务管理 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 ​开源贡献&#xff1a; toadb开源库 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff…

【精读Uboot】反汇编分析SPL的_main函数

1、简介 典型的Uboot启动分为两个阶段&#xff0c;bootrom->SPL&#xff08;Secondary Program Loader&#xff09;->ATF->OPTEE(可选)->Uboot。其中SPL为BL2&#xff0c;ATF为BL31&#xff0c;OPTEE为BL32&#xff0c;Uboot为BL33。其中bootrom是固化在芯片内部的…

MySQL 8.0.34(x64)安装笔记

一、背景 从MySQL 5.6到5.7&#xff0c;再到8.0&#xff0c;版本的跳跃不可谓不大。安装、配置的差别也不可谓不大&#xff0c;特此备忘。 二、过程 &#xff08;1&#xff09;获取MySQL 8.0社区版&#xff08;MySQL Community Server&#xff09;   从 官网 字样 “MySQL …

3D印刷电路板在线渲染查看工具

从概念上讲&#xff0c;这是有道理的&#xff0c;因为PCB印制电路板上的走线从一个连接到下一个连接的路线基本上是平面的。 然而&#xff0c;我们生活在一个 3 维世界中&#xff0c;能够以这种方式可视化电路以及相应的组件&#xff0c;对于设计过程很有帮助。本文将介绍KiCad…

VsCode Ctrl+.修复无效

vscode 快速修复(quick fix) 快捷键(Ctrl .)被占用问题解决方法_vscode快速修复快捷键_追求者2016的博客-CSDN博客

Android 系统源码目录frameworks/base/packages和packages/apps下的APP区别

概要 在 Android Open Source Project (AOSP) 源代码中&#xff0c;frameworks/base/packages 和 packages/apps 目录都包含 Android 系统中的应用程序&#xff0c;但它们在性质和用途上有一些区别&#xff1a; 1&#xff0c;frameworks/base/packages frameworks/base 目录…

OMRON G9SP和NB触摸屏使用232口通讯

G9SP和NB触摸屏使用232口通讯 实验时间&#xff1a;2023/9/7 实验设备&#xff1a;G9SP-N20S、CP1W-CIF01&#xff08;232串口选减板&#xff09;、NB5Q-TW00B、XW2Z-200T&#xff08;串口线&#xff09;&#xff0c;CP1W-20EDT1&#xff0c;D4GS-N4T&#xff08;安全门开关&a…

alibaba国际版阿里巴巴API接入说明(阿里巴巴商品详情+关键词搜索商品列表)

API地址:https://o0b.cn/anzexi 调用示例&#xff1a;https://api-gw.onebound.cn/alibaba/item_get/?keytest_api_key& &num_iid60840463360&&langzh-CN&secret 参数说明 通用参数说明 url说明 https://api-gw.onebound.cn/平台/API类型/ 平台&#xf…

Golang 方法使用的注意事项和细节

方法的声明(定义) furie (recevier type) methodName (参数列表) (返回值列表){方法体return返回值 } 1)参数列表&#xff1a;表示方法输入 2) recevier type:表示这个方法和type这个类型进行绑定&#xff0c;或者说该方法作用于type类型 3) receiver type:type可以是结构体…

mysql 安全加固

PS&#xff1a;之前在做安全测试的时候&#xff0c;报告mysql有安全漏洞&#xff0c;于是研究了下如何修复&#xff0c;于是记录下来分享给大家 1.1修改mysql 存放位置 修复 1.停服务 service mysqld stop2.迁位置 2.1 新建迁移目录 mkdir /home/database2.2 迁移数据文件…

深入探索KVM虚拟化技术:全面掌握虚拟机的创建与管理

文章目录 安装KVM开启cpu虚拟化安装KVM检查环境是否正常 KVM图形化创建虚拟机上传ISO创建虚拟机加载镜像配置内存添加磁盘能否手工指定存储路径呢&#xff1f;创建成功安装完成查看虚拟机 KVM命令行创建虚拟机创建磁盘通过命令行创建虚拟机手动安装虚拟机 KVM命令行创建虚拟机-…

如何基于国标GB28181视频平台EasyGBS国标云服务平台建设智慧环保在线监测系统

EasyGBS平台可提供流媒体接入、处理、转发等服务&#xff0c;支持内网、公网的安防视频监控设备通过国标GB/T28181协议进行视频监控直播。基于视频图像的环保监督管理智能监控系统&#xff0c;结合了计算机技术、AI、云计算、网络传输技术和网络存储技术等先进技术&#xff0c;…

CloudQuery X PolarDB:让数据库管理更简单

前言&#xff1a;8 月 15 日&#xff0c;CloudQuery 数据操作管控平台与阿里云 PolarDB 数据库管理软件&#xff0c;完成产品集成认证测试。也在以下功能上完善了用户使用 PolarDB 的体验&#xff0c;使数据库的管理更加安全高效。 支持在 CloudQuery 中创建连接&#xff0c;便…

如何学习python?比较通义千问、文心一言、ChatGPT给的答案,你就知道啦

通义千问 通义千问是阿里巴巴达摩院自主研发的超大规模语言模型&#xff0c;能够回答问题、创作文字&#xff0c;还能表达观点、撰写代码。通义千问的能力覆盖自然语言处理的多个领域&#xff0c;包括语言理解、文本生成、代码写作等。通义千问在多项性能指标上达到了业界领先水…

springcloudSeata处理分布式事务之1.7.0

1.5.0之后版本发生了很大改变 1.seata安装 1.1官网地址 http://seata.io/zh-cn/ 1.2下载地址 https://github.com/seata/seata/releases 下载的是seata-server-1.7.0.zip 1.3seata相关配置的修改 seata-server-1.7.0\seata\conf下的application.yml进行修改 server:por…