【数据结构】排序算法——Lessen1

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言

🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。


目录

  • 前言
  • 一、常见排序算法
    • 1、插入排序
      • 1.1直接插入排序
      • 1.2希尔排序
    • 2、选择排序
      • 2.1直接选择排序
      • 2.2堆排序
    • 3、交换排序
      • 3.1冒泡排序
      • 3.2快速排序
        • 3.2.1 hoare版本
        • 3.2.2 挖坑法
        • 3.2.3 前后指针法

前言

所谓排序,就是将一组数据按照某种顺序进行排列的过程。
排序在很多应用中都非常重要,比如数据分析、搜索算法、数据库管理等。
本篇文章将详细介绍常见的排序算法。


一、常见排序算法

1、插入排序

1.1直接插入排序

插入排序是一种简单直观的排序算法,它的基本思想是将待排序的元素分为已排序和未排序两部分,然后逐步将未排序的元素插入到已排序的部分中,直到所有元素都被排序。

动图演示:

请添加图片描述

插入排序是比较容易理解的,代码实现也比较简单,唯一需要注意的是下标的取值范围,因为我们是从下标为1的元素开始向前比较的,所以下标最大的取值应该为n-2,这样保证我们刚好取到下标为n-1的最后一个元素。

代码示例:

//插入排序(升序)
void InsertSort(int* arr, int n)
{//先单次,再整体for (int i = 0; i < n - 1; i++){//假设[0,end]有序,将end+1位置的元素插入int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}

插入排序时间复杂度是O(N^2),总体来说效率也是不高的,但在某些场景中还是有发挥空间。


1.2希尔排序

希尔排序是一种插入排序的改进版本,以增量gap(通常是gap = n/3 + 1)来划分元素,使得远离的元素能够交换,从而加快整体排序的速度。它的基本思想是将待排序的序列分成若干个子序列(也称为“增量”),对每个子序列进行直接插入排序,然后逐步减少增量(gap = gap / 3 + 1),最终进行一次普通的插入排序,从而实现整体排序。

  • 当gap > 1时都是预排序,目的是让数组更接近于有序
  • gap = gap / 3 + 1;后面+1是为了保证最后一次gap的值为1

当gap=1时,相当于直接插入排序,希尔排序是直接插入排序算法上改进而来的,综合来说它的效率肯定是要高于直接插入排序的。

在这里插入图片描述

代码示例:
(1)

//希尔排序
void ShellSort(int* arr, int n)
{//一组一组排int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < gap; i++){int end = i;int tmp = arr[end + gap];for (int j = i; j < n - gap; j += gap){while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}}
}

(2)

//希尔排序
void ShellSort(int* arr, int n)
{//多组并着走int gap = n;while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end -= gap;}else{break;}}arr[end + gap] = tmp;}}
}

这两种方式都是可以的,虽然循环层数不一样,但是时间复杂度是一样的。
希尔排序的时间复杂度比较特别,理想情况下希尔排序的时间复杂度为:O(N^1.3)。希尔排序和堆排序是一个量级的。

根据希尔排序的特点,可以总结出下面的折线图:
在这里插入图片描述

因此,希尔排序在最初和最后的排序次数都为n,即前一阶段排序次数逐渐上升,当到达某一顶点时,排序次数逐渐下降到n,而该顶点非常难计算。

因为gap的取值很多,导致希尔排序的时间复杂度不好计算,我们只需记住当n在某个特定返回内,时间复杂度约为O(N^1.3)


2、选择排序

2.1直接选择排序

选择排序的基本思想是每一次从待排序的数组中选出最小(或最大)的一个数,存在起始位置,知道全部数据排完。为了提高效率,我们每次从待排序数组中同时找到最小和最大的两个数,分别将它们放到起始、末尾位置。

动图演示:

请添加图片描述

代码示例:

//选择排序
void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){int mini = begin;int maxi = begin;for (int i = begin + 1; i <= end; i++){if (arr[i] < arr[mini]){mini = i;}if (arr[i] > arr[maxi]){maxi = i;}}Swap(&arr[mini], &arr[begin]);if (begin == maxi){maxi = mini;}Swap(&arr[maxi], &arr[end]);begin++;end--;}
}

