zoukankan      html  css  js  c++  java
  • 插入排序、交换排序、选择排序、归并排序、基数排序

    插入排序

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

    1.直接插入排序

    在解说直接插入排序之前,先让我们脑补一下我们打牌的过程。

    先拿一张5在手里,再摸到一张2,比5小,插到5前面,摸到一张6,嗯,比5大,插到5后面,摸到一张3,插到2和5之间,。。。

    最后一看,我靠,凑到全是同花顺。这下牛逼大了。 


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



    时间复杂度

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

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

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

    空间复杂度

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

    算法稳定性

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

    举例:


    2.折半插入排序

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

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

    3.希尔排序

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

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



    O(Nlog2N)=O(N1.3)

    算法稳定性

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


    交换排序

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

    1.冒泡排序

    置。将n个记录看作纵向排列。每趟排列时自下向上对每对相邻记录进行比較,若次序不符合要求(逆序)就交换。

    每趟排序结束时都能使排序范围内keyword最小的记录想一个气泡一样升到表上端相应位置,整个排序过程进行n-1趟,依次将keyword最小、次小、第三小...的各个记录“冒到”表的第一个、第二个、第三个...位置上。

      初态      第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的前面。


    时间复杂度

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


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

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

    空间复杂度

    高速排序在每次切割的过程中。须要 1 个空间存储基准值。而高速排序的大概须要 Nlog2N次 的切割处理,所以占用空间也是 Nlog2N 

    算法稳定性

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


    选择排序

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

    依据选出元素的方式又分为简单选择排序和堆排序。

    简单选择排序是通过简单地数组遍历方案确定最大(或最小)元素。

    而堆排序是利用堆这样的数据结构的性质,通过堆元素的删除、调整等一系列操作选出最大(或最小)元素。

    1.简单排序处理流程: 

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

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


    时间复杂度

    简单选择排序的比較次数与序列的初始排序无关。

    如果待排序的序列有 N 个元素。则比較次数总是N (N - 1) / 2

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

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


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

    空间复杂度

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


    算法稳定性

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

    2.堆排序

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

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


    当中每一个结点的keyword都不大于其孩子结点的keyword。这种堆称为小根堆

     

    当中每一个结点的keyword都不小于其孩子结点的keyword,这种堆称为大根堆


    举例来说,对于n个元素的序列{R0, R1, ... , Rn}当且仅当满足下列关系之中的一个时,称之为堆:
          (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 } 来加以说明。




    算法稳定性

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

    由于在堆的调整过程中。keyword进行比較和交换所走的是该结点到叶子结点的一条路径,

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

    归并排序

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

    二路归并算法

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

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

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


    时间复杂度

    归并排序的形式就是一棵二叉树。它须要遍历的次数就是二叉树的深度。而依据全然二叉树的能够得出它的时间复杂度是O(n*log2n)。


    空间复杂度

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


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


    基数排序

    基数排序与前面解说的七种排序方法都不同,它不须要比較keyword的大小它是依据keyword中各位的值,通过对排序的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个暂时空间。

    算法稳定性

    在基数排序过程中,每次都是将当前位数上同样数值的元素统一“装桶”,并不须要交换位置。

    所以基数排序是稳定的算法。







  • 相关阅读:
    解决ios下的微信页面背景音乐无法自动播放问题
    vue Vue-cli 笔记
    document.documentElement和document.body区别介绍
    GD库使用小结---2
    GD库使用小结---1
    踩到两只“bug”
    CI加载流程小结
    文件加载---理解一个project的第一步
    缓存的使用
    小小的分页
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7133294.html
Copyright © 2011-2022 走看看