【数据结构与算法】万字剖析八大排序(直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序)

news/2024/5/1 8:34:08/文章来源:https://blog.csdn.net/qq_43289447/article/details/130078910

在这里插入图片描述
在这里插入图片描述

目录

  • 一.直接插入排序
  • 二.希尔排序
  • 三.选择排序
  • 四.堆排序
  • 五.冒泡排序
  • 六.快速排序
    • 1.hoare版
    • 2.挖坑法
    • 3.前后指针
    • 4.选取基准值的优化
      • (1)快速排序非递归
  • 七.归并排序
      • (2)归并排序非递归
  • 八.计数排序
  • 九.八大排序稳定性分析

一.直接插入排序

初窥直接插入排序我们先来看一张动图:
在这里插入图片描述
由动图我们可以分析出直接插入排序是从第二个数据开始遍历,与前面的数据进行比较如果小于 则让前面的数据向前移动 自己接着向前面的数据比较 直到比较到大于等于自己的数据或者没有数据能进行比较时停止 插入当前的位置。

直接插入排序性能分析:
时间复杂度:O(N^2),最好情况当数据本来就有序时可以做到O(N)
空间复杂度:直接插入排序并没再开过一段连续的空间所以为O(1)

完整代码如下,我们看到while循环中的判断即是让tmp小于的数据 不断向前覆盖 直到找到小于tmp的数据 或者 end小于0(前面没有可比较的数据)时 跳出循环将数据插入。

