【数据结构】常见的排序算法

常见的排序算法

  • 常见的排序算法
  • 插入排序之直接插入排序
    • 时间复杂度
    • 特性总结
  • 插入排序之希尔排序
    • 时间复杂度
  • 选择排序之直接选择排序
    • 特性总结
  • 选择排序之堆排序
    • 时间复杂度
    • 特性总结
  • 交换排序之冒泡排序
    • 特性总结
  • 交换排序之快速排序
    • hoare版本
    • 挖坑法
    • 双指针法
    • 快速排序的优化1,增加三数取中
    • 快速排序的优化2,将递归算法改为非递归算法
    • 快速排序的性能总结
  • 归并排序
    • 归并排序特性总结

常见的排序算法

常见的七大排序算法:
在这里插入图片描述

插入排序之直接插入排序

在这里插入图片描述

void InsertOrder(vector<int>& v)
{for (int i = 0; i < v.size() - 1; ++i){//起始end为0int end = i ;//比较的值是下标为end的下一个int tmp = v[end + 1];while (end >= 0){//如果end对应的值比tmp大,说明需要进行插入排序if (v[end] > tmp){v[end + 1] = v[end];//插入完成后end要向前走end--;}//如果end对应的值比tmp小,说明不需要进行插入,跳出当前while循环,end向后走即可elsebreak;}//一趟完成后,要将tmp赋给end的下一个位置v[end + 1] = tmp;}
}

时间复杂度

最好的情况是O(n),数组为升序
最坏的情况是O(n2),数组为降序

特性总结

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

插入排序之希尔排序

在这里插入图片描述

针对直接插入排序中,当数组属于降序时,时间复杂度太高,设计了希尔排序
设计思路是:当数组降序时,使用分组,可以减小排序次数,逐渐减小分组间隙,当排序次数较多时,数组本身已经快要接近有序了,以此来解决数据降序时排序复杂度高的问题。
因此希尔排序是对直接插入排序的优化

void ShellSort(vector<int>& v)
{int gap = v.size();while (gap > 1){gap = gap / 3 + 1;for (int i = 0; i < v.size() - gap; ++i){int end = i ;int tmp = v[end + gap];while (end >= 0){if (v[end] > tmp){v[end + gap] = v[end];end -= gap;}elsebreak;}v[end + gap] = tmp;}}
}

时间复杂度

O(n1.25)

选择排序之直接选择排序

设计思路是:
1.从前往后遍历整个数组,拿到当前数组最大和最小值
2.将最小值和第一个元素交换,最大值和最后一个元素交换
3.缩小数组的范围,继续上述的操作
4.直到新数组范围内只有一个元素为止
在这里插入图片描述

void swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}
void SelectSort(vector<int>& v)
{int begin = 0;int end = v.size() - 1;while (begin < end){int mini = begin;int maxi = begin;for (int i = begin; i <= end; ++i){if (v[mini] > v[i])mini = i;if (v[maxi] < v[i])maxi = i;}if (maxi == begin)maxi = mini;swap(&v[mini], &v[begin]);swap(&v[maxi], &v[end]);begin++;end--;}
}

特性总结

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

选择排序之堆排序

思路:通过使用大堆或者小堆的思路,将数组进行排序
1.排升序建大堆,排降序建小堆
2.升序为例,先建一个大堆(双亲节点都大于左右孩子,根节点的值最大)
3.将数组最后一个位置的值和第一个位置的值交换
4.堆中除了最后一个节点,其余的节点进行调整,调整为新的大堆,将新的大堆的根节点值和倒数第二个节点的值交换
5.以此类推,直到当前新堆的范围为1停止
6.堆排序完成

