详解八大排序(六)------(三路划分,自省排序,归并排序外排序)
文章目录
- 1. 快排之三路划分
- 1. 1 三路划分的诞生由来
- 1. 2 三路划分的具体思路
- 1. 3 代码实现
- 2. 快排之自省排序
- 2. 1 自省排序的目的
- 2. 2 自省排序的思路
- 2. 3 自省排序的实现代码
- 3. 归并排序外排序
- 3. 1 外排序介绍
- 3. 2 归并排序外排序的思路
- 3. 3 归并排序的实现代码
1. 快排之三路划分
1. 1 三路划分的诞生由来
通过详解八大排序(三)------(快速排序)的介绍。我们可以知道,决定快排性能的关键是key的寻找:若是每次寻找的key都能将数组二分之一,那么快排的性能最佳;若是每次寻找的key都是将数组分成 1 和 N-1 个数,那么快排的时间复杂度就是O(N^2)。
通过 前后指针 和 寻找随机key 我们可以解决绝大部分数组的找key需求。但是也会有一小部分的数组需求没有实现。
例如:

在面对含有大量重复数据的数组时,hoare版本 和 lomuto版本 的性能都有一定程度的退化。而三路划分是针对大量重复数据的找key方法。
1. 2 三路划分的具体思路
三路划分的核心思路可以理解为 hoare版本 和 lomuto版本 的结合:将数组划分成三个区域,比key小的区域A,等于key的区域B,大于key的区域C。
通过左右指针的方式,将小于key的数放进区域A,大于key的数放进区域C。
再对区域A和C进行划分。直到全部划分完成。
具体的实现思路:
-
key默认取left位置的值。
-
left指向区间最左边,right指向区间最后边,cur指向left+1位置。

-
cur遇到比key小的值后跟left位置交换,换到左边,left++,cur++。
例如上面的数组,由于1 < 6,所以1换到6的位置上。

一直重复,直到 left 交换完。得到:
- cur遇到比key大的值后跟right位置交换,换到右边,right-- 。
接着以上述数组举例。左边数组交换完成,接着交换右边数组:

重复交换,得到:

-
cur遇到跟key相等的值后,cur++。
-
直到cur > right结束
最终得到:

