[C][数据结构][排序][下][快速排序][归并排序]详细讲解

文章目录

  • 1.快速排序
    • 1.基本思想
    • 2.hoare版本
    • 3.挖坑法
    • 4.前后指针版本
    • 5.非递归版本改写
  • 2.归并排序


1.快速排序

1.基本思想

  • 任取待排序元素序列的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

2.hoare版本

请添加图片描述

  • 选key – 一般是最左边或者最右边的值
    • 左作key,让右边先走
    • 右作key,让左边先走
  • 为什么左边做key,要让右边先走?
    • 要保证相遇位置的值,比key小/就是key
      • R先走,R停下,L去遇到R
        • 相遇位置就是R停下来的位置,必定比key小
      • R先走,R没有找到比key小的值,R去遇到了L
        • 相遇位置就是L上一轮停下来的位置,比key小/就是key
  • left向前找大
  • right向后找小
  • 单趟排完以后:
    • 左边都比key小
    • 右边都比key大
  • 实现:
    void QuickSort1(int* a, int begin, int end)
    {//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > endif (begin >= end){return;}if (end - begin > 0){//hoare版本int left = begin, right = end;int keyi = left;while (left < right){//右边先走,找小while (left < right && a[right] >= a[keyi])  //left < right是为了防止越界{right--;}//左边再走,找大while (left < right && a[left] <= a[keyi]){left++;}//交换Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;//key已经正确的位置上,左边都比key小,右边都比key大//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]QuickSort1(a, begin, keyi - 1);QuickSort1(a, keyi + 1, end);}else{//直接插入排序InsertSort(a + begin, end - begin + 1);}
    }
    

3.挖坑法

请添加图片描述

  • 本质同hoare,但是好理解些
  • 右边找小,填到左边的坑里去,这个位置形成新的坑
  • 左边找大,填到右边的坑里去,这个位置形成新的坑
  • 实现:
    void QuickSort2(int* a, int begin, int end)
    {//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > endif (begin >= end){return;}if (end - begin > 0){//挖坑法int key = a[begin];int piti = begin;int left = begin, right = end;while (begin < end){//右边找小,填到左边的坑里去,这个位置形成新的坑while (begin < end && a[right] >= key){right--;}a[piti] = a[right];piti = right;//左边找大,填到右边的坑里去,这个位置形成新的坑while (begin < end && a[left] <= key){left++;}a[piti] = a[left];piti = left;}a[piti] = key;//key已经正确的位置上,左边都比key小,右边都比key大//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]QuickSort2(a, begin, piti - 1);QuickSort2(a, piti + 1, end);}else{//直接插入排序InsertSort(a + begin, end - begin + 1);}
    }
    

4.前后指针版本

请添加图片描述

  • cur向前找小,遇到小,则prev++,且交换cur、prev
  • 实现:
    void QuickSort3(int* a, int begin, int end)
    {//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > endif (begin >= end){return;}if (end - begin > 0){//前后指针版本int prev = begin;int cur = begin + 1;int keyi = begin;//加入三数取中的优化int midi = GetMidIndex(a, begin, end);Swap(&a[keyi], &a[midi]);while (cur <= end)  //一直往后走,遇到小则停下来处理{//cur找小if (a[cur] < a[keyi] && ++prev != cur)  //防止prev和cur重合时,重复交换{Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;//key已经正确的位置上,左边都比key小,右边都比key大//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]QuickSort3(a, begin, keyi - 1);QuickSort3(a, keyi + 1, end);}else{//直接插入排序InsertSort(a + begin, end - begin + 1);}
    }
    

