本博客已弃用,当时存在一些小细节错误后期也不再修改了

欢迎来我的新博客

九大排序排序是数据结构体系中最重要的内容之一,这一块必须要非常熟练的掌握,应该做到可以立马写出每个排序的代码,有多种实现方法的必须多种都能很快写出来,当然对各个排序的性能的了解也是基础且重要的。我们先对排序这一块进行一个整体的把握。

这里先说明几个概念

  • 内排序:在对待排序数据存放在内存中进行的排序过程。是我们主要讨论与学习的重点。
  • 外排序:待排数据量太大,无法一次性将所有待排序数据放入内存中,在排序过程中需要对磁盘等外部储存器进行访问。不是我们谈论与学习的重点,但也要通过相关资料或书籍了解其基本原理。
  • 比较排序:排序过程中需要对数据关键字进行比较。
  • 非比较排序:排序过程中不需要对数据关键字进行比较。
  • 排序算法的稳定性:在每一次单趟排序后,相同关键字的相对顺序不变。(后面讲到了再具体解释)

所有讲解都以升序为例(降序完全就是改个符号的问题啦),本篇先讲几个简单的,复杂些的后面每个单独写。


直接插入排序

直接插入排序是非常简单的,对于一个长度为n的关键字序列a1,a2,a3,a4,a5...an, 一句话来说:我们就是要把每个关键字都放到其之前的有序子序列中的合适位置。

具体是这样:你看,该序列的第一个元素a1之前不存在子序列因此不用考虑,故从第二个元素开始,先把a2的值保存到Key中,那么此时,a2如果大于等于a1,那么a2就不动,因为这就是它当前合适的位置,若是a2小于a1那么就把a1往后挪一个,把a2放到a1的位置。

对于一般的情况是这样:

在有序子序列中从后往前找,直到找到一个key小于等于key的数(每往前找一个,那么就往后挪一个),把key放在这个数后面,这就是key合适的位置(好好想明白),或者可能找完了整个序列也没找到我们要的合适的位置,那就放在该有序子序列的第一个元素的位置(此时有序子序列的所有元素已经后挪),同样,你想想,若有序子序列的最后一个元素就小于key,那么就不用挪了,当前的位置就是合适的位置。

那么接下来

当然,更常见的情况是这样,并不是简单的是需要移动多次的,但是没关系,核心思想都是一样的

前面四个关键字都调整好了,那么第五个呢?看到这你肯定心中有答案了

代码

