25.选择排序,归并排序,基数排序

目录

一. 选择排序

(1)简单选择排序

(2)堆排序

二. 归并排序

三. 基数排序

四. 各种排序方法的比较

(1)时间性能

(2)空间性能

(3)排序方法的稳定性能

(4)关于“排序方法的时间复杂度的下限”


一. 选择排序

(1)简单选择排序

基本思想:在待排序的数据中选出最大(小)的元素放在其最终的位置。
基本操作:
1.首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换。
2.再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换。
3.重复上述操作,共进行n-1趟排序后,排序结束。

 

 不难写出算法:

void SelectSort(SqList &L){for(i=1; i<L.length; ++i){k=i;  //第i趟从第i个元素开始for(j=i+1; j<=L.length; j++)if(L.r[j].key < L.r[k].key) k=j;  //记录最小值位置if(k!=i)  L.r[i]←—→L.r[k];  //交换}
}

下面我们分析时间复杂度。对移动次数来说,最好情况是0,最坏情况是3(n-1),也就是每一趟都得移动(每次移动需要移动3次)。对比较次数来说,无论待排序列处于什么状态,选择排序所需进行的"比较”次数都相同,为\sum_{i=1}^{n-1}(n-i)=\frac{n}{2}(n-1)

上面的算法是不稳定排序(但是可以稳定化)。具体的说用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。例如,给定8,5,8*,7,9;第1次:5,8,8*,7,9;第2次:5,7,8*,8,9;从而可以验证它是不稳定的。

(2)堆排序

堆的定义:若n个元素的序列{\left \{ a_1,a_2...a_n \right \}}满足\left\{\begin{matrix} a_i\leqslant a_{2i}\\ a_i\leqslant a_{2i+1} \end{matrix}\right.\left\{\begin{matrix} a_i\geqslant a_{2i}\\ a_i\geqslant a_{2i+1} \end{matrix}\right.,则分别称该序列为小根堆和大根堆。从堆的定义可以看出,堆实质是满足如下性质的完全二叉树:二叉树中任一非叶子结点均小于(大于)它的孩子结点。

显然,大根堆的根结点是最大值,小根堆的根结点是最小值。若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列又重建成一个堆,则得到n个元素的次小值(次大值)....如此反复,便能得到一个有序序列,这个过程称之为堆排序

那么怎么重建呢?以小(大)根堆为例:
1.输出堆顶元素之后,以堆中最后一个元素(编号最大的元素)替代之;
2.然后将根结点值与左、右子树的根结点值进行比较,并与其中小(大)者进行交换;
3.重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。

例如,对下面的小根堆,把13输出,最后一个元素97作为根结点,它的左右孩子是38和27,27较小,所以把97和27交换。此时97的左右孩子是65和49,49较小,把49和97交换,这个时候97已经是叶子结点就不用再操作了。

 写出算法如下:

void HeapAdjust(elem R[], int s, int m){
/*已知R[s..m]中记录的关键字除R[s]之外均满足堆的定义,本函数调整R[s]的关键字,使R[s..m]成为一个大根堆*/rc = R[s];for (j=2*s; j<=m; j *= 2){  //沿key较大的孩子结点向下筛选if (j < m && R[j] < R[j+1]) ++j;  //j为key较大的记录的下标if (rc >= R[j]) break;  //rc大于左右孩子,这个时候已经符合要求,就不用做了R[s] = R[j];  //较大的孩子结点往上升s = j;  //rc应插入在位置s上,更新s}//forR[s] = rc;  //插入
}//HeapAdjust

HeapAdjust函数是一个用于调整堆的函数。它接受一个数组R,以及两个整数s和m作为参数。s表示要调整的子树的根节点的位置,m表示该子树的最后一个节点的位置。

首先,将根节点的值保存在变量rc中。然后,通过一个循环来比较根节点和其子节点的值。在循环中,变量j初始化为根节点的左子节点的位置(2*s),然后每次乘以2,即可得到下一个子节点的位置。在循环中,首先判断是否存在右子节点,并且右子节点的值是否大于左子节点的值。如果满足条件,则将j加1,即将j指向右子节点。然后,判断rc的值是否大于等于R[j]的值。如果满足条件,则退出循环。如果rc的值小于R[j]的值,则将R[j]的值赋给R[s],即将较大的子节点的值上移到根节点的位置。然后,将s更新为j,即将s指向较大子节点的位置。循环结束后,将rc的值赋给R[s],即将根节点的值放到合适的位置上。这样,HeapAdjust函数完成了对以s为根节点的子树的调整,使其满足堆的性质。

可以看出:对一个无序序列反复“筛选”就可以得到一个堆。即:从一个无序序列建堆的过程就是一个反复“筛选”的过程。我们重新考察堆的定义,显然:单结点的二叉树是堆,在完全二叉树中所有以叶子结点(序号i > n/2,这里是整除向下取整)为根的子树也是堆。这样,我们只需依次将以序号为n/2,n/2 - 1,.....1的结点为根的子树均调整为堆即可。即:对应由n个元素组成的无序序列,“筛选”只需从第n/2个元素开始。

由于堆实质上是一个线形表,那么我们可以顺序存储一个堆。下面以一个实例介绍建一个小根堆的过程。例如给定关键字为49,38,65,97,76,13,27,49的一组记录,将其按关键字调整为一个小根堆:

将初始无序的R[1]到R[n]建成一个小根堆,可用以下语句实现:

for(i = n/2 ; i >= 1; i--)HeapAdjust (R, i, n);

上面我们了解了怎么建堆。若对一个无序存列建堆,然后输出根。重复该过程就可以由一个无需序列输出有序序列。实质上,堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的。

void HeapSort(elem R[]){  //对R[1]到R[n]进行堆排序int i;for (i = n/2; i>= 1; i--)HeapAdjust(R, i, n);//建初始堆for (i = n; i > 1; i--){  //进行n-1趟排序Swap(R[1], R[i]);  //根与最后一个元素交换,也就是把根结点输出并放在最后一个位置HeapAdjust(R, 1, i-1);  //对R[1]到R[i-1]重新建堆}
}//HeapSort

最后我们来研究时间复杂度。初始堆化所需时间不超过O(n),排序阶段(不含初始堆化)每次重新堆化所需时间不超过O(logn),则n-1次循环所需时间不超过O(nlogn)。因此:
Tw(n)=O(n)+ O(nlogn)= O(nlogn)

堆排序的时间主要耗费在建初始堆和调整建新堆时进行的反复筛选上。堆排序在最坏情况下,其时间复杂度也为O(nlog2n),这是堆排序的最大优点。无论待排序列中的记录是正序还是逆序排列,都不会使堆排序处于"最好"或"最坏"的状态。另外,堆排序仅需一个记录大小供交换用的辅助存储空间。

然而堆排序是一种不稳定的排序方法,它不适用于待排序记录个数n较少的情况,但对于n较大的文件还是很有效的。

二. 归并排序

基本思想:将两个或两个以上的有序子序列“归并”为一个有序予列。在内部排序中,通常采用的是2-路归并排序。即:将两个位置相邻的有序子序列R[1..m]和R[m+1..n]归并为一个有序序列R[1..n]。

这种树称为归并树。n个元素归并排序只需要\left \lceil log_2n \right \rceil趟。下面讨论怎么把两个有序序列合并成一个有序序列。这里可以参考线性表的合并算法。设R[low]-R[mid]和R[mid+1]-R[high]为相邻,归并成一个有序序列R1[low] - R1[high].

若SR[i].key<=SR[j].key,则TR[k]=RS[i];k++;i++;  否则,TR[k]=SR[j];k++;j++;

归并排序的时间效率是O(nlog2n),空间效率是O(n),因为需要一个与原始序列同样大小的辅助序列(TR)。这正是此算法的缺点。归并排序算法是稳定的算法。

三. 基数排序

基本思想:分配+收集

基数排序也叫桶排序或箱排序:设置若干个箱子,将关键字为k的记录放入第k个箱子,然后在按序号将非空的连接。基数排序的数字是有范围的,均由0-9这十个数字组成,则只需设置十个箱子,相继按个、十、百...进行排序。例:给定待排序序列(614,738,921,485,637,101,215,530,790,306)。这里每一个箱子都是一个队列,遵循先进先出的原则:

至此排序完成!基数排序的时间效率:O(k*(n+m)),其中k:关键字个数(上面有3个关键字),m:关键字取值范围为m个值(上面为10),n:元素个数。这里,每一趟分配n个元素,收集m个桶,总共需要k遍。

空间效率:这里需要放置m个桶,回收的时候回收n个元素,则空间复杂度是O(n+m)。基数排序是稳定的。

四. 各种排序方法的比较

(1)时间性能

1.按平均的时间性能来分,有三类排序方法:

  • 时间复杂度为O(nlogn)的方法有:快速排序、堆排序和归并排序,其中以快速排序为最好;
  • 时间复杂度为O(n^2)的有:直接插入排序、冒泡排序和简单选择排序,其中以直接插入为最好,特别是对那些对关键字近似有序的记录序列尤为如此;
  • 时间复杂度为O(n)的排序方法只有:基数排序。

2.当待排记录序列按关键字顺序有序时,直接插入排序和冒泡排序能达到到O(n)的时间复杂度;而对于快速排序而言,这是最不好的情况,此时的时间性能退化为O(n^2),因此是应该尽量避免的情况。
3.简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。

(2)空间性能

指的是排序过程中所需的辅助空间大小.
1.所有的简单排序方法(包括:直接插入、冒泡和简单选择)和堆排序的空间复杂度为O(1)
2.快速排序为O(logn),为栈所需的辅助空间
3.归并排序所需辅助空间最多,其空间复杂度为O(n)
4.链式基数排序需附设队列首尾指针,则空间复杂度为O(rd)

(3)排序方法的稳定性能

稳定的排序方法指的是,对于两个关键字相等的记录,它们在序列中的相对位置,在排序之前和经过排序之后,没有改变。

  • 当对多关键字的记录序列进行LSD方法排序时,必须采用稳定的排序方法。
  • 对于不稳定的排序方法,只要能举出一个实例说明即可。
  • 快速排序和堆排序是不稳定的排序方法。

(4)关于“排序方法的时间复杂度的下限”

本章讨论的各种排序方法,除基数排序外,其它方法都是基于“比较关键字”进行排序的排序方法,可以证明,这类排序法可能达到的最快的时间复杂度为O(nlogn)。(基数排序不是基于“比较关键字”的排序方法,所以它不受这个限制)。

可以用一棵判定树来描述这类基于“比较关键字”进行排序的排序方法。

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

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

相关文章

Web网站服务器

目录 一、什么是Apache? 二、虚拟目录是什么&#xff1f; 三、Apcahe相关配置文件 四、httpd.conf主配置文件的常用配置参数 五、Web网站配置案例 5.1搭建基于用户的个人主页网站 5.2、配置虚拟目录 5.3、配置虚拟主机 5.3.1搭建两个基于IP地址的虚拟主机 5.3.2搭建两个基于域…

国标GB28181视频平台EasyGBS国标视频云平台级联到EasyCVR,上级平台无法播放通道视频的问题解决方案

EasyGBS国标视频云平台是基于国标GB28181协议的视频能力兼服务平台&#xff0c;可实现的视频能力包括将设备通过国标GB28181协议接入、流媒体转码、处理及分发、直播录像、语音对讲、云存储、告警、平台级联等功能。其中&#xff0c;平台级联功能是指平台与平台之间可以通过国标…

【ES6】Promise.race的用法

Promise.race()方法同样是将多个 Promise 实例&#xff0c;包装成一个新的 Promise 实例。 const p Promise.race([p1, p2, p3]);上面代码中&#xff0c;只要p1、p2、p3之中有一个实例率先改变状态&#xff0c;p的状态就跟着改变。那个率先改变的 Promise 实例的返回值&#…

使用Python写入数据到Excel:实战指南

在数据科学领域&#xff0c;Excel是一种广泛使用的电子表格工具&#xff0c;可以方便地进行数据管理和分析。然而&#xff0c;当数据规模较大或需要自动化处理时&#xff0c;手动操作Excel可能会变得繁琐。此时&#xff0c;使用Python编写程序将数据写入Excel文件是一个高效且便…

比较器的工作原理及性能指标介绍

一、什么是比较器 比较器的功能是比较两个或更多数据项&#xff0c;以确定它们是否相等&#xff0c;或者确定它们之间的大小关系和排列顺序&#xff0c;这称为比较。可以实现此比较功能的电路或设备称为比较器。比较器是将模拟电压信号与参考电压进行比较的电路。比较器的两个…

TikTok网红营销之谜:为何成功程度参差不齐?

近年来&#xff0c;随着社交媒体的迅猛发展&#xff0c;TikTok作为一款以短视频为主要内容形式的应用&#xff0c;在全球范围内迅速走红。不仅个人用户在TikTok上分享自己的创意&#xff0c;越来越多的品牌也开始借助TikTok网红进行营销推广。然而&#xff0c;尽管众多人都在尝…

苍穹外卖01-项目概述、环境搭建

项目概述、环境搭建 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; 管理端-外卖商家使用用户端-点餐用户使用当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一名软…

【力扣】62. 不同路径 <动态规划>

【力扣】62. 不同路径 一个机器人位于一个 m m m x n n n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。问总共有多少条…

在GPU服务器(Linux)上安装Anaconda和PyTorch环境

安装Anaconda3 Anaconda官网&#xff1a;https://repo.anaconda.com/archive/ 根据自己需要&#xff0c;复制安装包名字&#xff0c;以Anaconda3-2023.07-2-Linux-x86_64.sh为例 命名规则&#xff1a;Anaconda3-<版本号>-Linux-x86_64.sh 在终端输入命令&#xff0c;下…

Sharding-JDBC分片策略

Sharding-JDBC分片策略 包含分片键和分片算法&#xff0c;由于分片算法的独立性&#xff0c;将其独立抽离。真正可用于分片操作的是分片键 分片算法&#xff0c;也就是分片策略。目前提供5种分片策略。 一个好的分片策略好的分片键好的的分片算法 1. 标准分片策略 对应Stan…

java_error_in_idea.hprof 文件

在用户目录下的java_error_in_idea.hprof文件(/Users/用户) 大约1.5个G,IDEA的错误日志,可以删除

深度刨析数据在内存中的存储

✨博客主页&#xff1a;小钱编程成长记 &#x1f388;博客专栏&#xff1a;进阶C语言 深度刨析数据在内存中的存储 1.数据类型介绍1.1 类型的基本归类 2.整形在内存中的存储2.1 原码、反码、补码2.2 大小端介绍 3.浮点型在内存中的存储3.1 一个例子3.2 浮点数的存储规则3.3指数…