5.非递归版本改写

  • 为何需要改写非递归?
    • 极端场景下,若深度太深,会出现栈溢出
  • 方法:用数据结构模拟递归过程
  • 实现:
    //用栈模拟递归 -- 先进后出
    void QuickSortNonR(int* a, int begin, int end)
    {Stack st;StackInit(&st);StackPush(&st, end);StackPush(&st, begin);while (!isStackEmpty(&st)){//从栈中取出两个数,作为区间int left = StackTop(&st);StackPop(&st);int right = StackTop(&st);StackPop(&st);//排序,取keyiint keyi = PartSort3(a, left, right);//此时分成了两个区间 [left, keyi-1] keyi [keyi+1, right]//继续压栈if (keyi + 1 < right){StackPush(&st, right);StackPush(&st, keyi + 1);}if (left < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, left);}}StackDestroy(&st);
    }
    
  • 特性总结:
    • 综合性能&使用场景比较好
    • 时间复杂度:O(N*logN)
    • 空间复杂度:O(logN)
    • 稳定性:不稳定
  • [快速排序]优化方案
    • 三数取中法选key
      • 防止key都是极端数字 --> 递归过深 --> 栈溢出
    • 递归到小的子区间时,考虑使用插入排序
      • 大幅减少递归次数,提高效率
      • 例:最后一层递归占了总递归次数的50%,但可能只有极少量的数

2.归并排序

请添加图片描述

  • 基本思想:

  • 分治思维

    • 将已有序的子序列合并,得到完全有序的序列
    • 即先使每个子序列有序,再使子序列段间有序。
    • 若将两个有序表合并成一个有序表,称为二路归并
  • 归并排序核心步骤:
    请添加图片描述

  • 实现:

    //函数名前加_表示这个函数是内部函数,不对外提供接口 - 子函数
    //后序思想
    void _MergeSort(int* a, int begin, int end, int* tmp)
    {if (begin >= end){return;}int mid = (begin + end) / 2;//[begin, mid] [mid+1, end] 分治递归,让子区间有序_MergeSort(a,begin,mid,tmp);_MergeSort(a,mid+1,end,tmp);//归并 [begin, mid] [mid+1, end]int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int i = begin1;while (begin1 <= end1 && begin2 <= end2)  //有一组结束则结束{if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//已经有一组结束了,拷贝另一组剩余的while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//把归并数据拷贝回原数组memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
    }//时间复杂度:O(N*logN)
    //空间复杂度:O(N)
    void MergeSort(int* a, int n)
    {int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("MergeSort");exit(-1);}_MergeSort(a, 0, n - 1, tmp);free(tmp);
    }
    
  • 非递归版本改写

  • 实现:

  • void MergeSortNonR(int* a, int n)
    {int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc:");exit(-1);}//手动归并int gap = 1;  //每次归并的元素个数while (gap < n){for (int i = 0; i < n; i += 2 * gap){//[i, i+gap-1] [i+gap,i+2*gap-1]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//越界处理 - 修正边界if (end1 >= n){end1 = n - 1;//[begin2, end2]修正为不存在区间begin2 = n;end2 = n - 1;}else if (begin2 >= n){// [begin2, end2]修正为不存在区间begin2 = n;end2 = n - 1;}else if (end2 >= n){end2 = n - 1;}//归并int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}}//全部归并完后,拷贝回原数组memcpy(a, tmp, sizeof(int) * n);gap *= 2;}free(tmp);
    }
    
  • void MergeSortNonR(int* a, int n)
    {int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc:");exit(-1);}//手动归并int gap = 1;  //每次归并的元素个数while (gap < n){for (int i = 0; i < n; i += 2 * gap){//[i, i+gap-1] [i+gap,i+2*gap-1]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//越界处理//end1越界或者begin2越界,则可以不归并了if (end1 >= n || begin2 >= n){break;}else if (end2 >= n){end2 = n - 1;}//归并int m = end2 - begin1 + 1;int j = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}while (begin1 <= end1){tmp[j++] = a[begin1++];}while (begin2 <= end2){tmp[j++] = a[begin2++];}memcpy(a + i, tmp + i, sizeof(int) * m);}gap *= 2;}free(tmp);
    }
    
  • 特性总结:

    • 更多解决在磁盘中的外排序问题
    • 时间复杂度:O(N*logN)
    • 空间复杂度:O(N) – 缺点
    • 稳定性:稳定

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

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

