数据结构初阶之排序(下)

news2024/9/20 8:05:05

前言

上一期内容中我们了解了基本排序中的插入与选择排序,今天我将为大家带来剩下的几种排序算法

快速排序

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列在相应位置上为⽌。

快速排序有着许多种实现方式,今天我们就hoare、挖坑法以及lomuto前后指针法来对其进行递归实现,同时还将运用数据结构中的栈来实现快速排序的非递归实现方法。

快速排序实现的主框架

//快速排序 
void QuickSort(int* a, int left, int right) 
{
 if (left >= right) {
 return;
 }
 //_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分 
 int meet = _QuickSort(a, left, right);
 QuickSort(a, left, meet - 1);
 QuickSort(a, meet + 1, right);
}

将区间中的元素进⾏划分的 _QuickSort ⽅法主要有以下⼏种实现⽅式:

我们可以将这些方法看做一个找基准值的过程:

hoare版本

思路:

1)创建左右指针,确定基准值

2)从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环

问题1:为什么跳出循环后right位置的值⼀定不⼤于key?

        当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指向的数据⼀定不⼤于key

如图:

问题2:为什么left和right指定的数据和key值相等时也要交换?

        相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量重复时,⽆法进⾏有效的分割排序。

如图:

代码的实现
int _QuickSort(int* a, int left, int right) 
{
 int begin = left;
 int end = right;
 int keyi = left;
 ++left;
 while (left <= right)
 {
 // 右边找⼩ 
 while (left <= right && a[right] > a[keyi])
 {
 --right;
 }
 // 右边找⼩ 
 while (left <= right && a[left] < a[keyi])
 {
 ++left;
 }
 if (left <= right)
 {
 swap(&a[left++], &a[right--]);
 }
 }
 swap(&a[keyi], &a[right]);
 return right;
}

挖坑法

思路:

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

我们通过一张图来了解:

代码的实现
int _QuickSort(int* a, int left, int right) 
{
 int mid = a[left];
 int hole = left;
 int key = a[hole];
 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;
 return hole;
}

lomuto前后指针

思路:

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

如图:

代码的实现
int _QuickSort(int* a, int left, int right) 
{
 int prev = left, cur = left + 1;
 int key = left;
 while (cur <= right)
 {
 if (a[cur] < a[key] && ++prev != cur) 
 {
 swap(&a[cur], &a[prev]);
 }
 ++cur;
 }
 swap(&a[key], &a[prev]);
 
 return prev;
}

快速排序的特征

1. 时间复杂度:O(nlogn)

2. 空间复杂度:O(logn)


快速排序的非递归版本

⾮递归版本的快速排序需要借助数据结构:

根据栈结构先进后出的原则,我们先将待排序数组的末尾元素先入栈,头元素后入栈。

随后分别取栈顶与栈底元素,利用循环来实现递归操作。

代码的实现

void QuickSortNonR(int* a, 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 keyi = begin;
 int prev = begin;
 int cur = begin + 1;
 while (cur <= end)
 {
 if (a[cur] < a[keyi] && ++prev != cur)
 Swap(&a[prev], &a[cur]);
 ++cur;
 }
 Swap(&a[keyi], &a[prev]);
 keyi = prev;
 // [begin, keyi-1] keyi [keyi+1, end] 
 if (keyi + 1 < end)
 {
 STPush(&st, end);
 STPush(&st, keyi + 1);
 }
 if (begin < keyi-1)
 {
 STPush(&st, keyi-1);
 STPush(&st, begin);
 }
 }
 STDestroy(&st);
}

归并排序

归并排序的思想:

归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide  and Conquer)的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为⼆路归并。归并排序核⼼步骤(如下图):

利用递归分别将待排序的数组二等分割成n份,每次取一半,随后就分割后的两两数组进行合并操作(先比大小后放置)

代码的实现