void InsertSort(int* a,int len)
{int begin = 1;int i = 0;while(begin < len){int key = a[begin];for(i = begin-1;i>=0;i--){if(a[i]<=key)    //稳定的{a[i+1] = key;break;}a[i+1] = a[i];}if(i<0)a[0] = key;//说明找完了整个有序子序列都没找到begin++;}
}

其实在找关键字key合适位置时,从有序子序列前往后也是可以的,从时间复杂度上一样的,但是个人认为从后往前扫描的代码写出来非常简洁。

算法分析

  • 平均时间复杂度:o(N^2)这是显然的,标准的内外两层循环
  • 最好时间复杂度:o(N),如果有序,那么每个元素都已经在在它的待排子序列的合适位置,不用找合适位置
  • 最坏时间复杂度:o(N^2)
  • 空间复杂度:o(1),因为需要常熟个临时变量
  • 稳定性:稳定的

有人提出在定位关键字的合适位置的时候用二分查找,正好可以利用前面排好的结果(就是在把每个关键字放入到其前面的有序子序列中合适位置时,未必要一个个比较着来找,因为有序子序列是有序的,那么很自然的就可以想到用二分查找来降低时间复杂度),这样每次查找合适位置的时间复杂度就由o(n)降低到了o(log2n)了,但是对于找到后的插入这一操作,还是得挨个挪动数据,仍然为o(n)。这就很尴尬了,你会发现整体的时间复杂度还是o(n^2),并没有用,查找能快又怎么样呢,还是得一个一个挪数据,所以,普通的直接插入排序是没法再优化了。


希尔排序

希尔排序是一个叫希尔的数学家提出的一种优化版本直接插入排序

有必要先谈谈这个排序是怎么来的(主要基于以下两点)

  1. 在直接插入排序里,对于一组已经有序的关键字序列进行排序时间复杂度是o(n),因为每个元素的合适位置的查找都只需一次就能找到,因为左边的有序子序列的最后一个就小于等于该元素,那么就不需要挪动任何元素,每一趟都只用比较一次就结束了单趟排序。
  2. 放宽第一点的情况,我们不要求有序,其实只要待排序列的有序度越高,也就是逆序对越少,直接插入排序的时间复杂度肯定是越低的(这句话是希尔排序的核心)。

基于这两点希尔对直接插入排序进行了这样的优化:先将待排序列分为若干个稀疏的子序列(后面解释),对这些子序列分别进行直接插入排序。经过这一番调整,整个序列的有序度一定会被提高,逆序对一定会变少。最后,再对整个序列进行一趟直接插入排序。

首先选定子序列中各元素的距离,使待排数据中所有间距为为的数据被分成了一组,在各个组内进行直接插入排序,直到。此时我们最后一趟进行的就相当于一次完整的(但是效率很高的)直接插入排序。

过程如下

需要注意的是,虽然每次是对各组进行插入排序,但是写代码时候的思维当然不是直接单独对每个组那样直接插入排序依次,而仍然是从前往后扫描,那么显然,我们每次直接插入排序的key从每次的第一个子序列的第二个元素开始(与直接插入排序同理),然后挨个往后走,每个元素时属于哪个组,我们就把它在其所在组进行直接插入排序,上面的例子中,第一次排序中,我们实际进行时就是从94开始往后走的,第二次排序中,我们就是从05开始的,这样代码就非常好写了。

void ShellSort(int* a,int len)
{int gap = len;                                                                                                                          while(gap > 1){   gap = gap/3 + 1;for(int i = gap;i < len;i++){   int key = a[i];int start = i - gap;while(start >= 0 && key <= a[start])//对当前key进行一趟直接插入排序{   a[start+gap] = a[start];start -= gap;}   a[start + gap] = key;}   }
}

希尔排序的时间复杂度相当不好分析,因为其时间复杂度很大程度上取决于增量序列,而的选取至今也没有人找出怎样的一个增量序列是对所有情况的数据分布都有非常不错的效率,我们在代码中选取是一种较为常用、对于大部分数据分布的平均时间复杂度都较为不错的增量序列。

算法分析

  • 平均时间复杂度:o(N^1.3),大量数据研究表明在o(N^1.3)附近,事实上也不是严格论证的结果
  • 最好时间复杂度:仍然和增量序列的选取有关
  • 最坏时间复杂度:o(N^2)
  • 空间复杂度:o(1)
  • 稳定性:不稳定,例如待排序列(3,2,2,1),取时,显然就能看出是不稳定的

简单选择排序

简单选择排序是真的相当简单了,其思想概括来说就是每趟从当前待排序列中选出最小的放在已拍好序列的末尾

指针 记录待排序列的第一个的元素,指针用来找待排序列中最小的那个元素,当然是从i开始,遍历待排序列一趟后,把指针指向的元素与指针指向的元素交换,因为待排序列中又少了一个元素,已排序列末尾又多了一个,因此往后走一个,如此知道待排序列中只剩下一个元素。

这显然效率太低了点,我们忘了它吧。有个明显可以做的优化就是我们每趟遍历可以找出待排序序列中最小和最大的两个,放在待排序列两端,然后缩小待排序列,如此循环直到当前待排序列中只有一个元素。这样效率肯定能稍微好一些,但无疑仍然是平方级的时间复杂度。

代码

void SelectSort(int* a,size_t len)
{int begin = 0;int end = len - 1;int max = 0;int min = 0;while(begin < end){max = begin,min = begin;for(int i = begin;i <= end;i++){if(a[i]>=a[max])  //有相同元素时,最大的要找位置相对最后的一个最大的{max = i;}if(a[i]<a[min])  //最小的要找位置相对最前的一个最小的,这样可以使选择排序算法稳定{min = i;}}if(max == begin && min == end){swap(a[max],a[end]);continue;}if(max == begin){swap(a[max],a[end]);swap(a[min],a[begin]);continue;}if(min == end){swap(a[min],a[begin]);swap(a[max],a[end]);continue;}swap(a[min],a[begin]);swap(a[max],a[end]);begin++;end--;}
}

注意:找到当前待排序列最大、最小的元素后,要判断是否为最小的在最后,最大的在最前这些情况的组合,稍微分析下就能知道应该怎么交换才是合适的。

算法分析

  • 平均时间复杂度:o(N^2),嵌套双循环
  • 最好时间复杂度:o(N^2),每次要找最大最小肯定是要遍历一遍的
  • 最坏时间复杂度:o(N^2)
  • 空间复杂度:o(1)
  • 稳定性:稳定的(在注释中已解释)

冒泡排序

这应该是所有人在没系统的学习数据结构前就已经熟知的排序算法,它也很简单,而且它的名字非常形象,冒泡排序的思想就是每一趟排序都把大的元素往上浮,具体是这样:从当前待排序列第一个开始遍历,指针从第一个开始,如果当前元素大于下一个,那么二者交换,指针往后走,当走到待排序列末尾时,最大的一定被放到了最后(像冒泡泡一样上去了),然后缩小待排子序列(把最后一个从当前待排序序列删去),如此循环直到当前待排子序列只有一个元素。

有一定可以明显优化的是,如果在某次单趟排序中没有发生元素的交换,可以说明整个待排序列已经有序

代码

void BubbleSort(int* a,size_t len)
{int end = len-1;while(end > 0){bool exchange = false;for(int i = 0;i < end;i++){if(a[i]>a[i+1]){swap(a[i],a[i+1]);exchange = true;}}if(exchange == false)return;elseexchange = false;end--;}
}

算法分析

  • 平均时间复杂度:o(N^2),嵌套双循环
  • 最好时间复杂度:o(N),若已经有序,那么第一趟就排好了
  • 最坏时间复杂度:o(N^2)
  • 空间复杂度:o(1)
  • 稳定性:稳定的(在注释中已解释)

经典九大排序(1)——简单排序相关推荐

  1. 八大排序算法-简单排序(3种)

    八大排序算法-简单排序 javascript-选择排序 jacascript-冒泡排序 jacascript-插入排序 javascript-选择排序 算法思想:简单选择排序是最简单直观的一种算法,基 ...

  2. 数据结构 排序【简单排序(冒泡、插入)、希尔排序、堆排序、排序方法的综合比较、2套 排序汇总代码】

    目   录 第9章 排序(上) 9.1 简单排序(冒泡.插入) 1.前提 2.简单排序(冒泡排序) 3.简单排序(插入排序) 4.时间复杂度下界 9.2 希尔排序 9.3 堆排序 排序方法综合比较 排 ...

  3. 排序_简单排序_选择排序

    选择排序是有三个记录值,其中一个记录值标记着需要比较的数组的第一个值,也是变换后的最小值.其中两个记录值记录需要比较的两个对象,而且记录临时最小值的位置和记录另一个还未比较的数据. public cl ...

  4. 十大排序算法:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序

    冒泡排序.选择排序.插入排序.希尔排序.归并排序.快速排序.堆排序.计数排序.桶排序.基数排序的动图与源代码. 目录 关于时间复杂度 冒泡排序 选择排序 插入排序 希尔排序 归并排序 快速排序 堆排序 ...

  5. 简单排序算法设计(Java)

    总共有八种排序算法,还是慢慢看吧 1.简单排序算法 简单排序算法就是设置标兵,逐个比较数,然后查找插入位置,插入 public static void p(int[] a){for(int i=0;i ...

  6. 《Algorithm算法》笔记:元素排序(2)——希尔排序

    <Algorithm算法>笔记:元素排序(2)--希尔排序 Algorithm算法笔记元素排序2希尔排序 希尔排序思想 为什么是插入排序 h的确定方法 希尔排序的特点 代码 有关排序的介绍 ...

  7. 数据结构与算法之--高级排序:shell排序和快速排序

    高级排序比简单排序要快的多,简单排序的时间复杂度是O(N^2),希尔(shell)排序大约是O(N*(logN)^2),而快速排序是O(N*logN). 说明:下面以int数组的从小到大排序为例. 希 ...

  8. 经典十大排序算法(含升序降序,基数排序含负数排序)【Java版完整代码】【建议收藏系列】

    经典十大排序算法[Java版完整代码] 写在前面的话 十大排序算法对比 冒泡排序 快速排序 直接选择排序 堆排序 归并排序 插入排序 希尔排序 计数排序 桶排序 基数排序 完整测试类 写在前面的话   ...

  9. 九大排序算法Java实现

    之前学习数据结构与算法时花了三天时间整理九大排序算法,并采用Java语言来实现,今天第一次写博客,刚好可以把这些东西从总结的文档中拿出来与大家分享一下,同时作为自己以后的备忘录. 1.排序算法时间复杂 ...

最新文章

  1. IP 管理,几多欣喜几多忧
  2. SQL Server 2005 For XML[学习]
  3. 周五话营销 | 健身房花式卖卡,诠释点击营销流
  4. 2017年第八届蓝桥杯 - 省赛 - C/C++大学A组 - A. 迷宫
  5. python框架Flask学习笔记之get和post请求
  6. 常见的Java开发框架有哪些?
  7. matlab三维矩阵_Matlab绘制三维表面模型说明
  8. 信息系统运行维护服务方案(IT运维服务方案)
  9. UG二次开发-基础篇:GRIP函数查询表与帮助文档
  10. macOS 安装postman 中文语言包
  11. 计蒜客 青出于蓝胜于蓝
  12. Image Segmentation
  13. 做了个网页版的 五笔跟打器: 玫枫跟打器
  14. windows中动态磁盘卷种类介绍
  15. 第50篇-企查查请求头参数分析【2022-09-29】
  16. Encrypted traffic 加密流量分类任务进展综述
  17. 微信公众号开发——基础认识
  18. mysql如何获取今天的日期?
  19. java.lang.NullPointerException: null的错误
  20. 不务正业系列7:老照片去除斑点手法

热门文章

  1. 软件开发中的上游upstream
  2. maven配置本地仓库位置
  3. 软件过程开发模型主要有6种,1瀑布模型,2快速原型模型,3增量模型,4螺旋模型,5喷泉模型,6RUP;瀑布模型和快速原型模型本质上一种( 线性)模型;增量模型风险很大,增量模型本质是一种非整体开发模型
  4. demo3----蚊香制作
  5. “群箱乱舞”的背后,我们从京东叮咚了解到什么?
  6. 线程核酸检测代码——我根救我命
  7. IROS 2021 | PTT:把Transformer应用到3D点云目标跟踪任务
  8. 梯度下降:全梯度下降算法(FG)、随机梯度下降算法(SG)、小批量梯度下降算法(mini-batch)、随机平均梯度下降算法(SAG)。梯度下降法算法比较和进一步优化。
  9. 好多人学日语坚持不下去,有信心测一下你的日语天赋吗
  10. 区块链的技术简史与未来前景,从互联网进化角度分析