插入排序、交换排序、选择排序、归并排序、基数排序

插入排序

每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,知道全部插入完成。 不同插入排序算法的最根本的不同点是根据什么规则寻找新元素的插入点,直接插入排序采用依次寻找,而折半插入采用的是折半寻找。

1.直接插入排序

在讲解直接插入排序之前,先让我们脑补一下我们打牌的过程。先拿一张5在手里,再摸到一张2,比5小,插到5前面,摸到一张6,嗯,比5大,插到5后面,摸到一张3,插到2和5之间,。。。最后一看,我靠,凑到全是同花顺,这下牛逼大了。 

以上的过程,其实就是典型的 简单插入排序 每次将一个新数据插入到有序队列中的合适位置里

时间复杂度

当数据正序时,执行效率最好,每次插入都不用移动前面的元素,时间复杂度为O(N)

当数据反序时,执行效率最差,每次插入都要前面的元素后移,时间复杂度为O(N2)

所以,数据越接近正序,直接插入排序的算法性能越好。 

空间复杂度

由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为 1 。

算法稳定性

直接插入排序的过程中,不需要改变相等数值元素的位置,所以它是稳定的算法。

举例:


2.折半插入排序

时间复杂度折半插入排序比直接插入排序明显减少了关键字之间的比较次数,但是移动次数是没有改变。所以,折半插入排序和插入排序的时间复杂度相同都是O(N2)),在减少了比较次数方面它确实相当优秀,所以该算法仍然比直接插入排序好。

空间复杂度   折半插入排序和插入排序一样只需要一个多余的缓存数据单元来放第 i 个元素,所以空间复杂度是O(1),因为排序前2个相等的数在序列的前后位置顺序和排序后它们两个的前后位置顺序相同,所以它是一个稳定排序。 

3.希尔排序

希尔(Shell)排序又称为缩小增量排序,它是一种插入排序

希尔排序的基本思想是:把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。



O(Nlog2N)=O(N1.3)

算法稳定性

由上文的希尔排序算法演示图即可知,希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。

交换排序

交换排序的基本思想是:亮亮比较待排序元素的关键字,发现两个元素的次序相反时即进行交换,知道没有反序的元素位置。

1.冒泡排序

置。将n个记录看作纵向排列,每趟排列时自下向上对每对相邻记录进行比较,若次序不符合要求(逆序)就交换。每趟排序结束时都能使排序范围内关键字最小的记录想一个气泡一样升到表上端对应位置,整个排序过程进行n-1趟,依次将关键字最小、次小、第三小...的各个记录“冒到”表的第一个、第二个、第三个...位置上。

  初态      第1趟   第2趟   第3趟    第4趟   第5趟    第6趟   第7趟
    38        12      12      12      12      12      12      12                              
    20        38      20      20      20      20      20      20
    46        20      38      25      25      25      25      25
    38        46      25      38      38      38      38      38
    74        38      46      38      38      38      38      38
    91        74      38      46      46      46      46      46
    12        91      74      74      74      74      74      74
    25        25      91      91      91      91      91      91

public void BubbleSort(int [] array,int n)
{
    
    int i=0; 
    int j=0; 
    int temp=0;
    int flag = 0;
    for(i=0;i<n - 1 ;i++)   /*外循环控制排序的总趟数*/
    {
        flag = 0;   /*本趟排序开始前,交换标志应为假*/
       for(j=n-1;j > i;j--) /*内循环控制一趟排序的进行*/ 
       {
           if(array[j] < array[j-1] ) /*相邻元素进行比较,若逆序就交换*/
           {
             temp =array[j];
             array[j] = array[j-1];
             array[j-1] = temp;
             flag = 1;                  /*发生了交换,故将交换标志置为真*/
           }
           
       }
        if (flag == 0)  /*本趟排序未发生交换,提前终止算法*/
           break;
           }
}



算法稳定性

冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。

所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法

2.快速排序

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数

然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

  假设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。一躺快速排序的算法是:

   1)、设置两个变量I、J,排序开始的时候I:=1,J:=N;

   2)以第一个数组元素作为关键数据,赋值给X,即X:=A[1];

   3)、从J开始向前搜索,即由后开始向前搜索(J:=J-1),找到第一个小于X的值,两者交换;

   4)、从I开始向后搜索,即由前开始向后搜索(I:=I+1),找到第一个大于X的值,两者交换;

   5)、重复第3、4步,直到I=J;


此时再执行第三不的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是:27        38       13       49       76       97        65,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。


时间复杂度

当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。

而当数据随机分布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数接近相等,此时执行效率最好。

所以,数据越随机分布时,快速排序性能越好;数据越接近有序,快速排序性能越差

空间复杂度

快速排序在每次分割的过程中,需要 1 个空间存储基准值。而快速排序的大概需要 Nlog 2 N次 的分割处理,所以占用空间也是  Nlog2N  

算法稳定性

