124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法

目录

1.小区间优化

测试代码

运行结果

2.非递归的解决方法(重要!)

递归产生的问题

一般来说,递归改非递归有两种方法

算法分析

递归产生的二叉树

栈的示意图

先写代码框架

再填写细节部分


1.小区间优化

回顾121.【C语言】数据结构之快速排序(未优化的Hoare排序存在的问题)以及时间复杂度的分析文章的一般情况下快排的时间复杂度

类似二叉树, 下面分析二叉树的特点

注意到二叉树的最后一层节点个数为2^{n-1}近似占总节点个数2^n-1的一半(有关二叉树计算方面的知识参见100.【C语言】数据结构之二叉树的基本知识文章),因此越往二叉树的下方,递归的次数越多, 一般情况下快排递归近似二叉树,如果能适当减少递归的次数,可以提高效率

解决掉最后一层能减少一半递归次数,解决掉最后三层能减少\frac{1}{2}+ \frac{1}{4}+ \frac{1}{8} =87.5\%的递归次数!!

可以想到一个方法:越往二叉树的下方,子数组的元素个数越少,不用递归,用其他的排序方法

要注意到递归到小区间的特点,数组的大部分元素是有序的(即局部有序),选择合适的排序方法要利用好这一特点

目前讲过的排序方法有:

1.冒泡排序:118.【C语言】数据结构之排序(堆排序和冒泡排序) 点我跳转

2.选择排序:117.【C语言】数据结构之排序(选择排序) 点我跳转

3.直接插入排序:112.【C语言】数据结构之排序(详解插入排序) 点我跳转

4.希尔排序:115.【C语言】数据结构之排序(希尔排序) 点我跳转

5.堆排序:118.【C语言】数据结构之排序(堆排序和冒泡排序) 点我跳转

下面讲如何选择:

1.首先排除堆排序,堆排序要先建堆,耽误时间

2.再排除希尔排序,希尔排序是对直接插入排序的优化,由于数组的元素个数较少,没有必要使用希尔排序

3.再排除选择排序:无论最好还是最坏情况,时间复杂度都为O(N^2),而且当是小区间排序时数组的大部分元素是有序的,没有商量的余地,时间复杂度还是O(N^2)

4.冒泡排序 VS 直接插入排序

冒泡排序,最坏情况时间复杂度为O(N^2),最好情况(有序)时间复杂度为O(N)

直接插入排序,最坏情况时间复杂度为O(N^2),最好情况(有序)时间复杂度为O(N)

从时间复杂度上比较,看起来两者没有区别,但是要依据数组的实际情况来看这个问题!

从小区间的特点(数组的大部分元素是有序的(即局部有序:小段小段有序))上来看,选直接插入排序较好,原因:插入排序利用了局部有序这一特点,但是冒泡排序只是机械地将数组中最大、次大......的数依次移动,没有利用这一特点,所以直接插入排序较好

当然也可以稍加改造116.【C语言】测试排序性能的模板代码文章的测试性能的代码来比较冒泡排序和直接插入排序

例如区间长度大于10使用Hoare排序,区间长度小于等于10使用冒泡排序和直接插入排序

测试代码

void QuickSort_Hoare(int* arr, int left, int right)
{if (left >= right)return;if ((right - left + 1) <= 10)//添加这行代码return;//单趟快速排序int begin = left;int end = right;//随机选keysrand((unsigned int)time(0));int rand_i = left+rand() % (right - left);Swap(&arr[left], &arr[rand_i]);int ret = GetMiddleNum(left, right, arr);if (left != ret){Swap(&arr[left], &arr[ret]);}int key_i = left;while (left < right){//由于key_i==left,因此right指针先走//右边找小while (left < right && arr[right] >= arr[key_i]){right--;}//左边找大while (left < right && arr[left] <= arr[key_i]){left++;}Swap(&arr[left], &arr[right]);}Swap(&arr[key_i], &arr[left]);key_i = left;//arr[key_i]和arr[left]交换后下标要改变,否则会对下次递归产生不利结果QuickSort_Hoare(arr, begin, key_i - 1);QuickSort_Hoare(arr, key_i + 1,end);
}void TestTime()
{const int N = 500000;int* a1 = (int*)malloc(sizeof(int) * N);if (a1 == NULL){perror("malloc");return;}int* a2 = (int*)malloc(sizeof(int) * N);if (a2 == NULL){perror("malloc");return;}for (int i = 0; i < N; i++){a1[i] = rand();}QuickSort_Hoare(a1, 0, N - 1);for (int i = 0; i < N; i++){a2[i] = a1[i];}printf("区间长度大于10的排序完毕\n");clock_t begin1 = clock();BubbleSort(a1, N);//BubbleSort函数代码省略clock_t end1 = clock();printf("BubbleSort's time=%ldms\n", end1 - begin1);clock_t begin2 = clock();InsertSort(a2, N);//InsertSort函数代码省略clock_t end2 = clock();printf("InsertSort's time=%ldms\n", end2 - begin2);free(a1);free(a2);
}int main()
{TestTime();return 0;
}