void _MergeSort(int* a, int left, int right, int* tmp) 
{
 if (left >= right) 
 {
 return;
 }
 int mid = (right + left) / 2;
 //[left,mid] [mid+1,right]
 _MergeSort(a, left, mid, tmp);
 _MergeSort(a, mid + 1, right, tmp);
 
 int begin1 = left, end1 = mid;
 int begin2 = mid + 1, end2 = right;
 int index = begin1;
//合并两个有序数组为⼀个数组 
 while (begin1 <= end1 && begin2 <= end2)
 {
 if (a[begin1] < a[begin2]) 
 {
 tmp[index++] = a[begin1++];
 }
 else 
 {
 tmp[index++] = a[begin2++];
 }
 }
 while (begin1 <= end1)
 {
 tmp[index++] = a[begin1++];
 }
 while (begin2 <= end2)
 {
 tmp[index++] = a[begin2++];
 }
 for (int i = left; i <= right; i++)
 {
 a[i] = tmp[i];
 }
}
void MergeSort(int* a, int n) 
{
 int* tmp = new int[n];
 _MergeSort(a, 0, n - 1, tmp);
 delete[] tmp;
}

归并排序的特征

1. 时间复杂度:O(nlogn)

2. 空间复杂度:O(n)


测试代码

通过以下代码,我们可以得出各种排序算法的时间效率。

// 测试排序的性能对⽐  
void TestOP() 
{ 
 srand(time(0)); 
 const int N = 100000; 
int* a1 = (int*)malloc(sizeof(int)*N); 
 int* a2 = (int*)malloc(sizeof(int)*N); 
 int* a3 = (int*)malloc(sizeof(int)*N); 
 int* a4 = (int*)malloc(sizeof(int)*N); 
 int* a5 = (int*)malloc(sizeof(int)*N); 
 int* a6 = (int*)malloc(sizeof(int)*N); 
 int* a7 = (int*)malloc(sizeof(int)*N); 
 for (int i = 0; i < N; ++i) 
 { 
 a1[i] = rand(); 
 a2[i] = a1[i]; 
 a3[i] = a1[i]; 
 a4[i] = a1[i]; 
 a5[i] = a1[i]; 
 a6[i] = a1[i];
 a7[i] = a1[i]; 
 } 
 int begin1 = clock(); 
 InsertSort(a1, N); 
 int end1 = clock(); 
 
 int begin2 = clock(); 
 ShellSort(a2, N); 
 int end2 = clock(); 
 
 int begin3 = clock(); 
 SelectSort(a3, N); 
 int end3 = clock(); 
 
 int begin4 = clock(); 
 HeapSort(a4, N); 
 int end4 = clock(); 
 
 int begin5 = clock(); 
 QuickSort(a5, 0, N-1); 
 int end5 = clock(); 
 
 int begin6 = clock(); 
 MergeSort(a6, N); 
 int end6 = clock(); 
 
 int begin7 = clock(); 
 BubbleSort(a7, N); 
 int end7 = clock();
 
 printf("InsertSort:%d\n", end1 - begin1); 
 printf("ShellSort:%d\n", end2 - begin2); 
printf("SelectSort:%d\n", end3 - begin3); 
 printf("HeapSort:%d\n", end4 - begin4); 
 printf("QuickSort:%d\n", end5 - begin5); 
 printf("MergeSort:%d\n", end6 - begin6); 
 printf("BubbleSort:%d\n", end7 - begin7); 
 
 free(a1); 
 free(a2); 
 free(a3); 
 free(a4); 
 free(a5); 
 free(a6); 
 free(a7); 
} 

非比较排序

学习了以上的代码,那么有没有不通过比较大小就能实现的排序算法呢?有!

计数排序

计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。

操作步骤:

1)统计相同元素出现次数

2)根据统计的结果将序列回收到原来的序列中

通过两张图片来了解一下:

为了避免空间的无端浪费,我们将遍历待排数组中的最大最小值,后得出其间的差值,用其差值来申请空间大小以达到排序的效果。

代码的实现

