数据结构-C语言-排序(3)

        代码位置:test-c-2024: 对C语言习题代码的练习 (gitee.com)

一、前言:

1.1-排序定义:

        排序就是将一组杂乱无章的数据按照一定的规律(升序或降序)组织起来。(注:我们这里的排序采用的都为升序)

1.2-排序分类:

常见的排序算法:
  • 插入排序
    a. 直接插入排序
    b. 希尔排序

  • 选择排序
    a. 选择排序
    b. 堆排序
  • 交换排序
    a. 冒泡排序
    b. 快速排序
  • 归并排序
    a. 归并排序
  • 非比较排序
    a.计数排序
    b.基数排序

1.3-算法比较:

1.4-目的:

        今天,我们这里要实现的是快速排序

1.5-快速排序定义:

        通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序。

二、快速排序-key的选择:

2.1-直接在left和right中选择:

        这种选择方法具有局限性如果排序的序列已经为升序的情况下,根据快速排序的定义,我们可知,快速排序的话,会确定一个值的位置也就是key,这个值的作用就是把数据分割成独立的两部分,一部分是比他大,一部分是比他小,而如果是升序的情况下直接选择left,选出的left的值是最小的,也就是说右面的部分是N-1个数据,而如果是用递归的方式实现的快排,那么就需要递归N次,也就是建立N个栈才能实现最终排序的操作,如果数据量大就很有可能出现栈溢出的情况。

该情况下递归的图片如图所示:

2.2-随机选择key:

        随机选择key,也就是说,在数组下标范围内,随机生成一个下标,采用这个下标位置的数据值作为key这样的情况下,我们就大大降低了选出的key是最小值的情况。能有效地减少栈溢出的情况。

2.3-三数取中:

        三数取中就是在left 、midi((right+left)/2) 、right三个下标位置上的数据之间选择出这三个数据中的中间数。这样就避免了key为最小值的情况。

2.4-代码:

void Swap(int* p, int* q)				//交换函数
{int tem = *p;*p = *q;*q = tem;
}
//直接选取法
int keyi = left;//随机选keyi
int randi = left + (rand() % (right - left));
Swap(&a[randi], &a[left]);
int keyi = left;//三数取中
int midi = GetMidNumi(a, left, right);
if (midi != left)Swap(&a[midi], &a[left]);
int keyi = left;
int GetMidNumi(int* a, int left, int right)			//三数取中法
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[mid] > a[right]){return mid;}if (a[left] < a[right]){return left;}else{return right;}}if (a[left] < a[mid]){if (a[left] > a[right]){return left;}if (a[right] > a[mid]){return mid;}else{return right;}}
}

三、快速排序-Hoare:

3.1-思路:

Hoare快速排序的思路就是:

        如果key定义的是left,那么就首先从右边找小,找到以后再从左边出发找大,找完以后后将他们俩的数据调换,然后接着进行下一次先右后左找小再找大,直到left小于right为止。同理若定义key在right处那么就先左后右,处理方式与left类似。        

        这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

左边做key为什么相遇位置一定比key小?

3.2-过程图:

3.3-代码:

//Hoare
void QuickSort1(int* a, int left,int right)			//快速排序---时间复杂度(O(N*logN))
{if (left >= right)return;int begin = left;int end = right;//直接选取法//int keyi = left;//随机选keyi//int randi = left + (rand() % (right - left));//Swap(&a[randi], &a[left]);//int keyi = left;//三数取中int midi = GetMidNumi(a, left, right);if(midi!=left)Swap(&a[midi], &a[left]);int keyi = left;while (left < right){//右边找小while (left<right&&a[right] >=a[keyi]){--right;}//左边找大while (left<right&&a[left] <= a[keyi]){++left;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;//递归---小区间优化--小区间直接使用插入排序if (end - begin+1 > 10){QuickSort1(a, begin, keyi - 1);QuickSort1(a, keyi + 1, end);}else{InsertSort(a + begin, end - begin + 1);}
}

四、快速排序-挖坑法:

4.1-思路:

        快速排序,挖坑法的思路就是:

        先将第一个数据存放在临时变量k中,形成一个坑位,然后再数组的右边出发,向左寻找小于key的数,然后将这个数填在以前的坑位上,然后在该处重新生成坑位,接着在从左边向右寻找,找大于ker的数,然后将这个数填在生成的坑位上,然后在该处重新生成坑位,就这样一直循环实现上述操作,直到left与right相遇为止,此时,该坑位就应该填key。。

        这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

4.2-过程图:

        

4.3-代码:

//挖坑法
void QuickSort2(int* a, int left, int right)			//快速排序---时间复杂度(O(N*logN))
{if (left >= right)return;int begin = left;int end = right;//直接选取法//int keyi = left;//随机选keyi//int randi = left + (rand() % (right - left));//Swap(&a[randi], &a[left]);//int keyi = left;//三数取中int midi = GetMidNumi(a, left, right);if(midi!=left)Swap(&a[midi], &a[left]);int key = a[left];int hole = left;while (left < right){while (left < right &&a[right] >= key){--right;}a[hole] = a[right];hole = right;while (left < right&&a[left] <= key){++left;}a[hole] = a[left];hole = left;}a[hole] = key;//递归---小区间优化--小区间直接使用插入排序if (end - begin + 1 > 10){QuickSort2(a, begin, hole - 1);QuickSort2(a, hole + 1, end);}else{InsertSort(a + begin, end - begin + 1);}
}

五、快速排序-前后指针法:

5.1-思路:

快速排序,前后指针法的思路就是:

首先,定义一个prev=left ; cur=left+1。这里我们实现的操作:

        1.cur找到比key小的值,++prove, cur和prev位置的数调换,然后++cur。

        2.cur找到比key大的值,++cur。

说明:

        1.prev要么紧跟着cur(prev下一个位置就是cur)。

        2.prev跟cur中间隔着一段比key大的值。

         就这样,按上述思想进入循环知道,直到cur走到数组末端的下一个位置为止,接下来我们要实行的操作就是将keyi位置的值(key)与prev位置的值交换。这样排完一趟后,我们能将大于key的数分布在右边,小于key的数分布在左边,最后,我们只需要按照这个思路递归下去,就实现快速排序啦。

        注意:在递归时,如果出现left>=right的情况下,我们就需要返回,否则就会死循环。

5.2-过程图:

5.3-代码:

//前后指针法
void QuickSort3(int* a, int left, int right)			//快速排序---时间复杂度(O(N*logN))
{if (left>=right)return;int begin = left;int end = right;//直接选取法//int keyi = left;//随机选keyi//int randi = left + (rand() % (right - left));//Swap(&a[randi], &a[left]);//int keyi = left;//三数取中int midi = GetMidNumi(a, left, right);if (midi != left)Swap(&a[midi], &a[left]);int keyi = left;int prev= left;int cur = left + 1;while (cur<=right){if(a[cur]<a[keyi]&&++prev!=cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;//递归---小区间优化--小区间直接使用插入排序if (end - begin + 1 > 10){QuickSort3(a, begin, keyi - 1);QuickSort3(a, keyi + 1, end);}else{InsertSort(a + begin, end - begin + 1);}}

六、递归的问题与优化:

6.1-递归的问题:

        1.效率问题(略有影响)。

        2.深度太深时会栈溢出。

递归过程图:

6.2-小区间优化:

小区间优化的思想就是:

        将递归的最后几层,也就是基本有序的小区间,采用直接插入排序的方法,不再采用递归的方式,这样能够减少递归时所开辟栈。

        因为,递归栈的开辟相当于二叉树,而二叉树的最后一层相当于总数的一半,如果把最后一层省掉,也就是省去了递归所需开辟栈的大概50%的空间。

        所以,我们可以通过小区间优化来减少栈的开辟,在不影响时间复杂度的情况下,也减小了深度太深时会栈溢出的问题。

6.3-小区间优化代码:

//递归---小区间优化--小区间直接使用插入排序
if (end - begin + 1 > 10)
{QuickSort3(a, begin, keyi - 1);QuickSort3(a, keyi + 1, end);
}
else
{InsertSort(a + begin, end - begin + 1);
}

七、递归改非递归:

        由上述可知,通过递归实现快排,具有一定的弊端,也就是栈溢出,所以这里我们可以采取将递归改成非递归的方式来实现快速排序

7.1-方式:

        1.直接改成循环。

        2.使用栈辅助改成循环。

        通过上述代码可观察发现,递归改非递归的第一种方式,我们是实现不了的,所以我们这里需要借助栈来辅助将递归改成循环。

7.2-思路:

        这里的思路就是将递归时的左右区间,存入栈中。然后在循环的过程中,我们只需将区间值出站即可。

        注意:栈的原理是后进先出。

7.3-代码:

#include "Stack.h"
//递归改非递归
void QuickSortNonR(int* a, int left, int right)		//快速排序---时间复杂度(O(N*logN))
{ST ps;STInit(&ps);STpush(&ps, right);	//入栈STpush(&ps, left);	//入栈while (!STEmpty(&ps)){int begin= STTop(&ps);		//取栈顶元素STPop(&ps);		//出栈int end = STTop(&ps);		//取栈顶元素STPop(&ps);		//出栈int midi = GetMidNumi(a, begin, end);if (midi != begin)Swap(&a[midi], &a[begin]);int keyi = begin;int prev = begin;int cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;if (keyi + 1 < end){STpush(&ps, end);	//入栈STpush(&ps, keyi + 1);	//入栈}if (keyi - 1 > begin){STpush(&ps, keyi - 1);	//入栈STpush(&ps, begin);	//入栈}}for (int i = 0; i <=right; i++){printf("%d  ", a[i]);}printf("\n");STDestory(&ps);
}

7.4-栈的代码:

#pragma once#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST* ps);		//初始化
void STDestory(ST* ps);	//释放销毁void STpush(ST* ps, STDataType x);	//入栈
void STPop(ST* ps);		//出栈
int STSize(ST* ps);		//栈中元素个数
bool STEmpty(ST* ps);		//判断栈空
STDataType STTop(ST* ps);		//栈顶元素
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"void STInit(ST* ps)		//初始化
{assert(ps);ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);if (ps->a == NULL){perror("malloc");return;}ps->capacity = 4;ps->top = 0;				//top是栈顶元素的下一个位置//ps->top=-1				//top是栈顶元素位置
}void STDestory(ST* ps)		//释放销毁
{assert(ps);ps->top = 0;ps->capacity = 0;free(ps->a);ps->a = NULL;
}void STpush(ST* ps, STDataType x)		//入栈
{assert(ps);if (ps->top == ps->capacity){STDataType* tem = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);if (tem == NULL){perror("realloc");return;}ps->a = tem;ps->capacity *= 2;}ps->a[ps->top] = x;ps->top++;
}void STPop(ST* ps)		//出栈
{assert(ps);assert(!STEmpty(ps));ps->top--;
}int STSize(ST* ps)			//栈中元素个数
{assert(ps);return ps->top;
}bool STEmpty(ST* ps)		//判断栈空
{assert(ps);return ps->top == 0;
}STDataType STTop(ST* ps)		//返回栈顶元素
{assert(ps);assert(!STEmpty(ps));return ps->a[ps->top - 1];
}

八、结语:

        上述内容,即是我个人对数据结构排序中快速排序的个人见解以及自我实现。若有大佬发现哪里有问题可以私信或评论指教一下我这个小萌新。非常感谢各位友友们的点赞,关注,收藏与支持,我会更加努力的学习编程语言,还望各位多多关照,让我们一起进步吧!

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

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

相关文章

录取查询老师在哪里制作?