这样就将数组分成了三个区域。
1. 3 代码实现
//三路划分
void ThreeWaySort(int* arr, int left, int right)
{if (left >= right){return;}//取一个随机值当keyint randi = left + (rand() % (right - left + 1));Swap(&arr[left], &arr[randi]);int key = arr[left];int cur = left + 1;int begin = left,end = right;while (cur <= right){if (arr[cur] < key){Swap(&arr[left++], &arr[cur++]);}else if (arr[cur] > key){Swap(&arr[cur], &arr[right--]);}else {cur++;}}ThreeWaySort(arr, begin, left - 1);ThreeWaySort(arr, right + 1, end);
}
2. 快排之自省排序
2. 1 自省排序的目的
自省排序的诞生和三路划分相似,都是原有的排序在面对新的问题时,出现效率不足的情况。
三路划分面对数组中含有大量重复数据的情况,具有显著的性能提升。但是在其他情况下就很一般,同时三路划分相比一般的快排具有一定的损耗。
这个时候就有自省排序的出现。
2. 2 自省排序的思路
自省排序的本质很简单。就是通过一个深度检测器—可以理解为当前排序的时间复杂度检测器。判断当前快排的深度有没有超过logn。超过了就说明当前排序的效率是有问题的。然后接下来过程就不再使用快排,而是使用堆排序进行排序。
2. 3 自省排序的实现代码
void AdJustDown(int* arr,int sz,int parent)
{int child = parent * 2 + 1;while (child < sz){if (child + 1 < sz && arr[child + 1] > arr[child]){++child;}if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* arr, int sz)
{for (int i = (sz - 1 - 1) / 2; i >= 0; --i){AdJustDown(arr, sz, i);}int end = sz - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdJustDown(arr, end, 0);--end;}
}void InsertSort(int* arr, int sz)
{for (int i = 1; i < sz; i++){int end = i - 1;int tmp = arr[i];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];--end;}else {break;}}arr[end + 1] = tmp;}
}void IntroSort(int* arr,int left,int right,int depth,int defaultDepth)
{if (left > right){return;}if (right - left + 1 < 16)//当需要排序的数据总数小于16,就可以使用选择排序,这时这个排序的效率是最高的{InsertSort(arr + left,right - left + 1);return;}if (depth > defaultDepth)//深度检测器。深度大于一定程度,调用堆排序{HeapSort(arr + left, right - left + 1);return;}depth++;int begin = left, end = right;int randi = left + (rand() % (right - left + 1));Swap(&arr[left], &arr[randi]);//随机数取keyint prev = left, cur = prev + 1;int keyi = left;while (cur <= right){if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[prev], &arr[keyi]);keyi = prev;//使用的是lomuto版本的快排IntroSort(arr,begin,keyi - 1,depth,defaultDepth);//depth是当前的深度,defaultDepth是预定的深度。//depth超过dedaultDepth就说明深度有问题IntroSort(arr,keyi + 1,end,depth,defaultDepth);
}void IntroQuickSort(int* a, int left, int right)
{int depth = 0;int logn = 0;int N = right - left + 1;for (int i = 1; i < N; i *= 2){logn++;//计算目标深度}IntroSort(a, left, right, depth, logn * 2);
}
3. 归并排序外排序
3. 1 外排序介绍
外排序(External Sorting)是指能处理极大量数据的排序算法。通常来说,外排序处理的数据不能一次装入内存,只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采用的是⼀种“排序-归并”的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到⼀个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。然后在归并阶段将这些临时文件组合为⼀个大的有序文件,也即排序结果。
3. 2 归并排序外排序的思路
- 读取n个值排序后写到file1,再读取n个值排序后写到file2。
- file1和file2利用归并排序的思想,依次读取比较,取小的尾插到mfile,mfile归并为一个有序文件。
- 将file1和file2删掉,mfile重命名为file1。
- 再次读取n个数据排序后写到file2。
- 继续走file1和file2归并,重复步骤2,直到文件中无法读出数据。最后归并出的有序数据放到了file1中。

3. 3 归并排序的实现代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}int compare(const void* a,const void* b)
{return (*(int*)a - *(int*)b);
}int ReadNDataSortToFile(FILE* fout, int n, const char* file1)
{int* a = (int*)malloc(sizeof(int) * n);//创建好一个数组放下抽取出的数据if (a == NULL){perror("malloc error!");return 0;}int x = 0;//创建一个数用来记录抽取出的数据int j = 0;for (int i = 0; i < n; i++){if (fscanf(fout, "%d", &x) == EOF)//从文件中抽取的数据break;//如果x == EOF,说明文件中所有的数均已被抽出,跳出循环a[j++] = x;//j的大小等于已经被抽取的数据}if (a == 0)//如果上面的程序正常运行,而数组a是空的,说明这个文件是空的,已经没有数据了{free(a);//那么就不需要继续抽取文件,直接退出return 0;}qsort(a, j, sizeof(int), compare);//给抽出的数据排好序//打开文件,file1和file2用来存储抽取出的排序好的数据,mfile用来存储归并好的两个文件/*const char* file1 = "file1.txt";const char* file2 = "file2.txt";const char* mfile = "mfile.txt";*/FILE* fin = fopen(file1, "w");//创建一个fin文件,用来放置排好序的数据if (fin == NULL){free(a);perror("fopen error!");return 0;}for (int i = 0; i < j; i++){//阅读文件fprintf(fin, "%d\n", a[i]);//将数据放置进文件中}free(a);//释放数组fclose(fin);//关闭文件return j;//返回的是实际读取到的数据个数。没有数据就返回0。
}void MergeSortFile(const char* file1, const char* file2, const char* mfile)
{//创建好三个临时文件,用来存储排序好的数据FILE* fout1 = fopen(file1, "r");if (fout1 == NULL){perror("fopen1 error!");return 0;}FILE* fout2 = fopen(file2, "r");if (fout2 == NULL){perror("fopen2 error!");return 0;}//但是你要往这个文件中写入,就应该用w的方式打开FILE* mfin = fopen(mfile, "w");//这里用w的方式打开if (mfin == NULL){perror("mfin error!");return 0;}//下面是利用归并排序思路。把比较的数据换成打开文件的形式int x1 = 0, x2 = 0;int ret1 = fscanf(fout1, "%d", &x1);//把fout1的第一个数据赋值给retint ret2 = fscanf(fout2, "%d", &x2);//把fout2的第一个数据赋值给ret//将两个文件的数据进行比较,由小到大依次放进mfile文件中while (ret1 != EOF && ret2 != EOF){if (x1 < x2){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);//上一个数据放置完成。赋值下一个数据}else{fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}}//上面while跳出的条件,意味着有一个文件的数据没有比较完成。这里对剩余数据进行判断while (ret1 != EOF){fprintf(mfin, "%d\n", x1);ret1 = fscanf(fout1, "%d", &x1);}while (ret2 != EOF){fprintf(mfin, "%d\n", x2);ret2 = fscanf(fout2, "%d", &x2);}//判断完成,需要关闭文件 /*文件使用三部曲1.打开文件2.读取文件3.关闭文件*/fclose(fout1);fclose(fout2);fclose(mfin);
}//创建一个含有随机N个数据的文件
void CreateNData()
{int n = 1000000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file,"w");if (fin == NULL){perror("fopen error!");return 0;}for (int i = 0; i < n; i++){int x = rand() + i;fprintf(fin, "%d\n", x);}fclose(fin);
}int main()
{//CreateNData();//创建三个文件,用来排序const char* file1 = "file1.txt";const char* file2 = "file2.txt";const char* mfile = "mfile.txt";//给待排序数组创建一个临时文件FILE* fout = fopen("data.txt", "r");if (fout == NULL){perror("fout fopen error!");return 0;}int m = 100000;//这个是读取要排序的数据,从fout里读取,是读取到这个file1和file2这个文件中,//每个文件读取m个数据ReadNDataSortToFile(fout, m,file1);//这个fout是一个已经打开的文件,怎么能再次打开呢ReadNDataSortToFile(fout, m, file2);//由于data.txt的文件非常大,一次排序不了全部的数据,所以要多次排序while (1)//循环多次排序{//把这个file1和file2中的数据在归并到这个mfile中MergeSortFile(file1, file2, mfile);//删除file1和file2remove(file1);remove(file2);//将mfile重命名为file1rename(mfile, file1);int n = 0;if ((n = ReadNDataSortToFile(fout, m, file2)) == 0)//n=0,说明文件中已经没有数据需要排序了break;}return 0;
}
相关文章:
详解八大排序(六)------(三路划分,自省排序,归并排序外排序)
文章目录 1. 快排之三路划分1. 1 三路划分的诞生由来1. 2 三路划分的具体思路1. 3 代码实现 2. 快排之自省排序2. 1 自省排序的目的2. 2 自省排序的思路2. 3 自省排序的实现代码 3. 归并排序外排序3. 1 外排序介绍3. 2 归并排序外排序的思路3. 3 归并排序的实现代码 1. 快排之三…...
【Java从入门到放弃 之 从字节码的角度异常处理】
从字节码的角度异常处理 生成字节码Javap 命令的使用基本语法 字节码文件testTryCatchtestTryCatchFinallytestTryWithResource 如果大家对与java的异常使用还有问题或者还不太了解,建议先看一下我之前写的Java异常了解一下基本 的异常处理知识,再看这篇…...
Java虚拟机(JVM)中的元空间(Metaspace)一些关键点的总结
• 元空间的引入:在Java 8中,JVM的内存结构经历了变化,其中方法区被替代为元空间(Metaspace)。元空间用于存储类的元数据信息,包括类的名称、方法、字段等信息。 • 存储位置:与方法区不同&…...
小程序 模版与配置
WXML模版语法 一、数据绑定 1、数据绑定的基本原则 (1)在data中定义数据 (2)在WXML中使用数据 2、在data中定义页面的数据 3、Mustache语法的格式(双大括号) 4、Mustache语法的应用场景 (…...
当大的div中有六个小的div,上面三个下面三个,当外层div高变大的时候我希望里面的小的div的高也变大
问: 当大的div中有六个小的div,上面三个下面三个,当外层div高变大的时候我希望里面的小的div的高也变大 回答: 这时候我们就不能写死六个小的div的高度,否则上下的小的div的间距就会变大,因为他们的高度…...
MySQL——操作
一.库的操作 1.基本操作 创建数据库 create database 数据库名称; 查看数据库 show databases; 删除数据库 drop database 数据库名称; 执行删除之后的结果: 数据库内部看不到对应的数据库 对应的数据库文件夹被删除,级联删除,里面的数据表全部被删…...
Python语法之正则表达式详解以及re模块中的常用函数
正则表达式详解及re模块中的常用函数 概念、作用和步骤 概念: 本身也是一个字符串,其中的字符具有特殊含义,将来我们可以根据这个字符串【正则表达式】去处理其他的字符串,比如可以对其他字符串进行匹配,切分…...
《地球化学》
《地球化学》主要报道近代地球化学, 特别是其主要分支学科, 如岩石地球化学、元素地球化学、有机地球化学、环境地球化学、矿床地球化学、实验地球化学、生物地球化学、天体化学、计算地球化学、分析地球化学、海洋地球化学、沉积地球化学、纳米地球化学、油气地球化学和同位素…...
alpine openssl 编译
./config no-shared --prefix/usr/local/openssl apk add musl-dev gcc g apk add linux-headers ssh root 登录 编辑 SSH 配置文件 打开 SSH 配置文件 /etc/ssh/sshd_config: vi /etc/ssh/sshd_config PermitRootLogin yes...
【AI模型对比】AI新宠Kimi与ChatGPT的全面对比:技术、性能、应用全揭秘
文章目录 Moss前沿AI技术背景Kimi人工智能的技术积淀ChatGPT的技术优势 详细对比列表模型研发Kimi大模型的研发历程ChatGPT的发展演进 参数规模与架构Kimi大模型的参数规模解析ChatGPT的参数体系 模型表现与局限性Kimi大模型的表现ChatGPT的表现 结论:如何选择适合自…...
【C#设计模式(17)——迭代器模式(Iterator Pattern)】
前言 迭代器模式可以使用统一的接口来遍历不同类型的集合对象,而不需要关心其内部的具体实现。 代码 //迭代器接口 public interface Iterator {bool HashNext();object Next(); } //集合接口 public interface Collection {Iterator CreateIterator(); } //元素迭…...
二、部署docker
二、安装与部署 2.1 安装环境概述 Docker划分为CE和EE,CE为社区版(免费,支持周期三个月),EE为企业版(强调安全,付费使用)。 Docker CE每月发布一个Edge版本(17.03&…...
FFmpeg 4.3 音视频-多路H265监控录放C++开发十九,ffmpeg封装
封装就是将 一个h264,和一个aac文件重新封装成一个mp4文件。 这里我们的h264 和 aac都是来源于另一个mp4文件,也就是说,我们会将 in.mp4文件解封装成一路videoavstream 和 一路 audioavstream,然后 将这两路的 avstream 合并成一…...
ML 系列:第 39 节 - 估计方法:最大似然估计 (MLE)
目录 一、说明 二、什么是最大似然估计 (MLE)? 2.1 理解公式 2.2 MLE 的定义 2.3 我们何时使用 MLE? 三、结论 一、说明 在统计学领域,我们经常需要根据观察到的数据估计统计模型的参数。为此目的广泛使用的两种关键方法是最大似然估计 ( MLE…...
Linux 权限管理:用户分类、权限解读与常见问题剖析
🌟 快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。🌟 🚩用通俗易懂且不失专业性的文字,讲解计算机领域那些看似枯燥的知识点🚩 目录 💯L…...
网络原理之 UDP 协议
目录 1. UDP 协议报文格式 2. UDP 的特点 (1) 无连接 (2) 不可靠 (3) 面向数据报 (4) 全双工 3. 基于 UDP 的应用层协议 前文是:UDP 的使用 首先了解一下基础知识: 1. UDP 协议报文格式 传输层最重要的协议有两个,一个是 TCP&#x…...
并发框架disruptor实现生产-消费者模式
Disruptor是LMAX公司开源的高性能内存消息队列,单线程处理能力可达600w订单/秒。本文将使用该框架实现生产-消费者模式。一、框架的maven依赖 <!-- https://mvnrepository.com/artifact/com.lmax/disruptor --><dependency><groupId>com.lmax<…...
【Vivado】xdc约束文件编写
随手记录一下项目中学到的约束文件编写技巧。 时序约束 创建生成时钟 参考链接: Vivado Design Suite Tcl Command Reference Guide (UG835) Vivado Design Suite User Guide: Using Constraints (UG903) 通过Clocking Wizard IP创建的时钟(MMCM或…...
Redis使用场景-缓存-缓存雪崩
前言 之前在针对实习面试的博文中讲到Redis在实际开发中的生产问题,其中缓存穿透、击穿、雪崩在面试中问的最频繁,本文加了图解,希望帮助你更直观的了解缓存雪崩😀 (放出之前写的针对实习面试的关于Redis生产问题的博…...
概率论相关知识随记
作为基础知识的补充,随学随记,方便以后查阅。 概率论相关知识随记 期望(Expectation)期望的定义离散型随机变量的期望示例:掷骰子的期望 连续型随机变量的期望示例:均匀分布的期望 期望的性质线性性质期望的…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...
门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