在快速排序中,相等元素可能会因为分区而交换顺序,所以它是不稳定的算法。

选择排序

基本思路:每一趟从待排序的元素序列中选出关键字最大(或最小)的元素,按顺序放在已排序的元素序列的最后面(或最前面),直到全部排完为止。

根据选出元素的方式又分为简单选择排序和堆排序。简单选择排序是通过简单地数组遍历方案确定最大(或最小)元素。而堆排序是利用堆这种数据结构的性质,通过堆元素的删除、调整等一系列操作选出最大(或最小)元素。

1.简单排序处理流程: 

( 1 )从待排序序列中,找到关键字最小的元素;
( 2 )如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
( 3 )从余下的 N - 1 个元素中,找出关键字最小的元素,重复( 1 )、( 2 )步,直到排序结束。

红色粗体表示位置发生变化的颜色,每趟排序中,将当前第 i 小的元素放在位置 i 上。


时间复杂度

简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,则比较次数总是N (N - 1) / 2

而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0.

当序列反序时,移动次数最多,为3N (N - 1) /  2。

所以,综合以上,简单排序的时间复杂度为 O(N2)。 

空间复杂度

简单选择排序需要占用一个临时空间,在交换数值时使用。

算法稳定性

简单选择排序算法是一个不稳定的排序方法。

2.堆排序

在介绍堆排序之前,首先需要说明一下,堆是个什么玩意儿。

是一棵顺序存储完全二叉树

其中每个结点的关键字都 不大于其孩子结点的关键字,这样的堆称为 小根堆。 

其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大根堆

举例来说,对于n个元素的序列{R 0, R 1, ... , R n}当且仅当满足下列关系之一时,称之为堆:
      (1) Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
      (2) Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)

其中i=1,2,…,n/2向下取整; 


如上图所示,序列R{3, 8, 15, 31, 25}是一个典型的小根堆。
堆中有两个父结点,元素3和元素8。
元素3在数组中以R[0]表示,它的左孩子结点是R[1],右孩子结点是R[2]。
元素8在数组中以R[1]表示,它的左孩子结点是R[3],右孩子结点是R[4],它的父结点是R[0]。
可以看出,它们 满足以下规律
设当前元素在数组中以 R[i]表示,那么,
(1) 它的 左孩子结点是: R[2*i+1];
(2) 它的 右孩子结点是: R[2*i+2];
(3) 它的 父结点是: R[(i-1)/2];
(4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。

堆排序的基本思想

首先,按堆的定义将数组R[0..n]调整为堆(这个过程称为创建初始堆),交换R[0]和R[n];

然后,将R[0..n-1]调整为堆,交换R[0]和R[n-1];

如此反复,直到交换了R[0]和R[1]为止。

以上思想可归纳为两个操作:

(1)根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。

(2)每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下元素重新调整为大根堆。 

当输出完最后一个元素后,这个数组已经是按照从小到大的顺序排列了。

先通过详细的实例图来看一下,如何构建初始堆。设有一个无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }。



构造了初始堆后,我们来看一下完整的堆排序处理:

还是针对前面提到的无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。




算法稳定性

堆排序是一种不稳定的排序方法。

因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,

因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。

归并排序

归并排序是通过“归并”这种操作完成排序的目的,将两个或多个有序子表归并成一个子表。若将两个有序表合并成一个有序表,称为二路归并

二路归并算法

归并排序其实要做两件事:

(1)“分解”——将序列每次折半划分

(2)“合并”——将划分后的序列段两两合并后排序


时间复杂度

归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(n*log2n)。

空间复杂度

由前面的算法说明可知,算法处理过程中,需要一个大小为n的临时存储空间用以保存合并序列。

算法稳定性
在归并排序中,相等的元素的顺序不会改变,所以它是稳定的算法。

基数排序

基数排序与前面讲解的七种排序方法都不同,它不需要比较关键字的大小它是根据关键字中各位的值,通过对排序的N个元素进行若干趟“分配”与“收集”来实现排序的。 不妨通过一个具体的实例来展示一下,基数排序是如何进行的。 

基数排序有两种:最低位优先(LSD)

设有一个初始序列为: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。 我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以0~9来表示的。 所以我们不妨把0~9视为10个桶。 

我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是0,将这个数存入编号为0的桶中。


分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来。

这时,得到的序列就是个位数上呈递增趋势的序列。 

按照个位数排序: {50, 30, 0, 100, 11, 2, 123, 543, 187, 49}。

接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列


时间复杂度

通过上文可知,假设在基数排序中,r为基数,d为位数。则基数排序的时间复杂度为O(d(n+r))

我们可以看出,基数排序的效率和初始序列是否有序没有关联。

空间复杂度

在基数排序过程中,对于任何位数上的基数进行“装桶”操作时,都需要n+r个临时空间。

算法稳定性

在基数排序过程中,每次都是将当前位数上相同数值的元素统一“装桶”,并不需要交换位置。所以基数排序是稳定的算法。







  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值