随着考试的落幕&#xff0c;家长们焦急等待的心情终于可以稍微缓解&#xff0c;因为录取结果即将揭晓。然而&#xff0c;对于老师来说&#xff0c;这仅仅是另一项繁重工作的开始。他们需要将每一份录取通知单逐一发送给学生家长&#xff0c;这个过程不仅耗时而且容易出错。面对…

Blender中的重拓扑修改器如何使用?

许多人还不了解Blender中的重拓扑编辑器及其使用方法。Blender中的重拓扑修改器提供了一系列工具和选项&#xff0c;以简化创建优化网格的过程&#xff0c;无论是出于何种目的&#xff0c;都能为3D艺术家和建模者节省大量时间和精力。那么&#xff0c;在Blender中重拓扑的定义是…

《数据结构:C语言实现双链表》

文章目录 一、链表的分类二、双向链表1、概念与结构 三、双向链表实现1、双向链表要实现的功能2、哨兵位初始化3、双链表头插数据4、判断链表是否为空5、打印链表数据6、尾插数据7、头删数据8、尾删数据9、寻找数据所在结点10、在任意结点之后插入数据11、删除任意结点12、销毁…

FastGPT 代码调试配置

目录 一、添加 launch.json 文件 二、调试 本文简单介绍如何通过 vscode 对 FastGPT 进行调试。 这里假设已经安装 vsocde 和 FastGPT本地部署。 一、添加 launch.json 文件 vscode 打开 FastGPT 项目&#xff0c;点击 调试 -> 显示所有自动调试配置 -> 添加配置 -&…

IDEA创建Java工程、Maven安装与建立工程、Web工程、Tomcat配置

《IDEA破解、配置、使用技巧与实战教程》系列文章目录 第一章 IDEA破解与HelloWorld的实战编写 第二章 IDEA的详细设置 第三章 IDEA的工程与模块管理 第四章 IDEA的常见代码模板的使用 第五章 IDEA中常用的快捷键 第六章 IDEA的断点调试&#xff08;Debug&#xff09; 第七章 …

响应式编程(Reactive Programming)是什么?

响应式编程的概念 Reactive Programming(反应式编程或响应式编程)是一种面向数据流和变化传播的编程范式,它允许程序组件以声明式的方式响应数据的变化。 响应式编程强调以数据流作为核心,利用观察者模式等机制自动处理数据的变化和传播。 响应式编程的核心思想 以异步数…

【Nacos】Nacos服务注册与发现 心跳检测机制源码解析

在前两篇文章&#xff0c;介绍了springboot的自动配置原理&#xff0c;而nacos的服务注册就依赖自动配置原理。 Nacos Nacos核心功能点 服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务&#xff0c;提供自身的元数据&#xff0c;比如ip地址、端…

JVM监控及诊断工具-命令行篇--jcmd命令介绍

JVM监控及诊断工具-命令行篇5-jcmd&#xff1a;多功能命令行 一 基本情况二 基本语法jcmd -ljcmd pid helpjcmd pid 具体命令 一 基本情况 在JDK 1.7以后&#xff0c;新增了一个命令行工具jcmd。它是一个多功能的工具&#xff0c;可以用来实现前面除了jstat之外所有命令的功能…

pyspark使用 graphframes创建和查询图的方法

1、安装graphframes的步骤 1.1 查看 spark 和 scala版本 在终端输入&#xff1a; spark-shell --version 查看spark 和scala版本 1.2 在maven库中下载对应版本的graphframes https://mvnrepository.com/artifact/graphframes/graphframes 我这里需要的是spark 2.4 scala 2.…

QDockWidget

详细描述 QDockWidget 类提供了一个小部件&#xff0c;它可以停靠在QMainWindow内部&#xff0c;也可以作为桌面上的顶级窗口浮动。 QDockWidget 提供了停靠部件的概念&#xff0c;也称为工具调色板或实用窗口。停靠窗口是放置在 中央部件 周围的停靠部件区域中的辅助窗口&am…

AI算法24-决策树C4.5算法

