排序1——直接插入排序,希尔排序,选择排序,堆排序

1.排序的概念及其运用

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

  1. 内部排序:数据元素全部放在内存中的排序。
  2. 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2排序运用

1.3.常见排序

2.插入排序 

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。

实际中我们玩扑克牌时,就用了插入排序的思想

我们摸牌的时候,每摸一张牌我们就将其插入到有序的位置当中 

2.1直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与 array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移

在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。

//插入排序(升序)
void InsertSort(int* a, int n)
{int i = 0;for (i = 0; i < n-1 ; ++i){int end = i;//记录有序序列的最后一个元素的下标//最开始只有1个元素,是有序的,所以有序序列最后一个元素的下标是0int tmp = a[end + 1];//待插入的元素,就紧跟在有序序列的后面while (end >= 0){if (tmp < a[end])//待插入元素比有序序列最后一个元素还小              {a[end + 1] = a[end];//将有序序列最后一个元素往后移动,为待插入元素留位置end--;//更新新的有序序列的最后一个元素的下标}else   //待插入元素>=有序序列最后一个元素{break;}}//代码执行到此位置有两种情况://1.待插入元素比当前所有有序序列的所有元素都大或等于(break跳出循环到此,end=i)。//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此,end==-1)。a[end + 1] = tmp;//插入元素}
}

 我们来深入理解一下,假设给了我们一个数组,我们用直接插入排序将其排成有序的过程如下

如果还不理解,我们就看动画 

 2.1.1时间复杂度分析

  • 普通插入排序的时间复杂度最坏情况下为O(N2),此时待排序列为逆序,或者说接近逆序。
  • 普通插入排序的时间复杂度最好情况下为O(N),此时待排序列为升序,或者说接近升序。

2.2希尔排序

现在,我要讲解的算法叫希尔排序(Shell Sort)。

希尔排序是D.L.Shell于1959年提出来的一种排序算法,在这之前排序算法的时间复杂度基本都是0(n),希尔排序算法是突破这个时间复杂度的第一批算法之一。

我们前一节讲的直接插入排序,应该说,它的效率在某些时候是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,此时直接插入很高效。还有就是记录数比较少时,直接插入的优势也比较明显。

可问题在于,两个条件本身就过于苛刻,现实中记录少或者基本有序都属于特殊情况。

不过别急,有条件当然是好,条件不存在,我们创造条件也是可以去做的。于是科学家希尔研究出了一种排序方法,对直接插入排序改进后可以增加效率。

如何让待排序的记录个数较少呢?

很容易想到的就是将原本有大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了,然后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序,再对全体记录进行一次直接插入排序。

这便是希尔排序的基本内容

希尔排序分成两部分

希尔排序,又称缩小增量法。其基本思想是:

  1. 先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
  2. 当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。

问题:为什么要让gap由大到小呢?
因为gap越大,数据挪动得越快;gap越小,数据挪动得越慢。前期让gap较大,可以让数据更快得移动到自己对应的位置附近,减少挪动次数。

注:一般情况下,取序列的一半作为增量,然后依次减半,直到增量为1(也可自己设置,项下面的代码直接设置成三分之一)。

举个例子分析一下:
 现在我们用希尔排序对该序列进行排序。

gap的值折半,此时相隔距离为2的元素被分为一组(共分了2组,每组有5个元素),然后再分别对每一组进行直接插入排序。

 gap的值再次减半,此时gap减为1,即整个序列被分为一组,进行一次直接插入排序。

 该题中,前两趟就是希尔排序的预排序,最后一趟就是希尔排序的直接插入排序。 

代码

//希尔排序(升序)
void ShellSort(int* a, int n)
{// gap > 1 预排序// gap == 1 直接插入排序int gap = n;while (gap > 1){//gap /= 2;这个可以,但是有人嫌他慢,所以用了下面那个gap = gap / 3 + 1;//这里加1是为了保证最后一次gap==1for (int i = 0; i < n - gap; i++){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;}}
}

需要注意的是:增量序列最后一个增量值必须是1才可以,因为这个1代表整个序列被分成了一组,对这整个序列再来一次直接插入排序,完成希尔排序第二部分

2.2.1希尔排序复杂度分析

通过这段代码的剖析,相信大家有些明白,希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

这里“增量”的选取就非常关键了。我们在代码中gap=gap/3+1的方式选取增量的,可究竟应该选取什么样的增量才是最好,目前还是一个数学难题,迄今为止还没有人找到一种最好的增量序列。

需要注意的是,增量序列的最后一个增量值必须等于1才行。

另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。

时间复杂度:O(NlogN)  空间复杂度:O(1)

2.2.2希尔排序的特性总结:

2.2.2.1. 希尔排序是对直接插入排序的优化。

2.2.2.2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

2.2.2.3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定:

2.2.2. 4. 稳定性:不稳定

3.选择排序

3.1基本思想:

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

 代码:

void Swap(int* p1, int* p2)
{int x = *p1;*p1 = *p2;*p2 = x;
}
//选择排序(一次选一个数)
void SelectSort(int* a, int n)
{int i = 0;for (i = 0; i < n; i++)//i代表参与该趟选择排序的第一个元素的下标{int start = i;//记录当前遍历到的元素的下标int min = start;//记录最小元素的下标while (start < n){if (a[start] < a[min])//如果当前元素比最小元素还小min = start;//最小值的下标更新start++;//遍历下一个元素}Swap(&a[i], &a[min]);//最小值与参与该趟选择排序的第一个元素交换位置}
}

3.2直接选择排序的特性总结:

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

4.堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。

需要注意的是排升序要建大堆,排降序建小堆。

4.1基本思想

我们以升序为例讲讲

堆排序的基本思想是:

  • 将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。
  • 将其与末尾元素进行交换,此时末尾就为最大值。
  • 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了 

步骤一 构造堆

将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

1.假设给定无序序列结构如下

2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr的(size-1-1)/2=3/2=1,也就是下面的6结点),从左至右,从下至上进行调整。

