查看原文
其他

程序员进阶需要掌握的几大排序算法

strongerHuang 2022-05-20
关注、星标公众,不错过精彩内容

编排 | strongerHuang
微信公众号:strongerHuang

如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功。


想写出精炼、优秀的代码,不通过不断的锤炼,是很难做到的。


一、排序算法

排序算法作为数据结构的重要部分,系统地学习一下是很有必要的。


1、排序的概念

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。


排序分为内部排序和外部排序。


若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。

反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。


2、排序分类

八大排序算法均属于内部排序。如果按照策略来分类,大致可分为:交换排序、插入排序、选择排序、归并排序和基数排序。如下图所示:


3、算法分析
A.插入排序
  *直接插入排序
  *希尔排序
B.选择排序
  *简单选择排序
  *堆排序
C.交换排序
  *冒泡排序
  *快速排序
D.归并排序
E.基数排序
不稳定排序:简单选择排序,快速排序,希尔排序,堆排序
稳定排序:冒泡排序,直接插入排序,归并排序,基数排序


进一步描述八大排序:

①插入排序

将第一个和第二个元素排好序,然后将第3个元素插入到已经排好序的元素中,依次类推(插入排序最好的情况就是数组已经有序了)。


②希尔排序

因为插入排序每次只能操作一个元素,效率低。元素个数N,取奇数k=N/2,将下标差值为k的数分为一组(一组元素个数看总元素个数决定),在组内构成有序序列,再取k=k/2,将下标差值为k的数分为一组,构成有序序列,直到k=1,然后再进行直接插入排序。


③简单选择排序

选出最小的数和第一个数交换,再在剩余的数中又选择最小的和第二个数交换,依次类推。


④堆排序

以升序排序为例,利用小根堆的性质(堆顶元素最小)不断输出最小元素,直到堆中没有元素

1.构建小根堆

2.输出堆顶元素

3.将堆低元素放一个到堆顶,再重新构造成小根堆,再输出堆顶元素,以此类推。


⑤冒泡排序

改进1:如果某次冒泡不存在数据交换,则说明已经排序好了,可以直接退出排序

改进2:头尾进行冒泡,每次把最大的沉底,最小的浮上去,两边往中间靠1


⑥快速排序

选择一个基准元素,比基准元素小的放基准元素的前面,比基准元素大的放基准元素的后面,这种动作叫分区,每次分区都把一个数列分成了两部分,每次分区都使得一个数字有序,然后将基准元素前面部分和后面部分继续分区,一直分区直到分区的区间中只有一个元素的时候,一个元素的序列肯定是有序的嘛,所以最后一个升序的序列就完成啦。


⑦归并排序

将一个无序的数列一直一分为二,直到分到序列中只有一个数的时候,这个序列肯定是有序的,因为只有一个数,然后将两个只含有一个数字的序列合并为含有两个数字的有序序列,这样一直进行下去,最后就变成了一个大的有序数列


基数排序

找到最大的数,开个比最大的数大一点的数组,遍历每个元素,某个元素为k,则a[k]++,最好遍历数组a,a[k]等于多少就输出多少个k。只能处理整型数。


三、详解八大排序算法

1.插入排序

直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。


因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:

第一层循环:遍历待比较的所有数组元素。

第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。如果:selected > ordered,那么将二者交换。


2.希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。


希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。


算法步骤:

a.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;


b.按增量序列个数k,对序列进行k 趟排序;


c.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。


3.简单选择排序

简单选择排序的实现思想:比较+交换


从待排序序列中,找到关键字最小的元素;


如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;


从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。因此我们可以发现,简单选择排序也是通过两层循环实现。第一层循环:依次遍历序列当中的每一个元素 第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。


4.堆排序

堆:本质是一种数组对象。特别重要的一点性质:任意的叶子节点小于(或大于)它所有的父节点。对此,又分为大顶堆和小顶堆:

大顶堆要求节点的元素都要大于其孩子。

小顶堆要求节点元素都小于其左右孩子。

两者对左右孩子的大小关系不做任何要求。


利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。


基本思想:堆排序可以按照以下步骤来完成:

a.首先将序列构建称为大顶堆;(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)。


b.取出当前大顶堆的根节点,将其与序列末尾元素进行交换;(此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)。


c.对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质。


d.重复2.3步骤,直至堆中只有1个元素为止。


5.冒泡排序

算法思想:冒泡遍历所有的数据,每次对相邻元素进行两两比较,如果顺序和预先规定的顺序不一致,则进行位置交换;这样一次遍历会将最大或最小的数据上浮到顶端,之后再重复同样的操作,直到所有的数据有序。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端。


6.快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n logn)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来


快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。


算法步骤:

a.从数列中挑出一个元素,称为 “基准”(pivot)。


b.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。


c.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。


递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。


7.归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

算法步骤:
a.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

b.设定两个指针,最初位置分别为两个已经排序序列的起始位置;

c.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

d.重复步骤3直到某一指针达到序列尾;

e.将另一序列剩下的所有元素直接复制到合并序列尾。

8.基数排序

基数排序:通过序列中各个元素的值,对排序的N个元素进行若干趟的“分配”与“收集”来实现排序。


分配:我们将L[i]中的元素取出,首先确定其个位上的数字,根据该数字分配到与之序号相同的桶中 。


收集:当序列中所有的元素都分配到对应的桶中,再按照顺序依次将桶中的元素收集形成新的一个待排序列L[ ] 。


对新形成的序列L[ ]重复执行分配和收集元素中的十位、百位...直到分配完该序列中的最高位,则排序结束。


好了,本文就分享到这里,本文主要讲解原理,不同编程语言实现方法不同,后面有机会再给大家分享具体实现方法。


------------ END ------------

推荐阅读:
C语言实现面向对象的原理
C/C++代码规范注释有哪些讲究?
Embedded Studio中使用ST-Link调试教程

微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。


长按前往图中包含的公众号关注


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存