void CountSort(int* a, int n)
{
 int min = a[0], max = a[0];
 for (int i = 1; i < n; i++)
 {
 if (a[i] > max)
 max = a[i];
 if (a[i] < min)
 min = a[i];
 }
 int range = max - min + 1;
 int* count = (int*)malloc(sizeof(int) * range);
 if (count == NULL)
 {
 perror("malloc fail");
 return;
 }
memset(count, 0, sizeof(int) * range);
 // 统计次数 
 for (int i = 0; i < n; i++)
 {
 count[a[i] - min]++;`
 }
 // 排序 
 int j = 0;
 for (int i = 0; i < range; i++)
 {
 while (count[i]--)
 {
 a[j++] = i + min;
 }
 }
}

计数排序的特征

计数排序在数据范围集中时,效率很⾼,但是适⽤范围及场景有限。

时间复杂度:O(N + range)

空间复杂度:O(range)

稳定性:稳定

*计数排序仅适用于整数的排序


排序算法的稳定性及其复杂度综合分析

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的 相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,⽽在排序后的序列中,r[i]仍在r[j]之 前,则称这种排序算法是稳定的;否则称为不稳定的。


以上便是数据结构初阶的全部内容,感谢各位的支持!后面我将为大家带来C++有关的知识分享,敬请期待!

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

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

相关文章

ARM 架构与技术综述

目录 认识 ARM ARM 发展历史 指令集 ARM 公司产品分类 ARM 体系结构 数据类型约定 处理器的 32 位和 64 位含义 指令集 ARM 处理器的工作模式 CPU (内核) 组成 寄存器 时钟 认识 ARM ARM 可以指&#xff1a; 一家公司。一种技术。一系列处理器。 架构&#xff1a;A…

定时任务框架 xxl-job

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

算法通关:015:最小栈

文章目录 题目思路主要代码问题总结有时候提交不了可能是方法名的问题 题目 leetcode152 思路 主要代码 同一个思路&#xff0c;法一是用栈实现&#xff0c;法二是用数组实现 /*** Author: ggdpzhk* CreateTime: 2024-08-03* 最小栈 155*/ import java.util.Stack;public…

fonttools - 操作字体

文章目录 一、关于 fonttools二、安装贡献测试可选依赖 三、如何制作新版本四、工具五、库 一、关于 fonttools fontTools是一个用于操作字体的库&#xff0c;用Python编写。这 项目包括TTX工具&#xff0c;可以转换TrueType和OpenType XML文本格式之间的字体&#xff0c;也称…

cesium加载wms与arcgis服务

1、加载geoserver的wms服务 2、加载arcgis服务

idea使用free流程,2024idea、2023idea都可以安装免费使用

1.先到官网下载&#xff0c;这里选择win系统的&#xff0c;点击下图的.exe https://www.jetbrains.com/idea/download/?sectionwindows 2.下载好后基本上就是一直点击“下一步”到直到安装好&#xff0c;安装好后先打开软件后关闭退出 3.下载配配套资料 链接: https://pan.ba…

C++计算二维坐标欧式距离

1.何为欧式距离 在欧几里得空间中&#xff0c;点x (x1,…,xn)和 y (y1,…,yn)之间的欧氏距离为 2.C实现计算两点欧氏距离 point1(x1, y1): (1,3) point2(x2, y2): (2,6) #include <iostream> #include <cmath>using namespace std;int main() {struct point {…

【Android驱动08】Sensor传感器框架以及驱动移植和调试方法(Kernel层部分)

接续上一节&#xff0c;本文主要介绍驱动部分的客制化 1&#xff0c; hardware层 通过系统调用open,read,write对sys/class/sensor/m_acc_misc读写操作 路径&#xff1a;vendor/mediatek/proprietary/hardware/sensor/sensors-1.0/Acceleration.cpp 直接操作/sys/class/sensor…

pytorch基础模块:Tensorboard、Dataset、Transforms、Dataloader

Tensorboard、Dataset、Transforms、Dataloader 该文档主要参考【土堆】的视频教程&#xff1a;pytorch入门教程–土堆 一、Tensorboard 安装tensorboard&#xff1a;pip install tensorboard 使用步骤&#xff1a; 引入相关库&#xff1a;from torch.utils.tensorboard i…

DDL、DML、DQL、DCL具体实例与关系

一、DDL、DCL、DML、DQL 通过二维表的形式&#xff0c;更加清晰直观的学习、对比其关系。 DDL DCL DML DQL 英文释义 Data Defination Language 数据库定义语言 Data Control Language 数据库控制语言 Data Manipulation Language 数据操作语言 Data Query Language 数…

PyMuPDF-Guide

本文翻译整理自&#xff1a; https://pymupdf.readthedocs.io/en/latest/how-to-open-a-file.html 文章目录 一、打开文件1、支持的文件类型2、如何打开文件打开一个错误的文件扩展名 3、打开远程文件从云服务打开文件 4、以文本形式打开文件例子打开一个C#文件打开一个XML文件…

按摩行业的革新者:从挑战到辉煌的转型之路

在时代浪潮的推动下&#xff0c;一个勇于创新的团队于2018年毅然踏入按摩服务市场&#xff0c;创立了一家颠覆传统的按摩店。面对行业内的激烈竞争与瞬息万变的市场环境&#xff0c;他们凭借独树一帜的经营模式和不懈的努力&#xff0c;不仅稳固了市场地位&#xff0c;更在去年…

使用Greenhills生成Lib并使用Lib的两种方法

文章目录 前言GHS工程生成libmake方式生成liblib的使用总结 前言 在软件交付过程&#xff0c;如果不交付源代码&#xff0c;可以将源码编译之后生成lib文件提供给客户。本文介绍GHS中生成lib的两种方法&#xff0c;一种基于GHS工程&#xff0c;一种基于make文件。生成完lib后的…

uniapp自定义网格布局用于选择金额、输入框焦点事件以及点击逻辑实战

样式 <view class="withdraw-section"><text class="section-title">提现金额</text><view class="amount-options"><view v-for="(item, index) in list" :key="index" class="amount-opt…

使用Leaflet进行船舶航行警告区域绘制实战

目录 前言 一、坐标格式转换 1、数据初认识 2、将区域分割成多个点 3、数据转换 4、数据转换调用 二、WebGIS展示空间位置信息 1、定义底图 2、Polygon的可视化 3、实际效果 三、总结 前言 通常而言&#xff0c;海事部门如海事局&#xff0c;通常会在所述的管辖区域内…

Java从入门到精通(十五) ~ IO流

晚上好&#xff0c;愿这深深的夜色给你带来安宁&#xff0c;让温馨的夜晚抚平你一天的疲惫&#xff0c;美好的梦想在这个寂静的夜晚悄悄成长。 目录 前言 什么是IO流&#xff1f; IO流的作用&#xff1a; 一、基础流 1. 字节流 1.1 字节输入流 FileInputStream 1.2 字节…

找到第一个满足条件的格值

表格第1列是科目&#xff0c;之后几列是每次的考试成绩&#xff0c;顺序排列。 ABCDE1Art03.676.27.82History3.786.217.29.83Maths5.66.36.68.9 要求根据指定的科目和成绩&#xff0c;找到该科目中大于等于该成绩的第1个格值&#xff0c;比如参数是Maths、6.5时&#xff0c;…

element-ui简单入门1.0.0

第一篇&#xff1a;table标签速用 总结&#xff1a;建楼前&#xff0c;先打地基<el-table></el-table>&#xff0c;打完地基看高度&#xff0c;一层楼4米&#xff0c;80米20个<el-table-column></el-table-column>&#xff0c;每次楼的名字是label 第…

[翻译] Asset Administration Shells

关于资产管理外壳 (AAS) 资产管理外壳 (AAS) 是工业4.0中的关键概念&#xff0c;为产品、资源&#xff08;如设备&#xff09;和过程提供信息隐藏和更高层次的抽象。AAS 是技术和设备无关的机器可读描述&#xff0c;提供访问资产属性和功能的统一接口。与现有解决方案不同&…

C# 下的限定符运算详解(全部,任意,包含)与示例

文章目录 1.限定符概述2. 全部限定符运算&#xff08;All&#xff09;3. 任意限定符运算&#xff08;Any&#xff09;4. 包含限定符运算&#xff08;Contains&#xff09;总结 当我们在C#编程中需要进行条件判断或集合操作时&#xff0c;限定符&#xff08;qualifiers&#xff…