3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。

此时,我们就将一个无需序列构造成了一个大顶堆。

 步骤二

将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a.将堆顶元素9和末尾元素4进行交换

b.重新调整结构,使其继续满足堆定义

c.后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序 

再简单总结下堆排序的基本思路:

  1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
  3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

4.2代码

void Swap(int* p1, int* p2)
{int x = *p1;*p1 = *p2;*p2 = x;
}//堆的向下调整算法
void AdjustDown(int* a, int n, int root)
{int parent = root;int child = 2 * parent + 1;//假设左孩子较大while (child < n){if (child + 1 < n&&a[child + 1] > a[child])//右孩子存在,并且比左孩子大{child++;//左右孩子的较大值}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else//已成堆{break;}}
}//堆排序
void HeapSort(int* a, int n)
{//排升序,建大堆//从第一个非叶子结点开始向下调整,一直到根int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;//记录堆的最后一个数据的下标while (end){Swap(&a[0], &a[end]);//将堆顶的数据和堆的最后一个数据交换AdjustDown(a, end, 0);//对根进行一次向下调整end--;//堆的最后一个数据的下标减一}
}

4.3复杂度分析

1.  时间复杂度:堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以堆排序时间复杂度最好和最坏情况下都是O(nlogn)级。

2.  空间复杂度:堆排序不要任何辅助数组,只需要一个辅助变量,所占空间是常数与n无关,所以空间复杂度为O(1)

4.4堆排序的特性总结:

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

如果有不懂堆排序的还可以看看我的另外一篇文章:http://t.csdnimg.cn/QLNRL

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

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

相关文章

idea中使用git拉取代码详细操作

注意&#xff1a;解决 Git拉取代码和本地代码丢失问题请点这里查看 以textGit文件为例&#xff1a; 下图&#xff1a;本地刚拉取远程的代码 git上的代码 1、在本地对代码进行修改 2、在git上对代码进行修改&#xff0c;模拟其他人对此文件的提交修改 3、拉取远程代码 4、合并自…

智慧校园的主要功能是什么

随着信息化的发展&#xff0c;智慧校园的应用已经屡见不鲜。智慧校园是新技术与新科技落地的典型案例。智慧校园完善了校园信息化建设体系&#xff0c;推动了教育水平的提升&#xff0c;以下是智慧校园实现的几个比较典型的功能&#xff1a; 1.数字化办公 毋庸置疑&#xff0…

