初阶数据结构:排序(学习笔记)

目录

  • 1. 各种排序算法的分类
  • 2. 插入排序
    • 2.1 直接插入排序
    • 2.2 希尔排序
  • 3. 选择排序
    • 3.1 选择排序
    • 3.2 堆排序
    • 4. 交换排序
    • 4.1 冒泡排序
    • 4.2 快速排序
    • 4.2.1 霍尔法(hoare)
    • 4.2.2 挖坑法(hole)
    • 4.4.3 前后指针法
    • 4.4.4 补充:非递归快排
    • 4.5 快排优化
  • 5. 归并排序
  • 6. 非比较排序:计数排序
  • 7. 排序算法的稳定性

1. 各种排序算法的分类

按照排序逻辑的不同,排序大体可以分为四大类:

  1. 插入排序
  2. 选择排序
  3. 交换排序
  4. 归并排序

接下来,我们进行这些排序的学习

!注:本章内容中的动图并非原创

2. 插入排序

2.1 直接插入排序

  1. 将整个数组的元素,从起始遍历,一次向后移动一步,看作是将一个元素插入到数组中。
  2. 在"插入"的过程中,当新插入的元素小于其前面的元素时,交换两者,循环此步骤,直至前面的元素不小于新插入的元素,到此成功插入一个元素。

过程演示:
在这里插入图片描述

void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){for (int j = i; a[j - 1] > a[j]; j--){swap(&a[j - 1], &a[j]);}}
}

2.2 希尔排序

  1. 根据给定组距,进行数据的分组,组内进行插入排序。
  2. 不断减小组距,直至组距为1。
  3. 注:在组距不为1时,都是预排序,让数据更接近有序。
//多趟排
void ShellSort1(int* a, int n)
{int gap = n / 2;while (gap > 0){for (int i = 0; i < gap; i++){for (int j = i; j + gap < n; j += gap){if (a[j] > a[j + gap]){swap(&a[j], &a[j + gap]);}}}gap /= 2;}
}//一趟排
void ShellSort2(int* a, int n)
{int gap = n / 2;while (gap > 0){for (int i = 0; i + gap < n; i++){if (a[i] > a[i + gap]){swap(&a[i], &a[i + gap]);}}gap /= 2;}
}

3. 选择排序

3.1 选择排序

  1. 遍历一次,从数据中选出最小(或最大)的数据放至数据首部。
  2. 多次遍历选择,直至将最后一个数据选走。

过程演示:
在这里插入图片描述

void SelectSort(int* a, int n)
{for (int i = 0; i < n; i++){int min = i;for (int j = i; j < n; j++){if (a[j] < a[min]){min = j;}}swap(&a[i], &a[min]);}
}

选择排序优化:

一次遍历选出最大值与最小值

void SelectSort2(int* a, int n)
{for (int i = 0; i < n / 2; i++){int max = i;int min = i;for (int j = i; j < n - i; j++){if (a[j] > a[max]){max = j;}if (a[j] < a[min]){min = j;}}swap(&a[max], &a[n - i - 1]);swap(&a[min], &a[i]);}
}

3.2 堆排序

  1. 建大堆
  2. 交换首尾,size–,向下调整,直到size为0
void AdjustDown(int* a, int n, int root)
{int child = root * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[root] < a[child]){swap(&a[root], &a[child]);}root = child;child = root * 2 + 1;}
}void HeapSort(int* a, int n)
{//建大堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//交换首尾,调整int size = n;while (size > 0){swap(&a[0], &a[size - 1]);size--;AdjustDown(a, size, 0);}
}

4. 交换排序

4.1 冒泡排序

  1. 建立两个一前一后的指针,用这两个指针遍历整个数组
  2. 若后指针指向的数据大于前指针指向的数据,交换前后指针所指向的元素,之后两指针++,直至遍历完数据,得出一个最大数,需遍历的数据长度减1,此为遍历一趟。
  3. 多次遍历,当长度为0时,排序结束

过程演示:
在这里插入图片描述

void BubbleSort(int* arr, int n)
{for (int i = 0; i < n; i++){int flag = 1;for (int j = 0; j + 1 < n - i; j++){if (arr[j] > arr[j + 1]){swap(&arr[j], &arr[j + 1]);flag = 0;}}if (flag){break;}}
}