void InsertSort(int* a, int size)
{assert(a);for (int i = 0; i < size - 1; i++){int tmp = a[i + 1];int end = i;while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

二.希尔排序

希尔排序又称缩小增量排序,它选定了一个整数gap将待排数据分组,然后对组内的数据进行直接插入排序,接着减小gap 改变分组的区间 再进行排序。此过程称为预排序最后gap减小到1时,数据已经接近有序,再对数据进行直接插入的排序效率则较高。动图演示如下:
在这里插入图片描述

希尔排序性能分析:
时间复杂度:最坏O(N^ 2) ,平均在<数据结构>严蔚敏中给出O(N^1.3);
空间复杂度:O(1);

完整代码如下,如有疑问随时私信或者在评论区提出。

void ShellSort(int* a, int size)
{assert(a);int gap = size;while (gap > 1){gap /= 2;//使得gap最后能减到1 成为直接插入排序//gap /= 3 + 1;for (int i = 0; i < size - gap; i += gap){int end = i;int tmp = a[i + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}}
}

三.选择排序

选择排序在八大排序中属于是最“老实的”一个排序,其基本思想 首先选出一个关键值 一般是数据的第一位 接着遍历剩下的数据 记录剩下数据中最小值和最大值的下标。接着将最小值放到左边 最大值放到右边 接着减小需要排序的区间(因为已经排一个最大值和一个最小值了
在这里插入图片描述
当前代码逻辑实现如下:

void SelectSort(int* a, int n)
{int left = 0;int right = n - 1;while (left < right){int mini = left, maxi = left;for (int i = left + 1; i <= right; i++){if (a[mini] > a[i]){mini = i;}if (a[maxi] < a[i]){maxi = i;}}Swap(&a[left], &a[mini]);Swap(&a[right], &a[maxi]);--right;++left;}
}

将我测试代码时发现,有些情况,上面的代码无法处理。
在这里插入图片描述
这是为什么了,我们调试观察到:
在这里插入图片描述
此时关键值为a[3] 6 ,在下标区间4到6遍历后max仍然是6min是3。接着min与a[left]交换即:
在这里插入图片描述
后续最大值与a[right]交换时,原本应该放着最大值的a[left]已经被换成了最小值,所以交换会发生错误。也就是left与maxi重叠时,需要将maxi赋为mini才不会发生错误。

选择排序性能:
时间复杂度:O(N^2);
空间复杂度:O(1);

正确代码如下:

void SelectSort(int* a, int n)
{int left = 0;int right = n - 1;while (left < right){int mini = left, maxi = left;for (int i = left + 1; i <= right; i++){if (a[mini] > a[i]){mini = i;}if (a[maxi] < a[i]){maxi = i;}}Swap(&a[left], &a[mini]);if (left == maxi)//防止left与maxi重叠{maxi = mini;}Swap(&a[right], &a[maxi]);--right;++left;}
}

四.堆排序

堆排序在之前我就写过文章讲解,这里就不赘述了。需要注意的是:排升序、建大堆,排降序、建小堆文章堆和堆排序的链接

五.冒泡排序

在这里插入图片描述
冒泡排序在学习C语言时就已经学过,所以实现它轻而易举,代码如下:

选择排序性能:
时间复杂度:O(N^2);
空间复杂度:O(1);

void BubbleSort(int* a, int size)
{for (int i = 0; i < size - 1; i++){for (int j = 1; j < size  - i; j++){if (a[j - 1] > a[j]){Swap(&a[j - 1], &a[j]);}}}
}

六.快速排序

1.hoare版

快速排序顾名思义它的效率较高,是由Hoare于1962年提出的一种二叉树结构的交换排序方法。它的思想是选定一个基准值,将小于基准值的数据放到左边大于的放到右边。接着在分别递归左右区间。
在这里插入图片描述

快速排序性能:
时间复杂度:O(N*logN)最坏O(N^2)
空间复杂度:O(1);

此版本需要注意的是如果将左边第一个作为基准值就需要让右边先走,需要小于基准值的数据,相反就让左边先走

void QuickSort11(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = left;while (left < right){while (left < right && a[keyi] <= a[right])--right;while (left < right && a[keyi] >= a[left])++left;Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);keyi = left;QuickSort11(a, begin, keyi - 1);QuickSort11(a, keyi + 1, end);
}

2.挖坑法

挖坑法的优化不明显,排序思想也与原先的差不多。
在这里插入图片描述
代码如下,供大家理解:

void QuickSort22(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int hole = left;int key = a[left];while (left < right){while (left < right && key <= a[right])--right;a[hole] = a[right];hole = right;while (left < right && key >= a[left])++left;a[hole] = a[left];hole = left;}a[hole] = key;QuickSort22(a, begin, hole - 1);QuickSort22(a, hole + 1, end);
}

3.前后指针

前后指针是快速排序的第三个版本,它的做法就非常的妙:
在这里插入图片描述
完整代码如下:

void QuickSort33(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] <= a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);QuickSort33(a, begin, prev - 1);QuickSort33(a, prev + 1, end);
}

4.选取基准值的优化

我们知道快速排序的效率是很高的,特别是处理无序的数据时,但是当数组为有序时,时间复杂度就会下降到O(N^2),这是因为我们总是将第一个数据作为基准值导致的。这里就有个优化的方法———三数取中———它将比较mid left right的数据,取中值作为基准值,使得即使数据有序,效率也不会太差。

int GetMidNumi(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] > a[mid]){if (a[mid] > a[right])return mid;else if (a[left] < a[right])return left;elsereturn right;}else{if (a[mid] < a[right])return mid;else if (a[right] < a[left])return left;elsereturn right;}
}

(1)快速排序非递归

上文快速排序的三个版本都使用了递归,当数据量过大,建立的栈帧空间过多时,容易产生栈溢出,导致错误。所以我们要学习 将代码从递归改为非递归避免错误 是以后我们工作的必备技能。
快速排序的非递归需要使用到栈 使用栈辅助改为循环。
如何利用栈将快排改为循环呢?我们将数组左右下标压入栈,为了出栈顺序为先左后右,我们则要先压右下标,再压左下标。然后当栈非空时就 继续弹出区间给快排函数排序,当左右区间数据少于1时停止压栈。

void QuickSortNon(int* a, int left, int right)
{ST QS;InitST(&QS);Push(&QS, right);Push(&QS, left);while (!STEmpty(&QS)){int begin = STTop(&QS);Pop(&QS);int end = STTop(&QS);Pop(&QS);int key = QuickSort3(a, begin, end);if (begin < key - 1){Push(&QS, key - 1);Push(&QS, begin);}if (key + 1 < end){Push(&QS, end);Push(&QS, key + 1);}}DestroyST(&QS);
}

七.归并排序

归并排序采用了分治的算法思想,将数据分成两个区间不断递归到两个区间有序后,将两个区间内的数据排到新malloc的辅助空间中
演示动图如下:
在这里插入图片描述

归并排序性能:
时间复杂度:O(N*logN)
空间复杂度:O(N);归并排序需要的辅助空间较多,所以常用于解决磁盘的外排序问题

完整代码如下:

void _MergeSort(int* a, int left, int right, int* tmp)
{if (left >= right)return;int mid = (left + right) / 2;_MergeSort(a, left, mid, tmp);_MergeSort(a, mid+1, right, tmp);int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int i = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] > a[begin2]){tmp[i++] = a[begin2++];}else{tmp[i++] = a[begin1++];}}while(begin1 <= end1)//前面两个区间中还有多余没有拿下来的数据,一并排进去tmp[i++] = a[begin1++];while (begin2 <= end1)tmp[i++] = a[begin2++];memcpy(a + left, tmp+left, sizeof(int)*(right-left+1));
}