Remix Client/Server 架构

Remix 框架是服务端渲染架构&#xff0c;当路由请求时生成 HTML 并返回浏览器。这种 SSR 是如何实现的呢&#xff1f;如果不使用 Remix 这种框架&#xff0c;可以在服务器段启动一个无头浏览器进行页面渲染并返回&#xff0c;代价就是要在服务器上启动一个 Chrome 服务&#xf…

第12节 第二种shellcode编写实战(1)

我最近在做一个关于shellcode入门和开发的专题课&#x1f469;&#x1f3fb;‍&#x1f4bb;&#xff0c;主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料&#xff0c;内容里面的每一个环境我都亲自测试实操过的记录&#xff0c;有需要的小伙伴可以参考…

三层交换机与路由器连通上网实验

三层交换机是一种网络交换机&#xff0c;可以实现基于IP地址的高效数据转发和路由功能&#xff0c;通常用于大型企业、数据中心和校园网络等场景。此外&#xff0c;三层交换机还支持多种路由协议&#xff08;如OSPF、BGP等&#xff09;&#xff0c;以实现更为复杂的网络拓扑结构…

2024年第九届“数维杯”大学生数学建模挑战赛B题

第一个问题为&#xff1a;正己烷不溶物对热解产率是否产生显著影响&#xff1f; 第一个问题&#xff1a;正己烷不溶物(INS)对热解产率是否产生显著影响&#xff1f; 解析&#xff1a;正己烷不溶物(INS)主要是由生物质和煤中的非挥发性物质组成&#xff0c;它们在共热解过程中会…

通信指挥类装备(多链路聚合设备)-应急通信指挥解决方案

现场通信指挥系统是一种功能全面的便携式音视频融合指挥通信平台&#xff0c;可实现现场应急救援指挥、多种通信手段融合、现场通信组网等功能&#xff0c;是现场指挥系统的延伸。 多链路聚合设备&#xff0c;是一款通信指挥类装备&#xff0c;具有 4G/5G&#xff0c;专网&…

Cloudera的简介及安装部署

简介 Cloudera是一家位于美国的软件公司&#xff0c;成立于2008年&#xff0c;专注于为企业客户提供基于Apache Hadoop的软件、支持、服务以及培训。Cloudera的开源Apache Hadoop发行版&#xff0c;即Cloudera Distribution including Apache Hadoop&#xff08;CDH&am…

AI赋能未来教育:中国教学科研新蓝图

设“人啊 前言 回顾过去&#xff0c;传统的教育模式以知识灌输和应试为主&#xff0c;虽培养出大量人才&#xff0c;但也存在着学生创新能力不足、实践经验缺乏等问题。随着时代的进步和科技的发展&#xff0c;传统教育模式已难以满足当今社会对人才的需求。然而&#xff0c;当…

阿里云和AWS负载均衡服务对比分析

在云计算时代,负载均衡作为一种关键的网络基础设施,承担着在多个服务器之间分发网络流量的重要任务。作为全球两大主要的云服务提供商,阿里云和Amazon Web Services(AWS)都提供了强大的负载均衡解决方案。本文将从性能、功能、可用性和成本等方面对两者进行对比分析。我们九河云…

Html + Express 实现大文件分片上传、断点续传、秒传

在日常的网页开发中&#xff0c;文件上传是一项常见操作。通过文件上传技术&#xff0c;用户可以将本地文件方便地传输到Web服务器上。这种功能在许多场景下都是必不可少的&#xff0c;比如上传文件到网盘或上传用户头像等。 然而&#xff0c;当需要上传大型文件时&#xff0c;…

不容错过的秘籍:JavaScript数组的创建和使用详解

在编程的世界里&#xff0c;数据是构建一切的基础。而在JavaScript中&#xff0c;有一种特殊且强大的数据结构&#xff0c;它就是——数组。 今天&#xff0c;我们就来一起探索数组的奥秘&#xff0c;从创建到使用&#xff0c;一步步掌握这个重要的工具。 一、什么是数组 数…