4.2 快速排序

4.2.1 霍尔法(hoare)

  1. 将数据的首位确定为对照key,定义两个指针left(数据首部),right(数据尾部)。
  2. 右侧指针反向遍历数组,寻找小于key的值,当找到后停止,左侧指针正向遍历数组,寻找大于key的值,找到后将两指针指向的数据交换。
  3. 重复上述步骤2,直至左右指针相遇,交换key元素与左右指针同时指向的元素,此为一趟排序。
  4. 将数据分割为[0,key - 1]与[key + 1,n],在这两个区间内再进行上述步骤2,3。直至所有元素的位置都被确认。

过程演示:
在这里插入图片描述

// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int key = left;int keyi = a[key];while (left < right){while (left < right && a[right] >= keyi){right--;}while (left < right && a[left] <= keyi){left++;}swap(&a[left], &a[right]);}if (a[left] < keyi){swap(&a[left], &a[key]);key = left;}return key;
}

4.2.2 挖坑法(hole)

  1. 选择首位数据为key,然后将数据的首位标记为hole,创建两个指针left(首位 ),right(数据尾部)。
  2. 右侧指针找寻找小于key元素的值,找到后,将所找到的元素填充至挖好的"洞"里,此元素原位置标记为新的洞,然后,移动左侧指针寻找大于key元素的值,找到后,将找到的元素填入洞中。重复上述步骤,直至左右指针相遇,将key值填入左右指针相遇的位置,此时即确定好了key的位置。
  3. 在[left,key - 1]与[key + 1, right]的区间中,重复步骤2,直至所有位置都被确定。

过程演示:

在这里插入图片描述