直接选择排序的时间复杂度是O(N^2),效率很低,甚至还比不过冒泡排序,因此在实际中基本不使用,主要用于教学。


2.2堆排序

堆排序在之前的文章中有详解介绍,这里就不再赘述。

void HeapSort(int* arr, int n)
{//升序,建大堆//降序,建小堆//向下调整建堆//O(N)for (int i = (n-1-1) / 2; i >= 0; i--){AdjustDown(arr, i, n);}//O(N*logN)int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdjustDown(arr, 0, end);end--;}
}

3、交换排序

3.1冒泡排序

冒泡排序是一种简单的排序算法,其基本原理是通过重复遍历待排序的元素,比较相邻的两个元素,如果顺序错误则交换它们,直到没有需要交换的元素为止。

当某一趟并未发生交换说明此时数组已经有序,可以提前退出循环,我们可以设定一个标志来判断某一趟是否未发生交换。

动图演示:

请添加图片描述

代码示例:

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

冒泡排序的时间复杂度是O(N^2),效率是比较低的,在实际应用中很少用到,主要用于教学。


3.2快速排序

快速排序是一种二叉树结构的交换排序法,其基本思想是:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列有序。

快排主框架:

void _QuickSort(int* arr, int left, int right)
{//递归结束条件if (left >= right){return;}//_QuickSort用于按照基准值将区间[left, right)中的元素进行划分QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

将区间中的元素进行划分的_QuickSort方法主要有以下几种实现方法。


3.2.1 hoare版本

| 算法思路: (默认排升序)

  • 创建左右指针,确定基准值
  • 从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环
  • 如果在最左边找基准值,则右边先走,反之则左边先走

动图演示:

请添加图片描述

我们可以将最左边的数作为基准值,先从右向左找小于基准值的数记住当前位置end,然后从左向右找大于基准值的数记住当前位置begin,交换两个数的位置;重复上述操作直到beginend指向同一位置,此时这个位置的值一定小于基准值,最后再将这个位置的值与基准值交换位置,此时作为基准值的数已经排到了它相应的位置。

代码示例:

//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}int keyi = left;int begin = left;int end = right;while (begin < end){//右边找小while (begin < end && arr[end] >= arr[keyi]){end--;}//左边找大while (begin < end && arr[begin] <= arr[keyi]){begin++;}Swap(&arr[begin], &arr[end]);}Swap(&arr[begin], &arr[keyi]);keyi = begin;QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

但是此时的快排代码还有一些缺陷,当待排集合已经接近有序时,快排的效率是很低的,如果数据量比较大还会因为函数递归太深而导致栈溢出。
因为我们将最左边的数当做基准值时,如果这个数恰好是最小或比较小的数,此时从右向左的数基本都比这个基准值大,所以会有很多次的递归调用。

| 优化一:
解决这个问题的办法就是我们找基准值时尽量找数据中不大不小的数,可以粗略的从数据头、数据中、数据尾三个位置的值中选中间值作为基准值,这样就可以很大的降低递归太深而导致栈溢出的风险。

//三数取中
int GetMidi(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[left] < arr[right]){if (arr[right] < arr[midi ]){return right;}else if (arr[left] < arr[midi ])//arr[left]<arr[right] &&//arr[midi ]<arr[right]{return midi ;}else{return left;}}else{if (arr[right] > arr[midi ]){return right;}else if (arr[left] > arr[midi ]){return midi ;}else{return left;}}
}

拿到理想的基准值后,为了保证我们之前写的按最左边数作为基准值,可以将这个基准值和最左边的数交换位置。

//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi ]);int keyi = left;int begin = left;int end = right;while (begin < end){//右边找小while (begin < end && arr[end] >= arr[keyi]){end--;}//左边找大while (begin < end && arr[begin] <= arr[keyi]){begin++;}Swap(&arr[begin], &arr[end]);}Swap(&arr[begin], &arr[keyi]);keyi = begin;QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

| 优化二:
我们知道快速排序是二叉树结构的排序算法,当二叉树的层数较高时,此时分割的区间数据量比较小,而递归次数又占总体的大部分,可以考虑小区间优化,不再递归分割排序,减少递归的次数。
当数据量比较小时,排序算法的效率基本一致,我们优先考虑简单且较为高效的插入排序

//快速排序
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}//小区间优化,不再递归分割排序,减少递归次数if ((right - left) + 1 < 10){InsertSort(arr + left, (right - left) + 1);}else{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi ]);int keyi = left;int begin = left;int end = right;while (begin < end){//右边找小while (begin < end && arr[end] >= arr[keyi]){end--;}//左边找大while (begin < end && arr[begin] <= arr[keyi]){begin++;}Swap(&arr[begin], &arr[end]);}Swap(&arr[begin], &arr[keyi]);keyi = begin;QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}
}