运行结果

从运行的时间上看,插入排序占很大的优势!

则Hoare排序代码应该修改为

void QuickSort_Hoare(int* arr, int left, int right)
{if (left >= right)return;//小区间直接插入排序if ((right - left + 1) > 10){//单趟快速排序int begin = left;int end = right;//随机选keysrand((unsigned int)time(0));int rand_i = left + rand() % (right - left);Swap(&arr[left], &arr[rand_i]);int ret = GetMiddleNum(left, right, arr);if (left != ret){Swap(&arr[left], &arr[ret]);}int key_i = left;while (left < right){//由于key_i==left,因此right指针先走//右边找小while (left < right && arr[right] >= arr[key_i]){right--;}//左边找大while (left < right && arr[left] <= arr[key_i]){left++;}Swap(&arr[left], &arr[right]);}Swap(&arr[key_i], &arr[left]);key_i = left;//arr[key_i]和arr[left]交换后下标要改变,否则会对下次递归产生不利结果QuickSort_Hoare(arr, begin, key_i - 1);QuickSort_Hoare(arr, key_i + 1, end);}else{InsertSort(arr+left, right - left + 1);}
}

注意InsertSort(arr+left, right - left + 1);的写法!!

长度小于等于10的区间不一定都在数组的两端,可能在中间,因此需要提供该区间第一个元素的下标arr+left,一共(right-left+1)各元素,由于是闭区间注意一定要+1!

2.非递归的解决方法(重要!)

递归产生的问题

1.效率(这个影响较小)

2.深度太深,栈会溢出!(严重的问题)

一般来说,递归改非递归有两种方法

1.改循环,可以参见L25.【LeetCode笔记】 三步问题的四种解法(含矩阵精彩解法!)文章和35.【C语言】详解函数递归

2.使用辅助改成循环(★)

快速排序算法复杂,改非递归需要用到

回顾有关栈的一系列操作

参见99.【C语言】数据结构之栈(含栈的源码)文章

算法分析

递归产生的二叉树

先看递归产生的二叉树的一个图例,用某个算法取出key_i的值

可以看到:快速排序中递归的本质是:区间在变化! 显然可以用栈来存区间

栈的示意图

将上方二叉树图转化为栈图

可以看到类似二叉树的前序遍历:先对该区间快速排序, 相当于访问根,再左区间快速排序,相当于访问左子树,最后访问右区间

(回顾前序遍历知识点参见106.【C语言】数据结构之二叉树的三种递归遍历方式文章),因此将99.【C语言】数据结构之栈文章的栈的源码嵌入快速排序中即可

先写代码框架

栈的初始化和销毁

void QuickSort_Hoare_Use_Stack(int* arr, int left, int right)//非递归,使用栈辅助改循环
{ST st;STInit(&st);//do_somethingSTDestory(&st);
}

再填写细节部分

注意:区间的边界值的入栈顺序和出栈顺序时是有讲究的!

如果先对区间的边界入栈,再对区间的边界入栈,那么出栈时,第一次出栈为边界.第二次出栈为边界(顺序是着的!)

同理如果先对区间的边界入栈,再对区间边界入栈,那么出栈时,第一次出栈为边界.第二次出栈为边界

循环条件:只要栈不为空就继续快速排序

注意:

1.栈里取一段区间,单趟排序

2.单趟分割子区间(左区间和右区间)入栈

3.子区间只有一个值或不存在就不入栈

4.取区间等同于出栈

void QuickSort_Hoare_Use_Stack(int* arr, int left, int right)//非递归,使用栈辅助改循环
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st))//栈不为空继续执行快速排序{//先取栈顶元素,再出栈(这里的左边界和右边界不能改变,之后要入栈)int begin = STTop(&st); STPop(&st);int end = STTop(&st); STPop(&st);int left = begin;int right = end;//随机选keysrand((unsigned int)time(0));int rand_i = left + rand() % (right - left);Swap(&arr[left], &arr[rand_i]);int key_i = left;//Hoare排序while (left < right){//由于key_i==left,因此right指针先走//右边找小while (left < right && arr[right] >= arr[key_i]){right--;}//左边找大while (left < right && arr[left] <= arr[key_i]){left++;}Swap(&arr[left], &arr[right]);}Swap(&arr[key_i], &arr[left]);key_i = left;//左区间 [begin,key_i-1],key_i,右区间[key_i+1,end]//要想出栈时先对左区间排序,后对右区间排序,那么右区间先入栈,左区间后入栈if (key_i + 1 < end){STPush(&st, end);STPush(&st, key_i + 1);}if (begin < key_i - 1){STPush(&st, key_i - 1);STPush(&st, begin);}}STDestory(&st);
}

