七大排序(含快排+归并的递归版和非递归版)


文章目录

  • 前言
  • 一、冒泡排序
  • 二、选择排序
  • 三、插入排序
  • 四、希尔排序
  • 五、堆排序
  • 六、快速排序
    • 快排的递归方式
    • 快排的非递归方式
  • 七、归并排序
    • 自上而下的递归
    • 自下而上的迭代
  • 总结


前言

排序: 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序: 数据元素全部放在内存中的排序。
外部排序: 数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

常见的内部排序算法有:

在这里插入图片描述

一、冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

作为最简单的排序算法之一,冒泡排序给我的感觉就像 Abandon 在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉。冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1.算法步骤

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 //这是内层循环所控制的
3.针对所有的元素重复以上的步骤,除了最后一个。 //这是外层循环所控制的
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

2.动画演示

在这里插入图片描述

3.效率分析

当数据全是正序时,遍历一次即可,通过flag直接跳出外层循环,此时速度最快。
当数据全是反序时,每次都需要遍历和交换,此时速度最慢。

4.代码设计

//冒泡排序
template<class T>
void bubble_sort(std::vector<T>& arr, int size)
{for (int i = 0; i < size; i++){int flag = 0;for (int j = 0; j < size - 1 - i; j++){if (arr[j + 1] < arr[j]) // <是为了保证稳定性{swap(arr[j + 1], arr[j]);flag = 1;}}if (flag == 0)break;}
}

二、选择排序

选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。

1.算法步骤

1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
3.重复第二步,直到所有元素均排序完毕

2.动画演示

在这里插入图片描述

3.代码设计

//选择排序
template<class T>
void select_sort(std::vector<T>& arr, int size)
{for (int i = 0; i < size ; i++){int min = i;for (int j = i + 1; j < size ; j++){if (arr[min] > arr[j]) // >是为了保证稳定性min = j;}if (min != i) swap(arr[min], arr[i]);}
}

三、插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

1.算法步骤

1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面,保证稳定性。)

2.动画演示

在这里插入图片描述
3.代码设计

//插入排序
template<class T>
void insert_sort(std::vector<T>& arr, int size)
{for (int i = 1; i < size; i++){int key = arr[i];int end = i - 1;while (end >= 0 && key < arr[end]) // <是为了保证稳定性{arr[end +1] = arr[end];end--;}arr[end + 1] = key;}
}

四、希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序

1.算法步骤

1.选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
2.按增量序列个数 k,对序列进行 k 趟排序;
3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

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

3.算法分析

希尔排序时间复杂度是 O(n(1.3-2)),空间复杂度为常数阶 O(1)。希尔排序没有时间复杂度为 O(n(logn)) 的快速排序算法快 ,因此对在这里插入代码片中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O(n2 ) 复杂度的算法快得多。

步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。

4.代码设计

//希尔排序
template<class T>
void shell_sort(std::vector<T>& arr, int size)
{int gap = size;while (gap > 1){gap /= 2;for (int i = 0; i < size - gap; i++){int end = i;int key = arr[end + gap];while (end >= 0 && key < arr[end]){arr[end + gap] = arr[end];end -= gap;}arr[end + gap] = key;}}
}

五、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
    在这里插入图片描述

堆排序的平均时间复杂度为 Ο(nlogn)。

1.算法步骤

1.将待排序序列构建成一个堆 H[0……n-1],根据(升序降序需求)选择大顶堆或小顶堆;
2.把堆首(最大值)和堆尾互换;
3.把堆的尺寸缩小 1,并调用 AdjustDown(0),目的是把新的数组顶端数据调整到相应位置;
4.重复步骤 2,直到堆的尺寸为 1。

2.动画演示

在这里插入图片描述

3.代码设计

//堆排序
template<class T>
void AdjustDown(std::vector<T>& arr, int size, int parent)
{int child = parent * 2 + 1;while (child < size){int max = child;if (child + 1 < size && arr[child + 1] > arr[child])max = child + 1;if (arr[parent] < arr[max]){swap(arr[max], arr[parent]);parent = max;child = parent * 2 + 1;}elsebreak;}
}template<class T>
void heap_sort(std::vector<T>& arr, int size)
{for (int i = (size - 1 - 1) / 2; i >= 0; i--)AdjustDown(arr, size, i);for (int i = 1; i < size; i++){swap(arr[0], arr[size - i]);AdjustDown(arr, size - i, 0);}
}

六、快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n ogn) 的排序算法表现要更好:

快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

快排的递归方式

1.算法步骤

1.从数列中挑出一个元素,称为 “基准”(pivot);
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

2.动画演示

在这里插入图片描述

3.代码设计

//快速排序的优化——三数取中(key)
template<class T>
int getKey(std::vector<T>& arr, int begin, int end)
{int mid = (begin + end) / 2;if (arr[begin] < arr[mid]){if (arr[mid] < arr[end])return mid;else if (arr[begin] > arr[end])return begin;elsereturn end;}else{if (arr[mid] > arr[end])return mid;else if (arr[end] > begin)return begin;elsereturn end;}
}//快速排序
template<class T>
int partition1(std::vector<T>& arr, int left, int right)
{int mid = getKey(arr, left, right);swap(arr[left], arr[mid]);//left会移动,利用key保存最左边的下标;int key = left;while (left < right){while (left < right && arr[right] >= arr[key])//往左找小--right;while (left < right && arr[left] <= arr[key]) //往右找大++left;if (left < right)swap(arr[left], arr[right]);}//从右边开始找可以确定相遇位置的值要小于keyswap(arr[left], arr[key]);return left;
}template<class T>
void quick_sort(std::vector<T>& arr, int begin, int end)
{if (begin >= end) return;int mid = partition1(arr, begin, end);quick_sort(arr, begin, mid - 1);quick_sort(arr, mid + 1, end);}

快排的非递归方式

由于递归方式会大大增加函数栈帧的层数,导致栈溢出,所以利用栈去模拟递归的思想实现:

template<class T>
void quick_sortNoR(std::vector<T>& arr, int left, int right)
{std::stack<T> s;s.push(left);s.push(right);while (!s.empty()){right = s.top();s.pop();left = s.top();s.pop();int div = partition1(arr, left, right);if (div + 1 < right){s.push(div + 1);s.push(right);}if (left < div - 1){s.push(left);s.push(div - 1);}}
}

七、归并排序

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归;
  • 自下而上的迭代;

1.算法步骤

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;
5.将另一序列剩下的所有元素直接复制到合并序列尾。

2.动画演示
在这里插入图片描述
3.代码设计

自上而下的递归

//归并排序 对于[)区间
template<class T>
void merge(std::vector<T>& arr, int l, int mid, int r) {int index = 0;int ptrL = l;int ptrR = mid;static std::vector<T>tempary;if (arr.size() > tempary.size()) {tempary.resize(arr.size());}while (ptrL < mid && ptrR < r) {if (arr[ptrL] < arr[ptrR]) tempary[index++] = arr[ptrL++];elsetempary[index++] = arr[ptrR++];}while (ptrL < mid) {tempary[index++] = arr[ptrL++];}while (ptrR < r) {tempary[index++] = arr[ptrR++];}std::copy(tempary.begin(), tempary.begin() + index , arr.begin() + l);
}template<class T>
void merge_sort(std::vector<T>& arr, int l, int r) { // sort the range [l, r) in arrif (r - l <= 1) return;int mid = (l + r) / 2;merge_sort(arr, l, mid);merge_sort(arr, mid, r);merge(arr, l, mid, r);}

自下而上的迭代

template<class T>
void merge_sortNoR(std::vector<T>& arr, int sz)
{static std::vector<T> tempary;if (tempary.size() < arr.size())tempary.resize(arr.size());int gap = 1;while (gap < arr.size()){for (int i = 0; i < sz; i += 2 * gap){int b1 = i, e1 = i + gap - 1;int b2 = i + gap, e2 = i + 2 * gap - 1;if (e1 >= sz) break;//第一组越界if (b2 >= sz) break;//第二组全部越界if (e2 >= sz) e2 = sz - 1;//第二组部分越界,继续归并int index = i;while (b1 <= e1 && b2 <= e2){if (arr[b1] <= arr[b2])tempary[index++] = arr[b1++];elsetempary[index++] = arr[b2++];}while (b1 <= e1){tempary[index++] = arr[b1++];}while (b2 <= e2){tempary[index++] = arr[b2++];}std::copy(tempary.begin(), tempary.begin() + index, arr.begin());}gap *= 2;}}

总结

1.选择排序不稳定的原因:

比如 在序列5,3,5,2中,2 是最小值,会和第 1 个 5 进行交换,那第 1 个 5 就去了第 2 个 5 的后面,两个 5 的相对位置发生改变。

2.希尔排序不稳定的原因:

相同元素在不同子序列中可能被分到不同的位置,这导致了相同元素的相对位置可能被改变。

3.堆排序不稳定的原因:

在建立堆的调整步骤里,由于关键字相同的两个记录位置并不会被调换,所以建堆的时候是稳定的。但是,在堆顶与堆尾交换的时候两个相等的记录在序列中的相对位置就可能发生改变。

4.快速排序不稳定的原因:

比如序列为5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。

在这里插入图片描述

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

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

相关文章

代码随想录算法训练营第20天 | 654.最大二叉树 + 617.合并二叉树 + 700.二叉搜索树中的搜索 + 98.验证二叉搜索树

今日任务 654.最大二叉树 - Medium617.合并二叉树 - Easy700.二叉搜索树中的搜索 - Easy98.验证二叉搜索树 - Medium 654.最大二叉树 - Medium 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法…

数学建模-时间序列预测步骤

目录 数据 第一步&#xff1a;定义时间 第二步&#xff1a;创建传统模型 结果 论文下笔 GG 数据 第一步&#xff1a;定义时间 第二步&#xff1a;创建传统模型 点击条件&#xff0c;点击 离群值全部勾选 点击统计 点击图 保存 选项 结果 论文下笔 由于我们的数据中不存在…

Android Text View 去掉默认的padding的实现方法

先看下最终实现效果&#xff0c;满意您在往下看&#xff1a; TextView 绘制的时候自带一定的Padding值&#xff0c;要想实现去掉默认的padding值&#xff0c;xml文件可以设置一个属性值 &#xff1a; android:includeFontPadding"false" 然后运行起来就会发现&…

书生·浦语大模型--第三节课笔记--基于 InternLM 和 LangChain 搭建你的知识库

文章目录 大模型开发范式RAGLangChain框架&#xff1a;构建向量数据库构建检索问答链优化建议web 部署 实践部分 大模型开发范式 LLM的局限性&#xff1a;时效性&#xff08;最新知识&#xff09;、专业能力有限&#xff08;垂直领域&#xff09;、定制化成本高&#xff08;个…

Android 仿快手视频列表,RecyclerView与Banner联动效果

这是看到群里讨论过快手APP的一个观看他人视频列表的一个联动效果&#xff0c;但是并不是完全按照这个软件的效果来做的&#xff0c;只是参考&#xff0c;并不是完全仿照这个软件来做的&#xff0c;没时间去优化排版问题了&#xff0c;请见谅&#xff0c;如图&#xff1a; 实现…

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输出…

Swift 周报 第四十五期

文章目录 前言新闻和社区苹果或将扩充健康版图&#xff0c;为Apple Watch X铺路更新后的《Apple Developer Program 许可协议》现已发布 提案通过的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第四十五期&#xff0c;每个模块已初步成型。各位…

【Linux 内核源码分析】RCU机制

RCU 基本概念 Linux内核的RCU&#xff08;Read-Copy-Update&#xff09;机制是一种用于实现高效读取和并发更新数据结构的同步机制。它在保证读操作不被阻塞的同时&#xff0c;也能够保证数据的一致性。 RCU的核心思想是通过延迟资源释放来实现无锁读取&#xff0c;并且避免了…

IOS自动化测试元素定位

一、元素属性介绍 1、元素属性 2、查看各定位方式执行效率 二、iOS常用定位方法 1、accessibility_id 2、class_name 3、Xpath 4、ios_class_chain(类型链) 5、ios_predicate(谓词) 一个页面最基本组成单元是元素&#xff0c;想要定位一个元素&#xff0c;我们需…

Linux网络服务部署yum仓库

目录 一、网络文件 1.1.存储类型 1.2.FTP 文件传输协议 1.3.传输模式 二、内网搭建yum仓库 一、网络文件 1.1.存储类型 直连式存储&#xff1a;Direct-Attached Storage&#xff0c;简称DAS 存储区域网络&#xff1a;Storage Area Network&#xff0c;简称SAN&#xff0…

01-15

#include "ui_widget.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget) {ui->setupUi(this);//判断是否有该数据库if(!db.contains("stuInfo.db")){//说明数据库不存在 则创建dbQSqlDatabase::addDatabase("QSQLITE")…

ELK之Filebeat输出日志格式设置及输出字段过滤和修改

一、Filebeat输出日志格式设置 1.1 编辑vim filebeat.yml文件,修改输出格式设置 # output to console output.console:codec.format: string: %{[@timestamp]} %{[message]}pretty: true### 1.2 测试 执行 ./filebeat -e 可以看到/tmp/access.log(目前文件里只有140.77.188…

【LV12 DAY9 ADC实验】

电压在1501mv~1800mv时&#xff0c;LED2、LED3、LED4、LED5点亮 电压在1001mv~1500mv时&#xff0c;LED2、LED3、LED4点亮 电压在501mv~1000mv时&#xff0c;LED2、LED3点亮 电压在0mv~500mv时&#xff0c;LED2闪烁 #include "exynos_4412.h"void delay(unsigned in…

大语言模型系列-总述

大语言模型发展史 研究人员发现&#xff0c;扩展预训练模型&#xff08;Pre-training Language Model&#xff0c;PLM&#xff09;&#xff0c;例如扩展模型大小或数据大小&#xff0c;通常会提高下游任务的模型性能&#xff0c;模型大小从几十亿&#xff08;1 B 10亿&#x…

Mysql判断一个表中的数据是否在另一个表存在

方式一&#xff1a; 判断A表中有多少条数据在B表中【存在】,并且显示这些数据–EXISTS语句 select A.ID, A.NAME from 表A where EXISTS(select * from 表B where A.IDB.ID) 判断A表中有多少条数据在B表中【不存在】&#xff0c;并且显示这些数据–NOT EXISTS语句 select …

使用Go语言通过API获取代理IP并使用获取到的代理IP

目录 前言 【步骤一&#xff1a;获取代理IP列表】 【步骤二&#xff1a;使用代理IP发送请求】 【完整代码】 【总结】 前言 在网络爬虫、数据抓取等场景中&#xff0c;经常需要使用代理IP来隐藏真实的IP地址&#xff0c;以及增加请求的稳定性和安全性。本文将介绍如何使用…

ubuntu22: nvtop no gpu to monitor.

解决方法&#xff1a; 重新下载nvtop sudo apt update sudo apt -y install nvtop真是逆天 &#xff0c;ubuntu系统的nvidia driver突然坏了&#xff0c;然后我重装了nvidia driver, 之后用nvtop就出现这个问题了&#xff0c;但是逆天的是我竟然没有搜到一篇中文的帖子讲这个问…

NLP论文阅读记录 - 2021 | WOS 使用 GA-HC 和 PSO-HC 改进新闻文章的文本摘要

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试 二.相关工作三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Improved Text Summa…

云卷云舒:2023年,我眼中的十大数据库

我眼中的十大数据库&#xff0c;只要看成长性和演进速度&#xff08;个见勿怪&#xff09;。 一、五强 1、openGauss&#xff1a;生态影响力变大&#xff0c;基于高斯的产品层出不穷 2、OceanBase&#xff1a;只因霸榜&#xff0c;技术强大&#xff0c;新特性更新频繁&#x…

lv14 并发控制:上下文、中断屏蔽和原子变量

1 上下文和并发场合 执行流&#xff1a;有开始有结束总体顺序执行的一段代码 又称上下文 应用编程&#xff1a;任务上下文 内核编程&#xff1a; 任务上下文&#xff1a;五状态 可阻塞 a. 应用进程或线程运行在用户空间b. 应用进程或线程运行在内核空间&#xff08;通过调用…