【数据结构】八大排序(一)

目录

前言:

直接插入排序

直接插入排序代码实现

直接插入排序特性总结

希尔排序

希尔排序代码实现

希尔排序特性总结

直接选择排序

直接选择排序代码实现

直接选择排序特性总结

堆排序

堆的向下调整算法

建堆

堆排序代码实现

堆排序特性总结


前言:

排序即使得一串记录,按照其中某个或某些关键字的大小,递增或递减的排列起来的操作;

排序分为内部排序外部排序稳定排序非稳定排序

内部排序数据元素全部放在内存中的排序;

外部排序:数据元素太多不能同时存储于内存中,根据排序过程的要求不能在内外存之间移动数据的排序;

稳定性:假定在待排序的序列中,如果元素a位于元素b前面,且a=b,经过某种排序方法进行排序之后,元素a依然位于元素b前面,那么这种排序方法就是稳定的,否则就是不稳定的;

衡量一个排序方法的好坏可以根据时间复杂度与空间复杂度,稳定性等综合考虑

常见的排序算法:

本篇主要详细介绍前四种排序,均以升序为例 ;

直接插入排序

直接插入排序的基本思想:

对于一个长度为n的数组a,当插入第 i (i>=1)个元素时,前面的a[0],a[1],......,a[i-1]已经有序,此时只需将a[i]元素与a[i-1],a[i-2],.....的元素进行比较,找到插入位置先将原来位置上的元素依次向后移动,然后在插入位置插入元素a[i];

具体步骤如下

  1. 将待排序的序列分为有序区和无序区,初始时有序区只有一个元素,即序列的第一个元素,无序区包括除第一个元素外的所有元素;
  2. 从无序区中取出第一个元素,将其插入到有序区中的适当位置,使之成为新的有序区;
  3. 重复步骤2,直到无序区中的所有元素都插入到有序区中;
  • 单个数据必然有序,所以将元素3划分为有序区,其他元素均位于无序区,假设有序区最后一个元素下标为end,为防止移动过程中插入元素被覆盖,用tmp保存插入元素的数值;

  • 寻找插入位置,将tmp与数组有序区尾元素a[end]进行比较,若a[end]>tmp,先将有序区尾元素向后移动一位,然后通过控制end即end每次自减1,向左查找下一位,按此循环,直至a[end]<tmp出现或者end = -1;

  • 此时单趟排序已完成,end继续回到有序区的尾元素的位置,进行下一趟排序,若a[end]<tmp,直接插入到end的下一位置即a[end+1]=tmp;

 

 

.......

直接插入排序代码实现

void InsertSort(int* a, int n)
{//先写单趟,再写整体;for (int i = 0; i <n-1 ; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){//向后挪动一位a[end + 1] = a[end];end--;}else{//考虑极端情况,当插入的数值小于有序区数组每个元素,不断向前挪动过程中,直至end=-1;//出现插入的数值大于a[end];break;}}a[end + 1] = tmp;}
} 

直接插入排序特性总结

1.时间复杂度

最好情况就是全部有序,此时只需遍历一次,最好的时间复杂度为O(n)
最坏情况全部逆序,内层每次遍历已排序部分,最坏时间复杂度为O(n^2)

综上,因此直接插入排序的平均时间复杂度为O(n^2)

2. 空间复杂度

额外开辟的空间为常数个,所以空间复杂度为O(1);

3.算法稳定性

稳定性指相同元素的前后顺序是否改变,当插入元素小于a[end]时,插入到比它大的数前面,所以直接插入排序是稳定的;

希尔排序

希尔排序的基本思想

希尔排序是一种改进的插入排序算法,其基本思想是将待排序的序列按照一定的间隔(gap)分成若干个子序列,通过插入排序对大间隔的子序列进行排序,使得整个序列基本有序,然后逐步缩小间隔,重复上述操作,直到间隔gap为1,最后对整个序列进行直接插入排序;每次排序让数组接近有序的过程叫做预排序,最后一次排序为直接插入排序

1.  取间隔gap=3对下述序列进行分组,该序列被分成3组,每组之间都是以3的等差数列;

2. 对每一组进行插入排序,得到如下数组

3.缩小间隔,取间隔gap=2对数组进行分组,数组被分成2份,每组之间都是2的等差数列;

4. 对每一组进行插入排序,得到如下数组