目录 决策树C4.5算法概述 决策树C4.5算法简介 决策树C4.5算法发展历史 决策树C4.5算法原理 信息熵&#xff08;Information Entropy&#xff09; 信息增益&#xff08;Information Gain&#xff09; 信息增益比&#xff08;Gain Ratio&#xff09; 决策树C4.5算法改进 …

Golang中读写锁的底层实现

目录 Sync.RWMutex 背景与机制 接口简单介绍 sync.RWMutex 数据结构 读锁流程 RLock RUnlock RWMutex.rUnlockSlow 写锁流程 Lock Unlock Sync.RWMutex 背景与机制 从逻辑上&#xff0c;可以把 RWMutex 理解为一把读锁加一把写锁&#xff1b; 写锁具有严格的排他性&…

【python】OpenCV—Extreme Points in the Contour

文章目录 1、需求描述2、功能实现3、更多的例子4、完整代码5、参考 1、需求描述 给一张图片&#xff0c;找出其轮廓&#xff0c;并画出轮廓的上下左右极值点 输入图片 输出效果 2、功能实现 # 导入必要的包 import imutils import cv2 # 加载图像&#xff0c;将其转换为灰度…

vue3 + antd vue 纯前端 基于xlsx 实现导入excel 转 json,将json数据转换XLSX并下载(下载模版)

一、导入 0、关键代码 // 安装插件 npm i xlsx/yarn add xlsx // 导入xlsx import * as XLSX from xlsx; 点击提交的时候才整理数据。上传的时候文件保存在 state.form.file[0] 中的 // 定义字段映射关系 const fieldMap {sheet2json: {技能名称: skill_name,技能等级: …

uni-app 影视类小程序开发从零到一 | 开源项目分享

引言 在数字娱乐时代&#xff0c;移动设备已成为我们生活中不可或缺的一部分&#xff0c;尤其是对于电影爱好者而言&#xff0c;随时随地享受精彩影片成为一种日常需求。爱影家&#xff0c;一款基于 uni-app 开发的影视类小程序&#xff0c;正是为此而生。它不仅提供了丰富的影…

【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

文章目录 前言一、购物车模块1.后端核心逻辑2.前端页面代码3.操作流程及演示 二、订单模块1.订单模块模型类设计1.展示订单信息a.页面展示b.前端核心代码c.后端核心逻辑 2.订单是否使用优惠券与积分a.页面展示b.前端核心代码 3.订单支付方式a.页面展示b.前端核心代码 4.提交订单…

PyTorch Autograd内部实现

原文&#xff1a; 克補 爆炸篇 25s (youtube.com) 必应视频 (bing.com)https://www.bing.com/videos/riverview/relatedvideo?&qPyTorchautograd&qpvtPyTorchautograd&mid1B8AD76943EFADD541E01B8AD76943EFADD541E0&&FORMVRDGAR 前面只要有一个node的re…

北京交通大学《深度学习》专业课,实验3卷积、空洞卷积、残差神经网络实验

一、实验要求 1. 二维卷积实验&#xff08;平台课与专业课要求相同&#xff09; ⚫ 手写二维卷积的实现&#xff0c;并在至少一个数据集上进行实验&#xff0c;从训练时间、预测精 度、Loss变化等角度分析实验结果&#xff08;最好使用图表展示&#xff09; ⚫ 使用torch.nn…

Matlab基础语法篇(下)

Matlab基础语法&#xff08;下&#xff09; 一、逻辑基础&#xff08;一&#xff09;逻辑运算符&#xff08;二&#xff09;all、any、find函数&#xff08;三&#xff09;练习 二、结构基础&#xff08;一&#xff09;条件结构&#xff08;1&#xff09;if-elseif-else-end&am…

十、操作符详解

目录 1、操作符分类 2、二进制转换 2.1二进制转十进制 2.1.1、十进制转二进制 2.2、二进制转八进制和十六进制 2.2.1、二进制转八进制 2.2.2、二进制转十六进制 3、原码、反码、补码 4、移位操作符&#xff08;移动的是二进制位&#xff09; 4.1、左移操作符 4.2、右…