相关文章

技术选型考察哪些方面

在进行技术选型时&#xff0c;需要考虑多个方面&#xff0c;确保所选择的技术能够满足项目的需求&#xff0c;并且在实施过程中具备可行性和可维护性。以下是一些主要考察方面&#xff1a; 1. 业务需求匹配 功能需求&#xff1a;技术能否满足当前及未来的功能需求。性能需求&…

Leetcode.2862 完全子集的最大元素和

题目链接 Leetcode.2862 完全子集的最大元素和 rating : 2292 题目描述 给你一个下标从 1 1 1 开始、由 n n n 个整数组成的数组。你需要从 n u m s nums nums 选择一个 完全集&#xff0c;其中每对元素下标的乘积都是一个 完全平方数&#xff0c;例如选择 a i a_i ai​ 和…

目标检测中的anchor机制

目录 一、目标检测中的anchor机制 1.什么是anchor boxes&#xff1f; 二、什么是Anchor&#xff1f; ​编辑三、为什么需要anchor boxes&#xff1f; 四、anchor boxes是怎么生成的&#xff1f; 五、高宽比&#xff08;aspect ratio&#xff09;的确定 六、尺度(scale)的…

工业高温烤箱:现代工业的重要设备

工业高温烤箱&#xff0c;作为现代工业生产中不可或缺的关键设备&#xff0c;以其独特的高温烘烤能力&#xff0c;为各种工业产品的加工与制造提供了强有力的支持。斯博欣将对工业高温烤箱的原理、特点、应用领域及未来发展进行简要介绍。 一、工业高温烤箱的特点 1、高温性能优…

怎么修改Visual Studio Code中现在github账号

git config --global user.name “你的用户名” git config --global user.email “你的邮箱” git config --global --list git push -u origin your_branch_name git remote add origin

编程后端:深入探索其所属的行业领域

编程后端&#xff1a;深入探索其所属的行业领域 在数字化浪潮席卷全球的今天&#xff0c;编程后端作为技术领域的重要分支&#xff0c;其所属的行业领域一直备受关注。本文将从四个方面、五个方面、六个方面和七个方面&#xff0c;深入剖析编程后端所属的行业&#xff0c;并揭…

FastAPI 作为H5中流式输出的后端

FastAPI 作为H5中流式输出的后端 最近大家都在玩LLM&#xff0c;我也凑了热闹&#xff0c;简单实现了一个本地LLM应用&#xff0c;分享给大家&#xff0c;百分百可以用哦&#xff5e;^ - ^ 先介绍下我使用的三种工具&#xff1a; Ollama&#xff1a;一个免费的开源框架&…

2024年护网行动全国各地面试题汇总(1)作者:————LJS

目录 1. SQL注入原理 2. SQL注入分类&#xff1a; 3. SQL注入防御&#xff1a; 4. SQL注入判断注入点的思路&#xff1a; 5. 报错注入的函数有哪些&#xff1a; 6. SQL注入漏洞有哪些利用手法&#xff1a; 1. 文件上传漏洞的绕过方法有以下几种&#xff1a; 2. 文件上传时突破前…

centos7 xtrabackup mysql 基本测试(4)---虚拟机环境 mysql 修改datadir(有问题)

centos7 xtrabackup mysql 基本测试&#xff08;4&#xff09;—虚拟机环境 mysql 修改datadir 参考 centos更改mysql数据库目录 https://blog.csdn.net/sinat_33151213/article/details/125079593 https://blog.csdn.net/jx_ZhangZhaoxuan/article/details/129139499 创建目…

锌,能否成为下一个“铜”?