有了这两个优化,快排的效率就能得到很大的提升。


3.2.2 挖坑法

| 算法思路: (默认排升序)

首先将最左边的数据作为基准值拿出来,则原位置形成一个空位,创建左右指针,从右向左找出比基准值小的数据,找到后立即放入左边坑中,当前位置变为新的“坑”,重复上述操作直到左右指针相遇,最后再将基准值放入左右指针共同指向的“坑”中。

动图演示:

请添加图片描述
代码示例:

//挖坑法
void QuickSort(int* arr, int left, int right)
{//递归结束条件if (left >= right){return;}//区间优化,减少递归次数if (right - left + 1 < 10){InsertSort(arr + left, right - left + 1);}else{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int tmp = arr[keyi];int begin = left;int end = right;while (begin < end){while (begin < end && arr[end] >= tmp){end--;}arr[keyi] = arr[end];while (begin < end && arr[begin] <= tmp){begin++;}if (begin == end){arr[begin] = tmp;break;}arr[end] = arr[begin];keyi = begin;}keyi = begin;QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}
}

挖坑法虽然没有效率的提升,但是相对于hoare版本还是有一些优势:

  • 挖坑法不用分析为什么左边取基准值而右边先走的问题,因为当左边取基准值时左边就自然而然的形成了一个“坑”
  • 也不用分析为什么相遇位置一定比基准值小(或大)的问题,因为它相遇的位置是坑,自然而然的基准值就应该放在坑里

3.2.3 前后指针法

| 算法思路:
创建前后指针,两个指针从左向右分别找遇到的第一个小于和大于基准值的数进行交换,将所有大于基准值的数全都推到最后,使得小的都排在基准值的左边,大的都排在基准值的右边。

动图演示:
请添加图片描述
代码实现:

//前后指针法
int QuickSort3(int* arr, int left, int right)
{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){//先判断大小,再++prev,保证了当不满足大小时prev不再++if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[keyi], &arr[prev]);return prev;
}

这三种快排算法,前后指针法代码是最为清晰明了的,但三种算法时间复杂度是一样的。

| 三种快排算法汇总:

//三数取中
int GetMidi(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[left] < arr[right]){if (arr[right] < arr[midi]){return right;}else if (arr[left] < arr[midi]){return midi;}else{return left;}}else{if (arr[right] > arr[midi]){return right;}else if (arr[left] > arr[midi]){return left;}else{return midi;}}
}//hoare法
int QuickSort1(int* arr, int left, int right)
{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int begin = left + 1;int end = right;while (begin < end){//左边作基值,右边先走while (begin < end && arr[end] >= arr[keyi]){end--;}while (begin < end && arr[begin] <= arr[keyi]){begin++;}Swap(&arr[begin], &arr[end]);}Swap(&arr[begin], &arr[keyi]);return begin;
}//挖坑法
int QuickSort2(int* arr, int left, int right)
{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int tmp = arr[keyi];int begin = left + 1;int end = right;while (begin < end){while (begin < end && arr[end] >= tmp){end--;}arr[keyi] = arr[end];while (begin < end && arr[begin] <= tmp){begin++;}if (begin == end){arr[begin] = tmp;break;}arr[end] = arr[begin];keyi = begin;}return begin;
}//前后指针法
int QuickSort3(int* arr, int left, int right)
{//三数取中int midi = GetMidi(arr, left, right);Swap(&arr[left], &arr[midi]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){//先判断大小,再++prev,保证了当不满足大小时prev不再++if (arr[cur] < arr[keyi] && ++prev != cur){Swap(&arr[prev], &arr[cur]);}cur++;}Swap(&arr[keyi], &arr[prev]);return prev;
}void QuickSort(int* arr, int left, int right)
{//递归结束条件if (left >= right){return;}//区间优化,减少递归次数if (right - left + 1 < 10){InsertSort(arr + left, right - left + 1);}else{int keyi = QuickSort3(arr, left, right);QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}
}

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

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

相关文章

防御保护课-防火墙接口配置实验

一、实验拓扑 &#xff08;我做实验用的图如下&#xff09; 二、实验要求 1.防火墙向下使用子接口分别对应生产区和办公区 2.所有分区设备可以ping通网关 三、实验思路 配IP&#xff1b; 划分vlan并配置vlan&#xff1b; 配置路由和安全策略。 四、实验配置 1、画图并…

【引领未来智造新纪元:量化机器人的革命性应用】

在日新月异的科技浪潮中&#xff0c;量化机器人正以其超凡的智慧与精准的操作&#xff0c;悄然改变着各行各业的生产面貌&#xff0c;成为推动产业升级、提升竞争力的关键力量。今天&#xff0c;让我们一同探索量化机器人在不同领域的广泛应用价值&#xff0c;见证它如何以科技…

相对定位语法:css+xpath基础语法使用-定位页面元素

文章目录 CSS相对定位获取元素关系定位顺序关系 XPath相对定位基础语法顺序关系-通过索引获取元素选取元素 总结 ✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来&#xff01; 编程真是一件很奇妙的东西。你只是浅尝辄止&#xff0c;那么只会觉得枯燥乏味&#xff0c…

python基础语法 007 文件操作-2文件支持模式文件的内置函数

1.3 文件支持的模式 模式含义ropen a file for reading(default)wopen a file for writing,creates a new file if it does not exist or truncates the file if it exists x open a file foe exclusive creation. if the file already exists, the operation fails.独创模式&…

数据结构day4

一、思维导图 二、课后练习 头文件 #ifndef LINKLIST_H #define LINKLIST_H #include <myhead.h>//定义数据类型 typedef int datatype;//定义节点类型 typedef struct Node {union{int len; //头结点数据域datatype data; //普通节点数据域};struct Node *next; …

PyTorch 深度学习实践-处理多维特征的输入

视频指路 参考博客笔记 参考笔记二 通过多个线性模型来模拟非线性的空间变换&#xff0c;矩阵计算就是不同维度之间的空间转换 说明&#xff1a;1、乘的权重(w)都一样&#xff0c;加的偏置(b)也一样。b变成矩阵时使用广播机制。神经网络的参数w和b是网络需要学习的&#xff0c…

浏览器跨tab页面通信方式总结

需求&#xff1a; 浏览器不同 tab 标签页之间是独立的&#xff0c; 如果要通信必须通过特殊手段来实现跨标签页通信。 1.StorageEvent 事件 当一个标签页 localStorage 变化时&#xff08;sessionStorage 无效&#xff09;&#xff0c;同源下另一个或其他所有标签页使用 DO…

buuctf web 第五到八题

[ACTF2020 新生赛]Exec 这里属实有点没想到了&#xff0c;以为要弹shell&#xff0c;结果不用 127.0.0.1;ls /PING 127.0.0.1 (127.0.0.1): 56 data bytes bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var127.0.0.1;tac /f*[GXYCTF2019]Pin…

React的usestate设置了值后马上打印获取不到最新值

我们在使用usestate有时候设置了值后&#xff0c;我们想要更新一些值&#xff0c;这时候&#xff0c;我们要想要马上获取这个值去做一些处理&#xff0c;发现获取不到&#xff0c;这是为什么呢&#xff1f; 效果如下&#xff1a; 1、原因如下 在React中,当你使用useState钩子…

设计模式11-原型模式

设计模式11-原型模式 写在前面对象创建模式典型模式原型模式动机结构代码推导应用特点要点总结 原型模式与工厂方法模式对比工厂方法模式原型模式什么时候用什么模式 写在前面 对象创建模式 通过对象创建模式绕开动态内存分配来避免创建过程中所导致的耦合过紧的问题。从而支…

谷粒商城实战笔记-40-前端基础-Vue-计算属性、监听器、过滤器

文章目录 一&#xff0c;计算属性1&#xff0c;用途2&#xff0c;用法2.1 定义View2.2 声明计算属性 3&#xff0c;注意事项 二&#xff0c;监听器1. 使用 watch 监听属性的变化 三&#xff0c;过滤器1&#xff0c;定义局部过滤器2&#xff0c;定义全局过滤器3&#xff0c;使用…

GraphPad prism处理cck-8获得ic50

C组为空白对照组&#xff0c;a组为dmso对照组&#xff0c;b组为细胞加药组&#xff0c;八个梯度的药物浓度 一、数据转化 首先&#xff0c;打开软件&#xff0c;选项中选择x的第一项&#xff0c;y的第二项&#xff0c;单一药物浓度设定了几个孔就选几 把自己的药物浓度直接复制…

C语言·函数(超详细系列·全面总结)

前言&#xff1a;Hello大家好&#x1f618;&#xff0c;我是心跳sy&#xff0c;为了更好地形成一个学习c语言的体系&#xff0c;最近将会更新关于c语言语法基础的知识&#xff0c;今天更新一下函数的知识点&#xff0c;我们一起来看看吧&#xff01; 目录 一、函数是什么 &a…

一分钟图情论文:《演化视角下图像的语义表示》

随着图像资源的不断积累&#xff0c;如何有效地表示图像的语义信息成为提高图像检索效率的关键问题。由武汉大学信息管理学院的李旭晖、吴燕秋和王晓光教授合著论文《演化视角下图像的语义表示》中提出了一种基于“演化”视角的图像语义层次描述框架来剖析图像的语义表示问题。…

Android11 framework 禁止三方应用开机自启动

Android11应用自启动限制 大纲 Android11应用自启动限制分析验证猜想&#xff1a;Android11 AOSP是否自带禁止三方应用监听BOOT_COMPLETED​方案禁止执行非系统应用监听到BOOT_COMPLETED​后的代码逻辑在执行启动时判断其启动的广播接收器一棍子打死方案&#xff08;慎用&#…

DevExpress WPF中文教程 - 为项目添加GridControl并将其绑定到数据

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

【思科】链路聚合实验配置和背景

【思科】链路聚合实验配置和背景 背景链路聚合基本概念链路聚合聚合接口 思科链路聚合协议01.PAgP协议02.LACP协议 思科链路聚合模式LACP协议模式PAgP协议模式ON模式 实验准备配置二层链路聚合LACP协议模式SW1SW2PC1PC2查看LACP聚合组建立情况查看LACP聚合端口情况查看逻辑聚合…

2-40 基于Matlab编写的3维FDTD(时域有限差分算法)计算了球的RCS经典散射问题

基于Matlab编写的3维FDTD(时域有限差分算法)计算了球的RCS经典散射问题&#xff0c;采用PEC作边界&#xff0c;高斯波束激励。程序已调通&#xff0c;可直接运行。 2-40 3维FDTD 时域有限差分算法 - 小红书 (xiaohongshu.com)

Python自动化DevOps任务入门

目录 Python自动化DevOps任务入门 一、环境和工具配置 1. 系统环境与Python版本 2. 虚拟环境搭建 3. 必要的库安装 二、自动化部署 1. 使用Fabric进行流式部署 2. 使用Ansible编写部署剧本 三、持续集成和测试 1. 配置CI/CD工具 选择工具 配置工具 构建和测试自动…

深入理解设计模式:六大经典模式解析

深入理解设计模式&#xff1a;六大经典模式解析 1. 单例模式&#xff08;Singleton Pattern&#xff09;1.1 概述1.2 示例场景1.3 实现要点 2. 工厂模式&#xff08;Factory Pattern&#xff09;2.1 简单工厂2.2 抽象工厂2.3 示例场景2.4 实现要点 3. 观察者模式&#xff08;Ob…