int PartSort2(int* a, int left, int right)
{int hole = left;int keyi = a[hole];while (left < right){//额外检查,越界可能while (left < right && a[right] >= keyi){right--;}if (a[right] < keyi){a[hole] = a[right];hole = right;}while (left < right && a[left] <= keyi){left++;}if (a[left] > keyi){a[hole] = a[left];hole = left;}}a[hole] = keyi;return hole;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int key = PartSort2(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

4.4.3 前后指针法

思路1:

  1. 将数据首位设置为key,创建两个指针pre(首部),cur(首部 + 1)。
  2. cur开始遍历整个数组,如果cur指针指向的值小于key,那么pre指针一同++,否则pre指针不动,直至cur再次寻找到小于key的值,此时,pre++,然后将两指针指向的值交换。如此,反复直至cur遍历完整个数组,最后,将key与pre指针指向的值交换。
  3. 在[left,key - 1]与[key + 1, right]的区间中,重复步骤2,直至所有位置都被确定。

思路2:

  1. [left + 1,pre]区间为小于key的值,[pre + 1,cur - 1]为大于key的值,[cur,right]为未遍历到的值。
  2. cur指针遍历寻找小于pre指针的数据,找到后pre++,交换两指针所指向的值。

过程演示:
在这里插入图片描述

int PartSort3(int* a, int left, int right)
{int pre = left;int cur = left + 1;int keyi = a[left];while (cur <= right){if (a[cur] < keyi){swap(&a[++pre], &a[cur]);}cur++;}swap(&a[left], &a[pre]);return pre;
}void QuickSort(int* a, int left, int right)
{if (left >= right){return;}int key = PartSort3(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

4.4.4 补充:非递归快排

  1. 将原本递归传递的区间存储到栈中,用时从栈中取出
void QuickSortNonR(int* a, int left, int right)
{Stack stack;StackInit(&stack);//插入第一次遍历区间范围StackPush(&stack, left);StackPush(&stack, right);while (!StackEmpty(&stack)){//取出区间值进行运算right = StackTop(&stack);StackPop(&stack);left = StackTop(&stack);StackPop(&stack);int key = PartSort3(a, left, right);//区间遍历顺序:左区间,右区间if (key + 1 < right){StackPush(&stack, key + 1);StackPush(&stack, right);}if (left < key - 1){StackPush(&stack, left);StackPush(&stack, key - 1);}}
}

4.5 快排优化

  1. 三数取中(getmid)
  2. 当递归到小区间时,可以转而进行插入排序
//三数取中
int GetMidNum(int* a, int left, int right)
{int mid = (left + right) / 2;if(a[mid] > a[left]){if(a[mid] < a[right]){return mid;}else{if(a[right] > a[left]){return right;}else{return left;}}}else{if(a[mid] > a[right]){return mid;}else{if(a[left] < a[right]){return left;}else{return right;}}}
}

5. 归并排序

思路1:

归并逻辑:二叉树的遍历(深度优先:左右根)

  1. 将需要进行归并的区间范围视作结点,根结点的区间为整个数组
  2. 左右孩子结点为将区间范围一分为2,左孩子为前半区间,右孩子为后半区间
  3. 对每次得到的新区间都进行上述处理,直至区间中的元素数(<=2),即视为叶子结点。
  4. 按照后序遍历二叉树的顺序,对结点区间内的数据进行插入排序。

过程演示:
在这里插入图片描述
递归法:
在这里插入图片描述

void _mergesort(int* a, int* tmp, int left, int right)
{//深度优先if (left >= right){return;}int mid = (right + left) / 2;_mergesort(a, tmp, left, mid);_mergesort(a, tmp, mid + 1, right);//插入int i = left;int j = mid + 1;int k = left;while (i <= mid && j <= right){//当存在相同的数时if (a[i] <= a[j]){tmp[k++] = a[i++];}else{tmp[k++] = a[j++];}}while (i <= mid){tmp[k++] = a[i++];}while (j <= right){tmp[k++] = a[j++];}memcpy(a + left, tmp + left, (right - left + 1) * sizeof(int));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(n * sizeof(int));_mergesort(a, tmp, 0, n - 1);free(tmp);
}

思路2:

  1. 将数组的元素分为两个两个一组,将每一组都使用插入排序调整为有序,遍历一遍数组
  2. 将一组中的元素数翻倍,重复步骤1,遍历完成再次翻倍,直至一组中的元素数包含整个数组,排序完成
  3. 当剩余元素不足一组时,将剩余元素也算作一组

在这里插入图片描述

非递归法:

void MergeSortNonR(int* a, int n)
{int* c_a = (int*)malloc(n * sizeof(int));int gap = 1;while (gap < n){//确定初始区间int begin1 = 0;int end1 = begin1 + gap - 1;int begin2 = end1 + 1;int end2 = begin2 + gap - 1;//检测防止越界if (end2 >= n){end2 = n - 1;}while (begin1 < n){//插入int i = begin1;int j = begin2;int k = begin1;while (i <= end1 && j <= end2){if (a[i] <= a[j]){c_a[k++] = a[i++];}else{c_a[k++] = a[j++];}}while (i <= end1){c_a[k++] = a[i++];}while (j <= end2){c_a[k++] = a[j++];}//拷贝回原数组memcpy(a + begin1, c_a + begin1, (end2 - begin1 + 1) * sizeof(int));//向后调整区间begin1 = end2 + 1;end1 = begin1 + gap - 1;begin2 = end1 + 1;end2 = begin2 + gap - 1;//判断是否越界if (end1 >= n){end1 = n - 1;}if (end1 < n && end2 >= n){end2 = n - 1;}}gap *= 2;}free(c_a);
}

优化:

void MergeSortNonR(int* arr, int n)
{int* tmp = (int*)malloc(n * sizeof(int));//确定间距int gap = 1;while (gap < n){//i中间记录for (int i = 0; i < n; i += 2 * gap){int begin1 = i;int end1 = begin1 + gap - 1;int begin2 = end1 + 1;int end2 = begin2 + gap - 1;//上一趟已经遍历过if (end1 >= n){break;}if (end2 >= n){end2 = n - 1;}int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (arr[begin1] <= arr[begin2]){tmp[index++] = arr[begin1++];}else{tmp[index++] = arr[begin2];}}while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}memcpy(arr + i, tmp + i, (end2 - i + 1) * sizeof(int));}gap *= 2;}
}

6. 非比较排序:计数排序

  1. 根据数据的范围,创建一个合适大小的数组。
  2. 下标对应数据,根据数据中各个数字的出现次数在对应的下标处计数++。
  3. 限制:
    <1> 数据范围不可跨度太大,会导致空间复杂度过高
    <2>只能用来处理整形数据。

过程演示:
在这里插入图片描述

void CountSort(int* a, int n)
{//选出最大值与最小值int min = a[0];int max = a[0];for (int i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}//开辟空间int size = max - min + 1;int* count = (int*)malloc(size * sizeof(int));memset(count, 0, n * sizeof(int));//计数for (int i = 0; i < n; i++){count[a[i] - min]++;}//读数int index = 0;for (int i = 0; i < size; i++){while (count[i]){a[index++] = i + min;count[i]--;}}
}

7. 排序算法的稳定性

算法稳定性的判断标准:数据中相同数据在排序后,他们的相对位置是否变化。

在这里插入图片描述

  1. 直接插入排序(稳定,时间复杂度:O( n 2 n^2 n2))
  2. 希尔排序(不稳定,时间复杂度略小于O( n 2 n^2 n2))
  3. 选择排序(稳定,O( n 2 n^2 n2))
  4. 堆排序(不稳定,O( n n n * logn))
  5. 冒泡排序(稳定,O( n 2 n^2 n2))
  6. 快速排序(不稳定,O( n n n * logn))
  7. 归并排序(不稳定,O( n n n * logn))
  8. 计数排序(稳定,O(n,Max))

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

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

相关文章

存货计价方式 比较-移动平均和批次计价

SAP常用的存货计价方式有 标准价格移动平均价格批次计价 标准价格常用于制造企业&#xff0c;今天的方案比较主要集中在销售型企业常用的移动平均价和批次计价 批次计价&#xff1a; 移动平均&#xff1a; 两种计价方式的Pros&Cons 比较 批次计价 移动平均优点 1…

超好用的一键生成原创文案方法

在现代社会中&#xff0c;原创文案不管是在营销中&#xff0c;还是在品牌推广中都起着至关重要的作用。然而&#xff0c;对于许多人来说&#xff0c;创作出令人印象深刻且引人注目的原创文案并不容易。但随着技术的发展&#xff0c;我们现在可以利用一键生成原创文案的方法来帮…

黑马java-JavaSE进阶-java高级技术

1.单元测试 就是针对最小的功能单元方法&#xff0c;编写测试代码对其进行正确性测试 2.Junit单元测试框架 可以用来对方法进行测试&#xff0c;它是第三方公司开源出来的 优点&#xff1a; 可以灵活的编写测试代码&#xff0c;可以针对某个方法执行测试&#xff0c;也支持一键…

基于springboot的水果购物商城管理系统(程序+文档+数据库)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

Pinctrl子系统_04_Pinctrl子系统主要数据结构

引言 本节说明Pinctrl子系统中主要的数据结构&#xff0c;对这些数据结构有所了解&#xff0c;也就是对Pinctrl子系统有所了解了。 前面说过&#xff0c;要使用Pinctrl子系统&#xff0c;就需要去配置设备树。 以内核面向对象的思想&#xff0c;设备树可以分为两部分&#x…

rabbitmq3

指定通过通道将消息发送给哪个消息队列 同一个通道可以向不同的队列发送消息的&#xff0c;如果你绑定的队列和发布消息的队列不一致也是可以的&#xff0c;这个才是真正的发布消息去具体的某一个队列&#xff1a; 如果队列没有持久化&#xff0c;就不会把这个消息队列保存在磁…

stm32学习记录-5.2PWM输出控制sg90舵机角度

源码连接&#xff1a;https://gitee.com/HL12334/stm32-learning-code 前提知识&#xff1a; 1.定时器中断 1.关键概念 1.1pwm输出 1.常用术语 OC&#xff08;output compare&#xff09;输出比较CNT&#xff08;counter&#xff09;&#xff0c;定时器中用于计数的寄存器…

时序报告Report_timing_summary之一步精通配置选项使用

目录 一、前言 二、配置选项概览图 三、配置选项 3.1 Options 3.1.1 report 3.1.2 path limits 3.1.3 path display 3.2 Advanced 3.2.1 report 3.2.3 miscellaneous 3.3 Timer Settings 3.4 共有部分 四、工程示例 4.1 工程设计代码 4.2 约束文件 4.3 Option…

Linux系统架构----nginx的访问控制

nginx的访问控制 一、nginx基于授权的访问控制概述 Nginx与Apache一样&#xff0c;可以实现基于用户权限的访问控制&#xff0c;当客户端想要访问相应的网站或者目录时&#xff0c;要求用户输入用户名和密码&#xff0c;才能正常访问配置步骤生成用户密码认证文件 &#xff1…

qt带后缀单位的QLineEdit

QLineEditUnit.h #pragma once #include <QLineEdit> #include <QPushButton>class QLineEditUnit : public QLineEdit {Q_OBJECT public:QLineEditUnit(QWidget* parent Q_NULLPTR);~QLineEditUnit();//获取编辑框单位QString UnitText()const;//设置编辑框单位…

STM32的启动流程分析 和 一些底层控制的原理

阅读引言&#xff1a; 阅读本文之后&#xff0c; 你将对单片机&#xff0c; 甚至是嵌入式系统&#xff0c; 或者是传统的PC机系统的启动流程有一个大致的了解&#xff0c; 本文更加偏向于单片机的启动流程分析。 目录 一、基础知识 1.STM32系列的微控制器&#xff08;mcu&…

自研cloud框架专题–web模块(三)

项目特点一:框架集成 1.引入核心依赖2.配置相关功能 二:功能介绍 1.swagger支持并提供swagger快速配置2.knife增强swagger支持3.全局请求参数校验(Validation)支持4.字段脱敏支持5.默认jackson序列化6.xss,cors支持7.访问日志支持8.全局异常处理,统一返回结果9.系统关键及常用信…

数据结构之deque双端队列

一、概念&#xff1a; 众所周知&#xff0c;数据结构是用来存储数据&#xff0c;deque也不例外&#xff0c;他是集结了队列和栈的性质而成的结构&#xff0c;他几乎拥有所有数据结构能有的操作&#xff0c;看似已经大杀四方&#xff0c;可实际情况如何呢&#xff0c;那就带者这…

Day 8.TCP包头和HTTP

TCP包头 1.序号&#xff1a;发送端发送数据包的编号 2.确认号&#xff1a;已经确认接收到的数据的编号&#xff08;只有当ACK为1时、确认号才有用&#xff09;&#xff1b; TCP为什么安全可靠 1.在通信前建立三次握手 SYP SYPACK ACK 2.在通信过程中通过序列号和确认号和…

前端javascript的DOM对象操作技巧,全场景解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端泛海 景天的主页&#xff1a;景天科技苑 文章目录 1.js的DOM介绍2.节点元素层级关系3.通过js修改&#xff0c;清空节点…

【棘手问题】Spring JPA一级缓存导致获取不到数据库表中的最新数据,对象地址不发生改变

【棘手问题】Spring JPA一级缓存导致获取不到数据库表中的最新数据&#xff0c;对象地址不发生改变 一、问题背景二、解决步骤2.1 debug2.2 原因分析2.2.1 数据步骤2.2.2 大模型解释2.2.3 解释举例2.2.4 关键函数 2.3 解决方案 三、Spring JPA一级缓存 一、问题背景 项目的数据…

STM32 通过Modbus协议更改内部Flash(模仿EEPROM)的运行参数

main.c测试 uint8_t uart1RxBuf[64]{0};uint8_t Adc1ConvEnd0; uint8_t Adc2ConvEnd0;int main(void) {/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initial…

C++ 智能指针深度剖析

文章目录 1. 前言2. 为什么需要智能指针&#xff1f;3. 内存泄漏3.1 内存泄漏的概念及危害3.2 内存泄漏的分类3.3 如何检测内存泄漏3.4 如何避免内存泄漏 4. 智能指针的使用及原理4.1 RAII思想4.2 智能指针的原理4.3 C智能指针发展历史4.4 std::auto_ptr4.5 std::unique_ptr4.6…

掌握MySQL,看完这篇文章就够了!

1. MySQL MySQL是一种广泛使用的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典的MySQL AB公司开发&#xff0c;目前属于甲骨文公司&#xff08;Oracle Corporation&#xff09;。 MySQL使用结构化查询语言&#xff08;SQL&#xff09;进行数据库管理…

C++的面向诗篇:类的叙事与对象的旋律

个人主页&#xff1a;日刷百题 系列专栏&#xff1a;〖C/C小游戏〗〖Linux〗〖数据结构〗 〖C语言〗 &#x1f30e;欢迎各位→点赞&#x1f44d;收藏⭐️留言&#x1f4dd; ​ ​ 一、面向对象的定义 学习C语言时&#xff0c;我们就经常听说C语言是面向过程的&#xff0c;…