光大期货认为&#xff0c;今年以来&#xff0c;市场关注锌能否接棒铜价牛市。铜需求增长空间大&#xff0c;而锌消费结构传统&#xff0c;缺乏新亮点。虽然在供应的扰动上锌强于铜&#xff0c;但因需求乏善可陈&#xff0c;金融属性弱势&#xff0c;锌很难接棒铜&#xff0c;引…

数据质量守护者:数据治理视角下的智能数据提取策略

一、引言 在信息化和数字化高速发展的今天&#xff0c;数据已成为企业决策、运营和创新的核心要素。然而&#xff0c;随着数据量的快速增长和来源的多样化&#xff0c;数据质量问题逐渐凸显&#xff0c;成为制约企业数据价值发挥的关键因素。数据治理作为确保数据质量、提升数…

KEIL5.39 5.40 fromelf 不能生成HEX bug

使用AC6 编译,只要勾选了生成HEX。 结果报如下错误 暂时没有好的解决办法 1.替换法 2.在编译完后用命令生成HEX

蚓链研究院告诉你:蚓链数字化营销如何帮助力助你打造品牌!

在打造产品品牌的过程中&#xff0c;数字化营销会带来哪些利弊影响&#xff1f;如何消除或减少弊端&#xff1f;蚓链来和你一起分析、解决。 利处&#xff1a; 1.高度精准的目标定位&#xff1a;凭借大数据和先进算法&#xff0c;能精确锁定潜在客户&#xff0c;使营销资源得到…

k8s_探针专题

关于探针 生产环境中一定要给pod设置探针,不然pod内的应用发生异常时,K8s将不会重启pod。 需要遵循以下几个原则(本人自己总结,仅供参考): 探针尽量简单,不要消耗过多资源。因为探针较为频繁的定期执行,过于复杂和消耗资源的探针对k8s和生产环境是不利的。探针返回的结…

数栈xAI:轻量化、专业化、模块化,四大功能革新 SQL 开发体验

在这个数据如潮的时代&#xff0c;SQL 已远远超越了简单的查询语言范畴&#xff0c;它已成为数据分析和决策制定的基石&#xff0c;成为撬动企业智慧决策的关键杠杆。SQL 的编写和执行效率直接关系到数据处理的速度和分析结果的深度&#xff0c;对企业洞察市场动态、优化业务流…

针对k8s集群已经加入集群的服务器进行驱逐

例如k8s 已经有很多服务器&#xff0c;现在由于服务器资源过剩&#xff0c;需要剥离一些服务器出来 查找节点名称&#xff1a; kubectl get nodes设置为不可调度&#xff1a; kubectl cordon k8s-node13恢复可调度 kubectl uncordon k8s-node13在驱逐之前先把需要剥离驱逐的节…

File及典型案例

File File对象表示一个路径&#xff0c;可以是文件的路径&#xff0c;也可以是文件夹的路径 这个路径可以是存在的&#xff0c;也允许不存在 常见的构造方法 图来自黑马程序员网课 package com.lazyGirl.filedemo;import java.io.File;public class Demo1 {public static vo…

立式护眼台灯十大品牌哪个好?立式护眼台灯十大品牌排行

立式护眼台灯十大品牌哪个好?根据国际市场的研究数据表明&#xff0c;我国在日常生活中对电子产品的依赖度极高&#xff0c;每天看电子产品的时间超过8小时&#xff0c;出现眼睛酸痛、干涩、视觉疲劳的人群也不再少数&#xff0c;而给眼睛带来伤害的除了电子产品中所含的蓝光之…

Vue3-滑动到最右验证功能

1、思路 1、在登录页面需要启动向右滑块验证 2、效果图 3、文章地址&#xff1a;滑动验证码的实现-vue-simple-verify 2、成分分析 1、由三块构成&#xff0c;分别是底部条、拖动条、拖动移动部分 2、底部条&#xff1a;整体容器&#xff0c;包括背景、边框和文字&#xf…