注意出栈的写法:先保存栈顶元素后将其pop,这一点和汇编指令的pop ax的做法是一样的

pop ax执行过程:

1.将SS:SP指向的内存单元处的数据送入寄存器AX中

2.SP+=2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶

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

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

相关文章

汽车免拆诊断 | 2007款保时捷Carrera S车行驶中发动机冷却液温度报警灯异常点亮

故障现象 一辆2007款保时捷Carrera S车&#xff0c;搭载3.8 L自然吸气发动机&#xff0c;累计行驶里程约为7.8万km。车主反映&#xff0c;车辆行驶一段距离后&#xff0c;组合仪表上的发动机冷却液温度报警灯异常点亮。为此&#xff0c;在其他维修厂已更换过节温器、发动机冷却…

CES 2025|美格智能高算力AI模组助力“通天晓”人形机器人震撼发布

当地时间1月7日&#xff0c;2025年国际消费电子展&#xff08;CES 2025&#xff09;在美国拉斯维加斯正式开幕。美格智能合作伙伴阿加犀联合高通在展会上面向全球重磅发布人形机器人原型机——通天晓&#xff08;Ultra Magnus&#xff09;。该人形机器人内置美格智能基于高通QC…

Taro+Vue实现图片裁剪组件

cropper-image-taro-vue3 组件库 介绍 cropper-image-taro-vue3 是一个基于 Vue 3 和 Taro 开发的裁剪工具组件&#xff0c;支持图片裁剪、裁剪框拖动、缩放和输出裁剪后的图片。该组件适用于 Vue 3 和 Taro 环境&#xff0c;可以在网页、小程序等平台中使用。 源码 https:…

Opencv查找、绘制轮廓、圆形矩形轮廓和近似轮廓

查找、绘制轮廓、圆形矩形轮廓和近似轮廓 目录 查找、绘制轮廓、圆形矩形轮廓和近似轮廓1 轮廓查找和绘制1.1 轮廓查找1.1.1 函数和参数1.1.2 返回值 1.2 轮廓绘制1.2.1 函数和参数 1.3 步骤1.4 实际测试绘制轮廓 2 绘制近似轮廓2.1 函数和参数2.2 查找特定轮廓2.3 近似轮廓测试…

【Linux】模拟Shell命令行解释器

一、知识补充 1.1 snprintf snprintf() 是 C语言的一个标准库函数&#xff0c;定义在<stdio.h>头文件中。 snprintf() 函数的功能是格式化字符串&#xff0c;并将结果存储在指定的字符数组中。该函数的原型如下&#xff1a; int snprintf(char *str, size_t size, con…

云计算基础,虚拟化原理

文章目录 一、虚拟化1.1 什么是虚拟化1.2 虚拟化类型 二 、存储虚拟化2.1 存储指标2.2 存储类型2.3 存储协议2.4 RAID 三、内存 i/O虚拟化3.1 内存虚拟化基本概念地址空间转换原理内存共享与隔离原理 3.2 I/O 虚拟化基本概念模拟&#xff08;Emulation&#xff09;方式半虚拟化…

Vue3 + Vite + Electron + Ts 项目快速创建

