zoukankan      html  css  js  c++  java
  • 内排序笔记摘要

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

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

      接着谈一下略显诡异的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)这个量级。

      感谢阅读。

  • 相关阅读:
    LeetCode 264. Ugly Number II
    LeetCode 231. Power of Two
    LeetCode 263. Ugly Number
    LeetCode 136. Single Number
    LeetCode 69. Sqrt(x)
    LeetCode 66. Plus One
    LeetCode 70. Climbing Stairs
    LeetCode 628. Maximum Product of Three Numbers
    Leetcode 13. Roman to Integer
    大二暑假周进度报告03
  • 原文地址:https://www.cnblogs.com/zyna/p/12073872.html
Copyright © 2011-2022 走看看