(2)归并排序非递归

由上面的归并排序代码我们可知,排序开始先找到mid 将数据分为两个区间,接着不断递归到区间内只有一个数据也就是有序了然后与右区间的数据进行比较排进辅助空间中。
那我们可不可以不通过递归 控制数据区间有序呢?可以直接定义gap控制一区间内的数据个数,gap初值为1,也就达到了区间有序
在这里插入图片描述
控制如下:

	int gap=1;while (gap < size){	gap *= 2;}

而且非递归版本不想递归有mid,便于控制区间,其区间的控制需要理解:

	int gap = 1;while (gap < size){for (int i = 0; i < size; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;//区间控制int begin2 = i + gap, end2 = i + 2 * gap - 1;//好好体会int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] > a[begin2]){tmp[j++] = a[begin2++];}else{tmp[j++] = a[begin1++];}}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];memcpy(a, tmp, sizeof(int) * (end2-1+1));}

到这里我们就不免有个疑问,不同于上图所示,如果数据为奇数,不能正好的分区间,那该怎么办呢?
我们则需要在区间控制,再判断一下区间是否越界以及相关的处理。

	int begin1 = i, end1 = i + gap - 1;//区间控制int begin2 = i + gap, end2 = i + 2 * gap - 1;

区间如上所示,又因为i是0到size-1的所以end1 begin2 end2都有越界的可能,我们分类讨论解决一下。
在这里插入图片描述
共有三种情况:

  1. end1就越界了,begin2和end2肯定也就越界了,这时只有左区间的部分数据有效所以无法进行比较+排入,所以这时的gap是错误了,此次循环不进行直接break
  2. begin2和end2越界时,这时只有左区间有效,无法进行比较+排入与第一种情况相同直接break
  3. 最后一种情况只有end2越界了,我们只需将end2赋为正常区间之内即可
    调整代码如下:
		int begin1 = i, end1 = i + gap - 1;//区间控制int begin2 = i + gap, end2 = i + 2 * gap - 1;if (end1 >= size || begin2 >= size){break;}else if (end2 >= size){end2 = size - 1;}

完整代码如下:

void _MergeSortNon6(int* a, int size)
{int* tmp = (int*)malloc(sizeof(int) * size);if (tmp == NULL){perror("malloc fail");}int gap = 1;while (gap < size){for (int i = 0; i < size; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;//区间控制int begin2 = i + gap, end2 = i + 2 * gap - 1;//好好体会if (end1 >= size || begin2 >= size){break;}else if (end2 >= size){end2 = size - 1;}int j = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] > a[begin2]){tmp[j++] = a[begin2++];}else{tmp[j++] = a[begin1++];}}while (begin1 <= end1)tmp[j++] = a[begin1++];while (begin2 <= end2)tmp[j++] = a[begin2++];memcpy(a, tmp, sizeof(int) * (end2-1+1));}gap *= 2;}free(tmp);
}

上面的代码是归并一部分就拷贝一部分,也可以在for循环外一次性拷贝进去,一次性拷贝对区间的控制更为麻烦,这里就不过多赘述。感兴趣的可以在我的gitee中找到代码匿名者Unit码云

八.计数排序

计数排序属于非比较排序,适合范围集中,并且范围不大的整形数组排序。

计数排序性能:
时间复杂度:O(N+range)
空间复杂度:O(range);

完整代码如下:

void CountSort(int* a, int n)
{int max = a[0], min = a[0];for (int i = 0; i < n; i++){if (a[i] > max){max = a[i];}if(a[i]<min){min = a[i];}}int range = max - min + 1;int* countA = (int*)malloc(sizeof(int) * range);assert(countA);memset(countA, 0, sizeof(int) * range);for (int i = 0; i < n; i++){countA[a[i] - min]++;}int j = 0;for (int i = 0; i < range; i++){while (countA[i]--){a[j++] = i + min;}}
}

九.八大排序稳定性分析

在排序中稳定性的含义如下:
在这里插入图片描述
冒泡排序每一趟将一个数排到最终位置,可以控制相等条件使得冒泡稳定。
选择排序不稳定的情况就难以想到了:
在这里插入图片描述
1小于基准值2,则将1与2交换,导致了两个2的相对顺序发生改变
直接插入排序同样可以控制相等交换条件,做到稳定排序。
希尔排序有可能会出现相同的值分到不同组中,导致不稳定的情况。
堆排序在堆调整时可能会更改相同值的相对次序,所以不稳定。
归并排序在插入辅助空间时,是按照原来的顺序 不会改变次序,所以是稳定排序。
快速排序与选择排序的情况相同,也是交换了基准值导致了不稳定的情况。
在这里插入图片描述

文章到此结束,有疑问的随时私信或者评论区留言!

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

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

相关文章

javaEE 初阶 — 第一个 servlet 程序

文章目录Servlet 是什么第一个 Servlet 程序1 创建项目2 引入依赖3 创建目录结构4 代码编写5 打包程序6 部署7 验证如何使用 tomcat 插件打包及部署1 什么是插件2 插件的安装3 插件的使用4 可能会出现的问题Servlet 是什么 Servlet 是一种实现 动态页面 的技术&#xff0c;是一…

elasticsearch MySQL 数据同步。

elasticsearch & MySQL 数据同步。 文章目录elasticsearch & MySQL 数据同步。3. 数据同步。3.1. 思路分析。3.1.1. 同步调用。3.1.2. 异步通知。3.1.3. 监听 binlog。3.1.4. 选择。3.2. 实现数据同步。3.2.1. 思路。3.2.2. 导入 demo。3.2.3. 声明交换机、队列。1&…

ChatGLM-6B论文代码笔记

ChatGLM-6B 文章目录ChatGLM-6B前言一、原理1.1 优势1.2 实验1.3 特点&#xff1a;1.4 相关知识点二、实验2.1 环境基础2.2 构建环境2.3 安装依赖2.4 运行2.5 数据2.6 构建前端页面3 总结前言 Github&#xff1a;https://github.com/THUDM/ChatGLM-6B 参考链接&#xff1a; ht…

GPSS【实践 01】Developing a Greenplum Streaming Server Client 自定义GPSS客户端开发实例

自定义GPSS客户端开发流程1.GPSS是什么2.架构3.组件下载安装4.自定义客户端4.1 GPSS Batch Data API Service Definition4.2 Setting up a Java Development Environment4.3 Generating the Batch Data API Client Classes4.4 Coding the GPSS Batch Data Client4.4.1 Connect …

精准关键词获取-行业搜索词分析

SEO关键词的收集通常可以通过以下几种方法&#xff1a; 根据市场价值、搜索词竞争性和企业实际产品特征进行筛选&#xff1a;确定您的关键词列表之前&#xff0c;建议先进行市场分析&#xff0c;了解您的竞争对手、行业状况和目标受众等信息&#xff0c;以更好的了解所需的特定…

为何ChatGPT如此擅长编造故事?

“幻觉”——人工智能中的一个偏见性术语 AI聊天机器人(如OpenAI的ChatGPT)依赖于一种称为“大型语言模型”(LLM)的人工智能来生成它们的响应。LLM是一种计算机程序&#xff0c;经过数百万文本源的训练&#xff0c;可以阅读并生成“自然语言”文本语言&#xff0c;就像人类自然…

HTTP协议概述 | 简析HTTP请求流程 | HTTP8种请求方法

目录 &#x1f30f; HTTP的简单介绍 何为HTTP HTTP1.0与HTTP1.1 &#x1f30f; HTTP的请求方法 1、OPTIONS 2、HEAD 3、GET 4、POST 5、PUT 6、DELETE 7、TRACE 8、CONNECT &#x1f30f; HTTP的工作原理 &#x1f30f; HTTP请求/响应的步骤 1、客户端连接到Web…

【Linux】用户命令(创建,修改,切换,删除,密码)

目录 1.创建 查看用户信息 查看id 2.修改 修改用户名 修改用户uid 操作前&#xff1a; 操作后 修改组名 操作前&#xff1a; 操作后: 修改组id 操作前&#xff1a; 操作后&#xff1a; 操作前&#xff1a; 操作后: 3.切换用户 4.删除 操作前&#xff1a; 操作…

LeetCode:376. 摆动序列——说什么贪心和动规~

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340;算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;376. 摆动序列 题目描述&#xff1a;如果连续数字之间的差严格地在正数和…

php7类型约束,严格模式

在PHP7之前&#xff0c;函数和类方法不需要声明变量类型 &#xff0c;任何数据都可以被传递和返回&#xff0c;导致几乎大部分的调用操作都要判断返回的数据类型是否合格。 为了解决这个问题&#xff0c;PHP7引入了类型声明。 目前有两类变量可以声明类型&#xff1a; 形参&a…

拼多多运营中需要采集淘宝天猫京东平台商品详情页面数据上架拼多多店铺,如何使用技术封装接口实现

业务背景&#xff1a;电商平台趋势&#xff0c;平台化。大家可以看到大的电商都开始有自己的平台&#xff0c;其实这个道理很清楚&#xff0c;就是因为这是充分利用自己的流量、自己的商品和服务大效益化的一个过程&#xff0c;因为有平台&#xff0c;可以利用全社会的资源弥补…

RPC调用框架简单介绍

一.Thrift Apache Doris目前使用的RPC调度框架。Thrift是一款基于CS&#xff08;client -server&#xff09;架构的RPC通信框架&#xff0c;开发人员可以根据定义Thrift的IDL(interface decription language)文件来定义数据结构和服务接口&#xff0c;灵活性高&#xff0c;支持…

项目5:实现数据字典的上传下载

项目5&#xff1a;实现数据字典的上传下载 1.什么是数据字典&#xff1f;如何设计&#xff1f; 2.业务流程逻辑 3.数据库表的设计 4.实现上传下载逻辑&#xff08;前端&#xff09; 5.实现上传逻辑&#xff08;后端&#xff09; 6.实现下载依赖&#xff08;后端&#xff…

代码随想录Day49

今天继续学习动规解决完全背包问题。 322.零钱兑换 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额&#xff0c;…

vuex中的 mapState, mapMutations

vuex中的 mapState&#xff0c; mapMutations Start 今天使用vuex的过程中&#xff0c;遇到 mapState&#xff0c; mapMutations 这么两个函数&#xff0c;今天学习一下这两个函数。 本文介绍的vuex基于 vuex3.0 1. 官方文档说明 1.1 mapState 官方解释 点击这里&#xff1…

【JUC进阶】详解synchronized锁升级

文章目录1. synchronized概述2. synchronized 的实现原理2.1 Java对象组成2.2 Monitor2.3 从字节码角度看synchronized3. 锁升级3.1 偏向锁3.2 轻量级锁1. synchronized概述 synchronized是一个悲观锁&#xff0c;可以实现线程同步&#xff0c;在多线程的环境下&#xff0c;需…

DIN35电压电流转频率单位脉冲输出信号变换器集电极开路隔离变送器

主要特性 将直流电压或电流信号转换成单位脉冲信号。 精度等级&#xff1a;0.1 级、0.2 级。产品出厂前已检验校正&#xff0c;用户可以直接使用。 国际标准信号输入:0-5V/0-10V/1-5V 等电压信号,0-10mA/0-20mA/4-20mA 等电流信号。 输出标准信号&#xff1a;0-5KHz/0-…

Flink CDC 在京东的探索与实践

摘要&#xff1a;本文整理自京东资深技术专家韩飞&#xff0c;在 Flink Forward Asia 2022 数据集成专场的分享。本篇内容主要分为四个部分&#xff1a; 京东自研 CDC 介绍京东场景的 Flink CDC 优化业务案例未来规划点击查看直播回放和演讲 PPT 一、京东自研 CDC 介绍 京东自研…

小白学Pytorch系列- -torch.distributions API Distributions (1)

小白学Pytorch系列- -torch.distributions API Distributions (1) 分布包包含可参数化的概率分布和抽样函数。这允许构造用于优化的随机计算图和随机梯度估计器。这个包通常遵循TensorFlow分发包的设计。 不可能通过随机样本直接反向传播。但是&#xff0c;有两种主要方法可以…

tomcat中出现RFC7230和RFC3986问题解析

问题截图 问题分析 出现上述问题&#xff0c;是因为各版本tomcat中对特殊字符和请求路径中携带中文参数而产生的错误提示。 解决办法 1、调整tomcat版本 tomcat 7.0.76之前的版本不会出现类似问题 2、tomcat9之前&#xff0c;修改tomcat目录底下的/conf/catalina.properti…