5.缩小间隔,取间隔gap=1对数组进行分组,数组相当于没有分组,进行插入排序,当gap=1时为直接插入排序;

  • 如何选择希尔增量gap?

最初Shell提出的增量是 gap = [n / 2],每一次排序完让增量减少一半gap =[ gap / 2],直到gap = 1时排序变成了直接插入排序;后来Knuth提出的gap = [gap / 3] + 1,每次排序让增量成为原来的三分之一,加一是防止gap <= 3时gap = [gap / 3] = 0的发生,导致希尔增量最后不为1,无法完成插入排序;

上述无论哪一种主张都没有得到证明,本文代码实现部分采取gap=[n/2],gap=[gap/2];

希尔排序代码实现

  • 长度为n的数组间距为gap分为一组,共计gap组,定义遍历变量i,首先遍历第一组数据,i从0开始遍历,i每次移动gap步,由于数组尾元素下标为n-1,则i+gap=n-1,为防止数组越界访问,i最后访问位置为 n-1-gap ;

......

第一组排序代码如下:

    for (int i = 0; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}
  • 第一组排序完成,外面套一层循环,遍历其余组,便可完成第一趟排序,定义循环变量j,j从0开始,每次自增1,一共有gap组,j小于gap即可排序其他组;
	for (int j = 0; j < gap; j++){for (int i = j; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = tmp;}
  • 然后缩小增量 gap = gap /2 直到gap = 1,继续插入排序,直到排序完成;
void ShellSort(int* arr, int n)
{int gap = n;//预排序:分组排序,间距为gap分为一组,共计gap组;//最后进行直接插入排序,即gap==1;while (gap > 1){gap = gap / 2;//其次排序其余gap组,一次预排序完成,会进行多次预排序;for (int j = 0; j < gap; j++){//首先排序以第一个元素为起点,间距为gap的第一组;相当于整个数组排序;for (int i = j; i < n - gap; i += gap){//保证[0,end]有序,每个元素间隔为gap,控制[0,end+gap]有序;int end = i;int tmp = arr[end + gap];while (end >= 0){if (tmp < arr[end]){arr[end + gap] = arr[end];end = end - gap;}//else会出现俩种情况;//第一种为第一个元素以后且间距为gap的某个位置插入;//第二种会不断满足tmp<arr[end],直至end=-gap;else{break;}//end=-gap的位置,直接将arr[end+gap]=tmp;arr[end + gap] = tmp;}}}}
}

希尔排序特性总结

1.时间复杂度

希尔排序的时间复杂度取决于增量序列的选择,由于gap取值有多种方法,导致希尔排序的时间复杂度并不固定,一般来说,希尔排序的平均时间复杂度界于 O(n)到 O(n^2)之间, 最好的时间复杂度为 O(n^1.3)

2. 空间复杂度

希尔排序的空间复杂度为O(1),因为在希尔排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

希尔排序是一种非稳定的排序算法;希尔排序的精髓在于按照某个增量来划分子序列,实现跳跃式移动来提高排序效率,而也正是这种跳跃性带来了不稳定性。如果不同子序列中有相等的数,但它们不在同一子序列中,可能会因为子序列内部排序而改变原有的顺序

直接选择排序

直接选择排序的基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,通过交换存放在序列的起始位置(或最后一个位置),直到全部待排序的数据元素排完;

具体来说,就是在待排序序列中,首先找到最小的元素和最大的元素,然后将最小的元素与序列的第一个元素交换位置,最大的元素与序列的最后一个元素交换位置,接着在剩下的元素中找到最小的元素与最大的元素,将最小元素与序列的第二个元素交换位置,最大元素与序列的倒数第二个元素交换位置,以此类推,直到序列中只剩一个元素为止

具体步骤如下:

  1.   遍历整个序列,找到最小的元素与最大的元素;
  2.   将最小的元素与序列的第一个元素交换位置,最大的元素与序列尾元素交换位置;
  3.   序列遍历范围从第二个元素开始到倒数第二个元素,遍历序列,找到剩余元素中的最   小元素与最大元素;
  4.   将最小的元素与序列的第二个元素交换位置,最大的元素与序列倒数第二个元素交换位置;
  5.   重复上述步骤,直到整个列表都被排序;

 1. 定义变量begin,end,确定遍历范围[begin,end] ;定义maxi,mini记录遍历范围内的最大值下标  和最小值下标,最初maxi ,mini 指向遍历范围内的首元素,当在遍历范围内查找到最大值,最小值时更新maxi,mini ;

2. 遍历结束后查找到最大值,最小值,此时将最小的元素与遍历范围内的第一个元素交换位置,最大的元素与遍历范围内的尾元素交换位置;

3.交换完成后,begin自增1,end自减1,确定新的遍历范围,同时maxi ,mini 指向遍历范围内的首元素,当在遍历范围内查找到最大值,最小值时更新maxi,mini ;

4. 交换时先将遍历范围内的最小值与遍历范围的首元素交换,其次将遍历范围内的最大值与遍历范围内的尾元素交换;

当出现上述情况即出现 最大的位置恰好位于begin位置,说明mini是和最大的交换位置,这个时候max的位置就发生了变换, maxi变到了mini的位置,需要 更新max的位置,否则易导致错误,正确做法如下图所示
 

 

......

直接选择排序代码实现

//交换
void swap(int* p, int* q)
{int tmp = *p;*p = *q;*q = tmp;
}
//选择排序
void SelectSort(int* arr, int n)
{int begin = 0;int end = n - 1;while (begin < end){//单趟排序,将[begin,end]范围内最大与最小的数据调整于左右;int maxi = begin;//最大元素的下标,假设最大元素在最左边;int mini = begin;//最小元素的下标,假设最小元素在最左边;for (int i = begin+1; i <=end; i++){if (arr[i]>arr[maxi]){maxi = i;}if (arr[i]<arr[mini]){mini = i;}}swap(&arr[begin], &arr[mini]);//如果最大元素的位置位于begin位置,说明mini和最大的交换位置;//maxi的位置变化到mini的位置,要更新maxi;if (maxi == begin){maxi = mini;}swap(&arr[end], &arr[maxi]);end--;begin++;}
}

直接选择排序特性总结

1.时间复杂度

数据的比较次数为(n-1)+(n-2)+(n-3)+......1 = n*(n-1)/2,数据的交换次数为n-1,所以 时间复杂度为O(n^2)

2. 空间复杂度

选择排序的空间复杂度为O(1),因为在选择排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

选择排序是一种非稳定的排序算法,具体参见上图红五与白五排序前后的位置;

堆排序

大根堆:任意父节点的数值大于等于孩子节点的数值(a[i] >= a[2i+1] && a[i] >= a[2i+2]);

对堆中的结点按层进行编号,映射到数组便可得其存储结构

小根堆:任意父节点的数值小于等于孩子节点的数值(a[i] <= a[2i+1] && a[i] <= a[2i+2]);

对堆中的结点按层进行编号,映射到数组便可得其存储结构

结论: 大堆堆顶数据的数值最大; 小堆堆顶数据数值最小

堆排序是利用大堆或者小堆堆顶数据最大或最小的特性来进行排序的,所以,我们排序的对象必须是大堆或者小堆,但给定的数据不一定是大堆或者小堆,所以必须先建堆;

堆的向下调整算法

建堆的核心为堆的向下调整算法,堆的向下调整算法的基本思想

从根结点开始,利用假设法寻找同一高度处值最小的孩子,根据其左右子树为小堆还是大堆,按照小堆或大堆的原则将其交换,若为小堆,父节点处的数值大于孩子结点出的数值,则交换彼此位置;若为大堆,父节点处的数值小于孩子结点出的数值,则交换彼此位置;直至叶节点为止或出现满足其逻辑上不满足大小堆的数值为止;

//堆的向下调整算法(建大堆)
void AdjustDown(int* nums, int n, int parent)
{//假设法求同一深度左右孩子最小的一个,假设左孩子为最小int child = parent * 2 + 1;while (child<n){if (child + 1 < n && nums[child] < nums[child + 1]){child++;}//无论左右孩子,child为最大,调整为大堆;if (nums[child] > nums[parent]){swap(&nums[child], &nums[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

建堆

由于向下调整算法的使用前提是左右子树必须是大堆或者小堆,从倒数第一个非叶子结点开始调整,一定能够保证待排序序列的左右子树都是大堆或者小堆,此时即可看作大堆又可以看作小堆,根据需要进行调整即可;

倒数第一个非叶子结点恰好是最后一个结点的父亲,最后一个结点的下标为n-1,套用公式:parent=(child-1)/2,则该结点下标为:(n-1-1) / 2;

	//建堆 升序建大堆,降序建小堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}

 详细图解可参考:数据结构之堆-CSDN博客

堆排序的基本思想:

  1. 将待排序序列构造成一个大根堆
  2. 整个序列的最大值就是堆顶的根节点
  3. 堆顶元素数组尾元素进行交换,此时数组尾元素就为最大值;
  4. 将数组尾元素不看做堆里面的数据,前n-1个数继续向下调整为堆,选出次大的数,和倒数第二个位置的数交换;反复执行,得到升序序列;

堆排序代码实现

void HeapSort(int* arr, int n)
{//建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}//排序int end = n - 1;while (end > 0){swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);end--;}
}

堆排序特性总结

1.时间复杂度

堆向下调整算法的时间复杂度为O(logn),堆排序由建堆与排序俩部分组成,建堆的时间复杂度O(n) ;  假设节点数为n,需要进行n-1次交换,每次调整的时间复杂度O(logn),总的时间复杂度为(n-1)*O(logn),所以 堆排序的时间复杂度O(n)+O(n*logn)=O(n*logn)

2. 空间复杂度

堆排序的空间复杂度为O(1),因为在堆排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

堆排序是一种非稳定的排序算法,堆排序时需要交换堆顶和堆尾的数据,交换时两个相等的数据在序列中的相对位置就可能发生改变,所以堆排序不是一个稳定排序;

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

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

相关文章

【设计模式】模板方法模式

目录 一、定义二、使用场景三、使用方法四、结构五、代码示例六、优点七、缺点八、适用场景 一、定义 1.在父类定义一个操作中的算法骨架&#xff0c;将算法的一些步骤延迟到子类中&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤 二、使用场景 …

Vue3-pnpm包管理器创建项目

一些优势&#xff1a;比同类工具快2倍左右、节省磁盘空间 官网&#xff1a;pnpm - 速度快、节省磁盘空间的软件包管理器 | pnpm中文文档 | pnpm中文网 npm升级到yarn再升级到pnpm&#xff08;速度更快&#xff09; 安装方式&#xff1a;npm install -g pnpm 创建项目&#…

【vue】浏览器安装vue插件不生效

上一篇&#xff1a;浏览器安装vue插件 https://blog.csdn.net/m0_67930426/article/details/134598104 目录 问题情景 解决办法 问题情景 输入框无内容 解决办法 添加 Vue.config.devtools true; 并且控制台不显示的vue又出现

C_6微机原理

一、单项选择题&#xff08;本大题共 15小题&#xff0c;每小题3分&#xff0c;共45分。在每小题给出的四个备选项中&#xff0c;选出一个正确的答案&#xff0c;请将选定的答案填涂在答题纸的相应位置上。 n1 位有符号数 的补码表示范围为&#xff08;&#xff09; A. -2n&l…

lenovo联想笔记本YogaPro 14s IRP8D 2023款(83BU)原装出厂Windows11预装OEM系统

链接&#xff1a;https://pan.baidu.com/s/1s7PcN-y8RyHSV7uJQzC5OQ?pwddy9y 提取码&#xff1a;dy9y 联想电脑原厂W11系统&#xff0c;自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16GB或以上的U盘…

linux task_struct中进程调度相关的变量记录

参考文章&#xff1a; Linux进程调度分析记录&#xff0c;进程优先级&#xff0c;隔离处理器&#xff0c;isolcpus - 知乎

echarts 通用线性图

echarts 通用线性图 getLineData() {const myChart echarts.init(this.$refs.chartDom);const option {tooltip: {trigger: axis,},legend: {show: false,textStyle: {fontSize: 14, //字体大小color: #ffffff, //字体颜色},data: [AAA, BBB],},grid: {show: true,left: 10%,…

Linux文件操作应用及open和fork

1.文件操作的应用: 1).打开一个文件并往里面写入hello: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <assert.h> int main() { int fdopen("file.txt",O_WRONLY|O_CREAT,0600); …

NJU操作系统公开课笔记(2)

上期目录&#xff1a; NJU操作系统公开课笔记&#xff08;1&#xff09;https://blog.csdn.net/jsl123x/article/details/134431343?spm1001.2014.3001.5501 目录 一.处理器与寄存器 二.中断 三.中断系统 四.进程 五.线程与多线程技术概述 六.处理器调度算法 一.处理器…

完美的输出打印 SQL 及执行时长[MyBatis-Plus系列]

导读 Hi,大家好,我是悟纤。过着爱谁谁的生活,活出不设限的人生。 在我们日常开发工作当中,避免不了查看当前程序所执行的SQL语句,以及了解它的执行时间,方便分析是否出现了慢SQL问题。 MyBatis-Plus提供了两种SQL分析打印的方式,用于输出每条SQL语句及其执行时间,针…

上游任务和下游任务

起源多任务学习中的定义理解结合定义分析例子示例 1&#xff1a;计算机视觉示例 2&#xff1a;自然语言处理示例 3&#xff1a;语音处理示例 4&#xff1a;强化学习总结 起源 "上游任务"和"下游任务"这两个术语在深度学习领域中通常用来描述一种 多任务学…

FLASK博客系列6——数据库之谜

我们上一篇已经实现了简易博客界面&#xff0c;你还记得我们的博客数据是自己手动写的吗&#xff1f;但实际应用中&#xff0c;我们是不可能这样做的。大部分程序都需要保存数据&#xff0c;所以不可避免要使用数据库。我们这里为了简单方便快捷&#xff0c;使用了超级经典的SQ…

​无人机石油管道巡检方案新亮点:灵活准确又高效

在当前石油工业的安全管理中&#xff0c;无人机技术逐渐成为一种不可或缺的工具。随着我国油气管道里程的持续增长&#xff0c;确保这些关键基础设施的安全运行变得越来越重要。传统的巡检方法已经无法满足现代油气行业的需求&#xff0c;而无人机石油管道巡检技术的应用提供了…

2023.11.27如何使用内网穿透工具实现Java远程连接操作本地Elasticsearch搜索引擎

文章目录 前言1. Windows 安装 Cpolar2. 创建Elasticsearch公网连接地址3. 远程连接Elasticsearch4. 设置固定二级子域名 前言 简单几步,结合Cpolar内网穿透工具实现Java远程连接操作本地Elasticsearch。 什么是elasticsearch&#xff1f;一个开源的分布式搜索引擎&#xff0…

解决:AttributeError: module ‘os’ has no attribute ‘mknod’

解决&#xff1a;AttributeError: module ‘os’ has no attribute ‘mknod’ 文章目录 解决&#xff1a;AttributeError: module os has no attribute mknod背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&#xff0c;报错…

element table滚动条失效

问题描述:给el-table限制高度之后滚动条没了 给看看咋设置的&#xff1a; <el-table:data"tableData"style"width: 100%;"ref"table"max-height"400"sort-change"changeSort">对比了老半天找不出问题&#xff0c;最后…

使用opencv的matchTemplate进行银行卡卡号识别

![字体文件](https://img-blog.csdnimg.cn/3a16c87cf4d34aceb0778c4b20ddadb2.png#pic_center import cv2 import numpy as npdef show_img(img, name"temp"):img cv2.resize(img, (0, 0), fx3, fy3)cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()de…

excel单元格内换行按什么快捷键

如果我们使用excel软件的时候&#xff0c;因为一些日常的操作太过繁琐想要简化自己的操作步骤的话&#xff0c;其实是有很多快捷方式在其中的。那么对excel单元格内换行按什么快捷键这个问题&#xff0c;据小编所知我们可以在表格中使用Alt Enter来进行换行。详细内容就来看下…

C语言入门---位操作

目录 1. 两个数不同的二进制位个数 2.原码、反码、补码 3.不创建临时变量实现两个数的交换 4.求一个整数存储在内存中的二进制中1的个数 5. 特例-1 6.将指定的位置置1 7.将指定位置置1 8.a与a 9.||与&& 10.逗号表达式 11.srand与rand 12.sizeof 13.结构体初始…

操作系统 选择题 期末试题 考研真题 + 参考答案

1.&#xff08;考研真题&#xff0c;单项选择题&#xff09;单道批处理系统的主要缺点是&#xff08; &#xff09;。 A. CPU利用率不高 B.失去了交互性 C.不具备并行性 D.以上都不是 【参考答案】A 【解析】单道批处理系统的内存中只有一道程序&#xff0c;当该程序…