内排序笔记摘要

        今天简单回顾了一下内排序的内容,感觉有些认识在以前比较模糊,所以又去重温了一下,写在这里希望和大家分享一下,第一次写作,不好的地方请不吝指正。

   首先是三类简单的排序方法,分别是冒泡排序、直接选择排序、插入排序,分别是稳定的、不稳定的、稳定的。在这些基本的实现上有一些优化的方法,比如冒泡排序可以用一个变量来记录某一轮冒泡中是否发生过交换,如果没有的话就可以直接结束排序过程,在有序情况下是很快的(这不是理所应当的的吗);直接选择排序比较经典的优化是二分查找,虽然减少了比较的次数,但是移动还是不可避免地,所以这些算法的优化并没有降低复杂度的上界。

  接着谈一下略显诡异的shell排序吧,这个算法的想法时用一个严格递减的delta间隔量把整个序列变成若干的子序列,对子序列插入排序,最后delta = 1也就是整个数组的插入排序(为什么要插入排序这么多次),实际上这个算法还是比较有意思的,因为插入排序在一个序列基于有序的时候代价比较小,所以是一个很好的排序方法,同时不难证明shell排序的正确性。有一些很奇葩的序列选择方法,我从wiki上找了一些资料以供参考:

    先贴一下wiki的伪代码

# Sort an array a[0...n-1].
gaps = [701, 301, 132, 57, 23, 10, 4, 1]

# Start with the largest gap and work down to a gap of 1
foreach (gap in gaps)
{
    # Do a gapped insertion sort for this gap size.
    # The first gap elements a[0..gap-1] are already in gapped order
    # keep adding one more element until the entire array is gap sorted
    for (i = gap; i < n; i += 1)
    {
        # add a[i] to the elements that have been gap sorted
        # save a[i] in temp and make a hole at position i
        temp = a[i]
        # shift earlier gap-sorted elements up until the correct location for a[i] is found
        for (j = i; j >= gap and a[j - gap] > temp; j -= gap)
        {
            a[j] = a[j - gap]
        }
        # put temp (the original a[i]) in its correct location
        a[j] = temp
    }
}

       wiki里有这样的一张表(只能说一句太强了!):

  

       接着是比较经典的快速排序和归并排序了。

  先讲归并排序吧,主流实现是用到了O(n)的辅助空间(之前还有题目问用O(1)的辅助空间怎么实现来着。。。),先在原来的序列一分为二,再二分为四,直到小于某个你设定的阈值,然后就不断从底往上归并,在归并的时候,我们可以把要归并的两段统统放入辅助空间里面,然后再一次比较要归并的两段的首元素,从辅助空间里把元素摆到原向量的合理位置。另一种小技巧是在两段的最后都加一个哨兵(充分大),这样子就避免了之后某一段空了需要单独加上另一段的操作(总之无伤大雅,看个人喜好了)。为什么要先讲归并排序呢,因为这个排序除了辅助空间的消耗之外,整个想法十分朴实,又非常稳定有效,给人一种非常踏实的感觉(属于个人看法了)。小注一下,在归并算法中可以嵌入逆序对的计算,从而使得整个序列的逆序对数量的计算复杂度在O(nlogn)里,顺序对和逆序对本身就有数量关系,所以在已知后者的前提下,前者也就立刻知道了。

  再谈快速排序,快速排序不是浪得虚名的,实验表明这个算法可以说是内排序的老大哥。和归并排序一样都有分治的想法,但是它按自己的方式去分治,也就是先选择一个轴值(比如说待排序列的第一个元素,或者随机之类的),然后扫描一遍序列,把轴值放在合理的位置,使得左边的元素都比它小,右边的都比它大。所以这个时候左右的长度不是固定的,但是在统计意义下可以算出平均复杂度是O(nlogn),这里还有用到调和级数和一些数列的小技巧,不赘述了,不过还是有点意思的,在计算二叉树的平均深度时也有用。

  至于桶排序、基于桶排序的基数排序以及索引排序就留到下次讲把,感觉细节比较多,但是不是很难理解。

  最后是对基于比较和交换的排序方法的复杂度的估计,可以用一颗判定树在进行估计,有n!个叶子结点,则高度大概是log(n!),斯特林公式可以看出比较次数就是在O(nlogn)这个量级。

  感谢阅读。

posted @ 2019-12-20 16:36  一笑正中双飞翼  阅读(386)  评论(1编辑  收藏  举报