void adjustDown(int* a, int n, int parent) {int child = parent * 2 + 1;while (child < n){if (child+1 < n && a[child] < a[child + 1]) {child++;}if (a[child] > a[parent]) {Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else {break;}}
}
void HeapSort(int* a, int n) {//1、建堆//parent = (child-1)/2for (int i = (n-1-1)/2; i >= 0; i--){adjustDown(a, n, i);}//2、堆排序int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);end--;adjustDown(a, end, 0);}
}

时间复杂度

O(nlogn)

特性总结

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

交换排序之冒泡排序

思想:就是从前往后,依次判断当前位置的值和后一个位置的值关系,如果当前值大于后一个位置的值,就进行交换,这样将最大的值就放到的最后面,依次让数组的范围从后往前减小,循环遍历数组,就可完成排序

void BobbleSort(int* v, int size)
{int exchage = 0;for (int i = 0; i < size ; ++i){for (int j = 0; j < size - i - 1; ++j){if (v[j] > v[j + 1]){swap(v[j], v[j + 1]);exchage = 1;}}if (exchage == 0)break;}
}

特性总结

  1. 冒泡排序是一种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定

交换排序之快速排序

hoare版本

在这里插入图片描述

思想:将某个元素作为基准值,数组被该元素分为两个子数组,左子数组中的值均小于基准值,右子数组中的值均大于基准值,左右子数组重复上述过程,最终实现排序

做法
1.先设置数组的第一个元素为基准值,设置第一个位置为左,最后一个位置为右
2.右先走,找比基准值小的,左后走,找比基准值大的
3.交换左右两个位置的值
4.右继续向左走,左继续向右走
以上的条件都是在左小于右的基础上进行的
5.如果相遇则将基准值和相遇值交换,函数返回相遇点

int _QuickSort(int* v, int left, int right)
{int k = left;while (left < right){//从右往左遍历,找比k小的数while (left < right && v[right] >= v[k]){right--;}//从左往右遍历,找比k大的数while (left < right && v[left] <= v[k]){left++;}//找到之后,进行交换swap(&v[left], &v[right]);}//如果相遇,将相遇点和数组左边的值交换swap(&v[left], &v[k]);return left;
}
void QuickSort(int* v, int left, int right)
{if (left >= right)return;int meet = _QuickSort(v, left, right);QuickSort(v, left, meet - 1);QuickSort(v, meet + 1, right);
}

挖坑法

在这里插入图片描述

思路
将某个元素作为基准值,数组被该元素分为两个子数组,左子数组中的值均小于基准值,右子数组中的值均大于基准值,左右子数组重复上述过程,最终实现排序
做法
1.设置一个坑,hole为最左边的下标,一个key值
2.让右下标向左走,找比key对应值小的,让坑对应的值赋给右下标位置,并且让右下标表示为hole
3.让左下标向右走,找比key对应值大的,让坑对应的值赋给左下标位置,并且让左下标表示为hole
如果遇到左下标大于等于右下标的情况,则将key对应的值赋给相遇点
上述操作是在左小于右的范围下进行的
4.递归进行上述操作

int _QuickSort1(int* v, int left, int right)
{int hole = left;int key = v[left];while (left < right){while (left < right && v[right] >= key)right--;v[hole] = v[right];hole = right;while (left < right && v[left] <= key)left++;v[hole] = v[left];hole = left;}v[left] = key;return left;
}
void QuickSort1(int* v, int left, int right)
{if (left >= right)return;int meet = _QuickSort1(v, left, right);QuickSort1(v, left, meet - 1);QuickSort1(v, meet + 1, right);
}

双指针法

在这里插入图片描述

思想:和hoare版本和hole版本相似,都是将当前数组分为两个子数组,递归进行排序
做法:
1.设置key等于数组的左下标,left和right分别是数组的最左和最右,设置prev为左,prev的下一个为cur
2.先让cur向右走,找比key对应的值小的数
3.让prev++,交换prev和cur对应的两个数
4.继续上述操作
5.当cur超出right范围时,停止循环,让key和prev对应的值交换

int _QuickSort2(int* v, int left, int right)
{int prev = left;int cur = left + 1;int key = left;while (cur <= right){if (v[cur] < v[key]){prev++;swap(&v[prev], &v[cur]);}cur++;}swap(&v[key], &v[prev]);return prev;
}
void QuickSort(int* v, int left, int right)
{if (left >= right)return;int meet = _QuickSort2(v, left, right);QuickSort(v, left, meet - 1);QuickSort(v, meet + 1, right);
}

快速排序的优化1,增加三数取中

在这里插入图片描述
快速排序由于其思想是:根据基准值将数组划分为两个子区间,通过不断的划分子区间将其进行排序,但是当被排序数组是有序的,那么就会退化成下面图示的情况,导致复杂度为O(n2)。
在这里插入图片描述
使用三数取中的方式,将数组可以平均二分,从而提高效率

//三数取中算法
int SelectMidIndex(int* v, int left, int right)
{int mid = left + (right - left) >> 1;if (v[left] > v[mid]){if (v[right] < v[mid])return mid;else if (v[right] > v[left])return left;else return right;}else //v[left] < v[mid]{if (v[right] > v[mid])return mid;else if (v[right] < v[left])return left;else return right;}
}
//hoare版本
int _QuickSort(int* v, int left, int right)
{int mid = SelectMid(v, left, right);swap(v[mid], v[left]);int k = left;while (left < right){//从右往左遍历,找比k小的数while (left < right && v[right] >= v[k]){right--;}//从左往右遍历,找比k大的数while (left < right && v[left] <= v[k]){left++;}//找到之后,进行交换swap(&v[left], &v[right]);}//如果相遇,将相遇点和数组左边的值交换swap(&v[left], &v[k]);return left;
}//hole版本
int _QuickSort1(int* v, int left, int right)
{int mid = SelectMid(v, left, right);swap(v[mid], v[left]);int hole = left;int key = v[left];while (left < right){while (left < right && v[right] >= key)right--;v[hole] = v[right];hole = right;while (left < right && v[left] <= key)left++;v[hole] = v[left];hole = left;}v[left] = key;return left;
}//双指针法
int _QuickSort2(int* v, int left, int right)
{int mid = SelectMidIndex(v, left, right);swap(&v[mid], &v[left]);int prev = left;int cur = left + 1;int key = left;while (cur <= right){if (v[cur] < v[key] && ++prev != cur)swap(&v[prev], &v[cur]);cur++;}swap(&v[key], &v[prev]);return prev;
}

快速排序的优化2,将递归算法改为非递归算法

我们可以将递归的快速排序算法改为非递归的方法,采用栈的数据结构作为辅助
思想:把之前递归版本的方法,通过循环放入栈来实现

做法

  1. 将当前数组的左右下标都放入栈中
  2. 判断栈是否为为空,若不为空,则出栈,进行快速排序获取基准值的位置
  3. 通过基准值将当前数组再次划分为左右两个子数组
  4. 判断划分完之后的数组是否需要再次划分(判断条件是数组大小是否为1)
int _QuickSort1()
{//此处可以使用hoare,hole,双指针法
}
void QuickSort(int* v, int left, int right)
{stack<int> s;//先push右边界,再push左边界,这样后序取栈顶元素时的顺序就是左-->右s.push(right);s.push(left);while(!s.empty()){int begin = s.top();s.pop();int end = s.top();s.pop();//此时需要使用快速排序的函数,找中间值int meet = _QuickSort1(a, begin, end);//此时本质上是通过meet将数组分为了[begin,meet-1]和[meet+1,end]两部分,需要判断是否继续放入栈中if(meet-1 > begin){s.push(meet-1);s.push(begin);}if(end > meet + 1){s.push(end);s.push(meet+1);}}
}

快速排序的性能总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

归并排序

在这里插入图片描述

思路:采用分治法的思想,将已有的子序列合并,得到完全有序的序列,让每个子序列有序,再让两个有序列表合并为一个有序列表,称为二路归并
做法

  1. 将一个数组递归分解成左右大小皆为1的数组
  2. 再对其进行排序(合并两个有序数组)
  3. 将合并好的数组回写到原数组中
void _MargeSort(int* v, int left, int right, int* tmp)
{if (left >= right)return;int mid = left + (right - left)/2;_MargeSort(v, left, mid, tmp);_MargeSort(v, mid + 1, right, tmp);int begin1 = left;int end1 = mid;int begin2 = mid + 1;int end2 = right;int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (v[begin1] < v[begin2])tmp[index++] = v[begin1++];else tmp[index++] = v[begin2++];}while(begin1 <= end1)tmp[index++] = v[begin1++];while (begin2 <= end2)tmp[index++] = v[begin2++];for (int i = left; i <= right; ++i)v[i] = tmp[i];
}
void MargeSort(int* v, int n)
{int left = 0;int right = n-1;int* tmp = (int*)malloc(n * sizeof(int*));_MargeSort(v, left, right, tmp);free(tmp);
}

归并排序特性总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

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

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

相关文章

【Python基础教程】super()函数的正确使用方法

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 1.super(本身类名,self).方法名(参数)这样就可以调用父类的方法和参数了,super()内也可不加参数 2.规律是super是按调用的次序执行&#xff0c;super后面的语句是逆向执行的。 有2段示例代码&#xff0c;不同的在于value有没…

NB-IOT 和蜂窝通信(2/3/4/5G)的区别和特点是什么?

NB-IOT 和蜂窝通信(2/3/4/5G)的区别和特点是什么? 参考链接:https://www.sohu.com/a/221664826_472880 NB IOT是窄带物联网技术,主要解决的是低速率数据传输,可使用GSM900或DCS1800频段,在频段使用上比较灵活,可以和GSM,UMTS或LTE共存,具备优异的MCL(最小耦合损耗…

Vue3 条件渲染简单应用

去官网学习-》条件渲染 | Vue.js 运行示例&#xff1a; 代码&#xff1a;HelloWorld.vue <template><div class"hello"><h1>Vue 条件渲染</h1><h2 v-if"flag">true显示内容</h2><h2 v-if"flag2">fal…

我的创作5周年纪念日

机缘 CSDN在 SEO 方面做得很好。所以容易接触到。 然后就尝试使用了。没想到已经5年了。 收获 写blog其实是对知识的总结&#xff0c;能让自己更好的分享交流&#xff0c;让自己能和其他技术者一起交流迭代&#xff0c;并且把技术内容不断做好&#xff0c;让更多人通过技术…

【周末闲谈】“深度学习”,人工智能也要学习?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 系列目录前言机器学习深度学习深度学习的三在种方法深度学习讲解…

据说这是最详细的,HttpRunner接口自动化框架讲解,直接上高速...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 框架简介 HttpRu…

Golang之路---02 基础语法——函数

函数 由于Golang语言是编译型语言&#xff0c;所以函数编写的顺序是无关紧要的&#xff0c;它不像 Python 那样&#xff0c;函数在位置上需要定义在调用之前。 函数定义 func function_name( [parameter list] ) [return_types] {函数体 }参数解释&#xff1a; func&#x…

Java实现数字加密

Java实现数字加密 需求分析代码实现小结Time 需求分析 1.首先&#xff0c;考虑方法是否需要接收数据处理&#xff1f; 需要一个4位数&#xff0c;至于是哪一个数&#xff0c;让方法的调用者传递。 所以&#xff0c;方法的参数&#xff0c;就是这个需要加密的四位数 2.接着&…

webpack基础知识三:说说webpack中常见的Loader?解决了什么问题?

一、是什么 loader 用于对模块的"源代码"进行转换&#xff0c;在 import 或"加载"模块时预处理文件 webpack做的事情&#xff0c;仅仅是分析出各种模块的依赖关系&#xff0c;然后形成资源列表&#xff0c;最终打包生成到指定的文件中。如下图所示&#…

转运相关的征兆,大家可以来看看

转运是一种喜讯&#xff0c;意味着运势将逐渐好转&#xff0c;人生会迎来一系列积极的变化。 虽然没有确切的科学根据可以证明转运的存在&#xff0c; 但是在许多传统文化和民俗中&#xff0c;人们都相信转运的征兆是实实在在的。 虽然无法确保这些征兆会在每种情况下都适用&am…

【100天精通python】Day27:文件与IO操作_CSV文件处理

目录 专栏导读 1. CSV文件格式简介 2 csv模块的使用方法 3 读写CSV文件的示例 3.1 读取CSV文件示例 3.2 写入CSV文件示例 4 CSV文件的常用数据处理 4.1 读取CSV文件的特定列 4.2 读取CSV文件的特定行 5 csv 文件的特殊处理 5.1 处理包含逗号、换行符、引号的字段 5.…

Verilog学习记录-自用

always语句块一定条件写完整&#xff0c;否则电平触发&#xff0c;综合生成锁存器 task不可综合&#xff0c;主要用于仿真/验证 大部分都是并行执行的&#xff0c;只有begin end块中阻塞语句是串行 if-else和case的区别 if-else面积小&#xff0c;但时延&#xff08;执…

软件为什么要进行性能压力测试?

软件为什么要进行性能压力测试&#xff1f;随着软件应用的不断增多和复杂度的提高&#xff0c;软件的性能对用户体验和业务成功至关重要。性能问题可能导致软件运行缓慢、崩溃或无响应&#xff0c;给用户带来不便甚至损失。为了确保软件能够在高负载和压力下正常运行&#xff0…

【Yolov5+Deepsort】训练自己的数据集(1)| 目标检测追踪 | 轨迹绘制

&#x1f4e2;前言&#xff1a;本篇是关于如何使用YoloV5Deepsort训练自己的数据集&#xff0c;从而实现目标检测与目标追踪&#xff0c;并绘制出物体的运动轨迹。本章讲解的为第一个内容&#xff1a;简单介绍YoloV5Deepsort中所用到的目标检测&#xff0c;追踪及sort&Depp…

el-table 去掉边框(修改颜色)

原始&#xff1a; 去掉表格的border属性&#xff0c;每一行下面还会有一条线&#xff0c;并且不能再拖拽表头 为了满足在隐藏表格边框的情况下还能拖动表头&#xff0c;修改相关css即可&#xff0c;如下代码 <style lang"less"> .table {//避免单元格之间出现白…

UI自动化测试之Jenkins配置

背景&#xff1a; 团队下半年的目标之一是实现自动化测试&#xff0c;这里要吐槽一下&#xff0c;之前开发的测试平台了&#xff0c;最初的目的是用来做接口自动化测试和性能测试&#xff0c;但由于各种原因&#xff0c;接口自动化测试那部分功能整个废弃掉了&#xff0c;其中…

Spring5.2.x 源码使用Gradle成功构建

一 前置准备 1 Spring5.2.x下载 1.1 Spring5.2.x Git下载地址 https://gitcode.net/mirrors/spring-projects/spring-framework.git 1.2 Spring5.2.x zip源码包下载&#xff0c;解压后倒入idea https://gitcode.net/mirrors/spring-projects/spring-framework/-/…

【NLP概念源和流】 05-引进LSTM网络(第 5/20 部分)

一、说明 在上一篇博客中,我们讨论了原版RNN架构,也讨论了它的局限性。梯度消失是一个非常重要的缺点,它限制了RNN对较短序列的建模。香草 RNN 在相关输入事件和目标信号之间存在超过 5-10 个离散时间步长的时间滞时无法学习。这基本上限制了香草RNN在许多实际问题上的应用,…

数学知识(三)

一、容斥原理 #include<iostream> #include<algorithm>using namespace std;const int N 20;typedef long long LL; int n,m; int p[N];int main() {cin>>n>>m;for(int i 0;i < m;i ) cin>>p[i];int res 0;//从1枚举到2^m(位运算)for(int …

【C++】开源:事件驱动网络库libevent配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍事件驱动库libevent配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xf…