一、创建 Vue 项目 1. 创建项目 pnpm create vite 2. 安装依赖 cd excel-electron pnpm install 3. 运行项目 pnpm dev 二、添加 Electron 1. 安装 electron pnpm add electron -D 2. 修改 package.json 添加入口 js 和执行命令。 {"main": "dist-ele…

pytest+allure 入门

使用allure如何生成自动化测试报​​​​​​告 &#xff1f;一文详解allure的使用 。_allure测试报告-CSDN博客 例子&#xff1a; import allure import pytest import osallure.epic("闹钟") allure.feature("闹钟增删") class TestSchedule():def setu…

新活动平台建设历程与架构演进

01 前言 历时近两年的重新设计和迭代重构&#xff0c;用户技术中心的新活动平台建设bilibili活动中台终于落地完成&#xff01;并迎来了里程碑时刻 —— 接过新老迭代的历史交接棒&#xff0c;从内到外、从开发到搭建实现全面升级&#xff0c;开启了活动生产工业化新时代&#…

从CentOS到龙蜥:企业级Linux迁移实践记录(系统安装)

引言&#xff1a; 随着CentOS项目宣布停止维护CentOS 8并转向CentOS Stream&#xff0c;许多企业和组织面临着寻找可靠替代方案的挑战。在这个背景下&#xff0c;龙蜥操作系统&#xff08;OpenAnolis&#xff09;作为一个稳定、高性能且完全兼容的企业级Linux发行版&#xff0…

MR实战:IP地址去重

文章目录 1. 实战概述2. 提出任务2.1 原始问题2.2 简单化处理 3. 准备数据3.1 在云主机上创建文本文件3.2 上传文件到HDFS指定目录 4. 实现步骤4.1 创建Maven项目4.2 添加相关依赖4.3 创建日志属性文件4.4 创建网址去重映射器类4.5 创建网址去重归并器类4.6 创建网址去重驱动器…

AnaConda下载PyTorch慢的解决办法

使用Conda下载比较慢&#xff0c;改为pip下载 复制下载链接到迅雷下载 激活虚拟环境&#xff0c;安装whl&#xff0c;即可安装成功 pip install D:\openai.wiki\ChatGLM2-6B\torch-2.4.1cu121-cp38-cp38-win_amd64.whl

Photoshop PS批处理操作教程(批量修改图片尺寸、参数等)

前言 ‌Photoshop批处理的主要作用‌是通过自动化处理一系列相似的操作来同时应用于多张图片&#xff0c;从而节省时间和精力&#xff0c;提高工作效率。批处理功能特别适用于需要批量处理的任务&#xff0c;如图像尺寸调整、颜色校正、水印添加等‌。 操作步骤 1.创建动作 …

Web渗透测试之XSS跨站脚本 防御[WAF]绕过手法

目录 XSS防御绕过汇总 参考这篇文章绕过 XSS payload XSS防御绕过汇总 服务端知道有网络攻击或者xss攻 Html 通过js代码 标签属性等手段进行一个过滤 不允许出现css的payload 前端过滤 我可以在抓包工具里面修改 抓包工具是不受前端的防御 也 就是浏览器 服务端过滤…

git提交

基本流程&#xff1a;新建分支 → 分支上开发(写代码) → 提交 → 合并到主分支 拉取最新代码因为当前在 master 分支下&#xff0c;你必须拉取最新代码&#xff0c;保证当前代码与线上同步&#xff08;最新&#xff09;&#xff0c;执行以下命令&#xff1a;bashgit pull orig…

多云架构,JuiceFS 如何实现一致性与低延迟的数据分发

随着大模型的普及&#xff0c;GPU 算力成为稀缺资源&#xff0c;单一数据中心或云区域的 GPU 资源常常难以满足用户的全面需求。同时&#xff0c;跨地域团队的协作需求也推动了企业在不同云平台之间调度数据和计算任务。多云架构正逐渐成为一种趋势&#xff0c;然而该架构下的数…

【Git原理和使用】Git 分支管理(创建、切换、合并、删除、bug分支)

一、理解分支 我们可以把分支理解为一个分身&#xff0c;这个分身是与我们的主身是相互独立的&#xff0c;比如我们的主身在这个月学C&#xff0c;而分身在这个月学java&#xff0c;在一个月以后我们让分身与主身融合&#xff0c;这样主身在一个月内既学会了C&#xff0c;也学…

静态路由配置与调试——计算机网络实训day1

文章目录 操作前准备一、实验目的二、实验要求三、实验过程1、在R1和R2上配置设备名称。基本配置设备命名 2、在R1和R2上配置接口IP地址&#xff0c;并查看IP地址的配置情况。3、在R1和R2上配置静态路由&#xff0c;并查看路由表。静态路由缺省路由&#xff08;默认路由&#x…

农产品直播带货方案拆解

作为一名经验丰富的营销策划人道叔&#xff0c;今天我来拆解一下咱们4A营销广告圈的这份《直播天府川农好物带货方案》&#xff0c;让你能学到很多实用的策略和技巧&#xff0c;直接应用到你的策划工作中去。 首先&#xff0c;咱们看看背景分析。 助农直播现在可是个大热门&a…

【Qt】控件概述和QWidget核心属性1(enabled、geometry、windowTitle、windowIcon、QRC机制)

一、控件概念 界面上各种元素、各种部分的统称&#xff08;如按钮、输入框、下拉框、单选复选框...&#xff09; Qt作为GUI开发框架&#xff0c;内置了各种的常用控件&#xff0c;并支持自定义控件。 二、控件体系发展 1.没有完全的控件&#xff0c;需要使用绘图API手动绘制…