【算法】排序算法小结(上)

        排序算法是算法中的基础,通常是解决其他算法的第一步。排序是将一组对象按照某种逻辑顺序重新排列的过程,本文将经典的排序算法根据《算法第四版》进行整理。以备复习使用。最后,结合leetcode经典题目,进行应用。另外值得注意的是,排序算法有内排序和外排序之分,在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。外排序一般来说外排序分为两个步骤:预处理和合并排序。我们常提到的以及下面提到的都是内排序。

        首先贴上汇总图,选择题必备。

        想记下来也很简单,时间复杂度除了堆排序、归并排序和基数,剩余时间复杂度均为O(n^2),从省时间角度考虑以上三种;从空间角度,除了快速排序和基数排序,剩余均为O(1)。剩下的问题,比如最好情况和最坏情况是相同的有直接选择、堆排序和归并排序最好情况下时间复杂度最大的是直接选择排序等等。

                            

        接下来,对于每一种进行详细的说明与实现。

一、选择排序

        算法描述:首先,找到数组中最小的元素,其次将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么就和自己交换);再次,在剩下的元素中找到一个最小的元素,将它和数组的第二个元素交换位置。如此往复,直至将整个数组排序。称之为选择排序,是因为它在不断地选择剩余元素之中的最小者。

        复杂度分析:长度为N的数组,0-N-1之间的任意 i 都会进行一次交换和 N-1-i 次比较,因此总的交换次数为 N, 总的比较次数为 (N-1) + (N-2) + (N-3) + ...... +2 + 1=N*(N-1)/2 ~ N^2/2。因而时间复杂度为O(N^2),最好、最坏或平均均相等。无需额外空间。对于稳定性,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

        示例

                                          

def selection(nums):
    n = len(nums)
    for i in range(n):
        mins = i
        for j in range(i+1,n):
            if nums[mins] > nums[j]:
                mins = j
        tmp = nums[i]
        nums[i] = nums[mins]
        nums[mins] = tmp
    return nums

二、冒泡排序(基于比较)

        算法描述:重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列)。

        复杂度分析:若初始状态是正序的,一趟扫描即可完成排序。所需的关键字比较次数C和记录移动次数M均达到最小值,Cmin=N-1, Mmin= 0, 冒泡排序最好的时间复杂度为O(1)。若初始状态是反序的,需要进行 N-1 趟排序。每趟排序要进行 N-i 次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:

                                          ,            

        因而,时间复杂度最差和平均均为 O(N^2) 。同样,无需额外空间。

       对于稳定性来说,冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

        示例

                                                                    

def blob(nums):
    "sort in ascending order"
    n = len(nums)
    for i in range(n,-1,-1):
        for j in range(i-1):
            if nums[j]>nums[j+1]:
                tmp = nums[j]
                nums[j] = nums[j+1]
                nums[j+1] = tmp
    return nums
        

三、插入排序

        算法描述:将新元素插入其他已经有序的序列中的适当位置。为了给要插入的元素腾出空间,需要将其余的元素在插入之前都向右移动一位。与选择排序相同,当前索引左边的所有元素均为有序的,但不代表最终位置,为了给更小的元素腾出空间,它们可能会被移动。但是当索引到达数组的最右端时,数组排序就完成了。

        复杂度分析:插入排序的时间复杂度取决于输入中元素的初始顺序。插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需 n-1 次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有 n(n-1)/2 次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。            无需额外的空间。

        插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

        适用性:对于一些部分有序的数组,例如:数组中每个元素距离它的最终位置都不远;一个有序的大数组接一个小数组;数组中只有几个元素的位置不正确等大多数有序的情况,插入排序十分有效。

        示例:

                                                           

def Insertion(nums):
    n = len(nums)
    for i in range(1,n):
        for j in range(i,0,-1):
            if nums[j]<nums[j-1]:
                tmp = nums[j]
                nums[j] = nums[j-1]
                nums[j-1] = tmp
    return nums

四、希尔排序

        算法描述:对于大规模乱序数组,插入排序很慢,因为它只会交换相邻的元素,效率十分低。为了加快速度,进行了改进,主要思想是使数组中任意间隔为h的元素都是有序的。这样的数组称为 h 有序数组。如果h很大,就可以将元素移动到很远的地方。具体来说,先取一个小于长度的整数 h1 作为第一个增量,把数组的全部记录分组。所有距离为h1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量ht=1( ht< ht-1< …<h2<h1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法.。比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。

        希尔排序的高效是因为它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后是部分有序的,这两种情况都很适合插入排序。值得注意的是,子数组部分有序的程度取决于递增序列的选择,但具体的原理仍未研究透彻。在递增序列的选择上,可以选取初次取序列的一半为增量,以后每次减半,直到增量为1。也可以使用每次为之前1/3,例如序列 h = h*3+1 (1,4,13,40,121,264,1093....), h<N/3 等。

        复杂度分析:时间复杂度依赖于增量序列的选择,不同的序列,时间复杂度不同。可以在O(N^(1.2—2))之间。平均约为O(N^1.5)。

        稳定性上,由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

        示例:

def shell(nums):
    n = len(nums)
    h = 1
    while h<n/3:
        h = h*3+1

    while h>=1:
        for i in range(h,n):
            j = i
            while j>=h and nums[j]<nums[j-h]:
                nums[j-h],nums[j] = nums[j],nums[j-h]
                j -= h
        h //= 3
    return nums

        未完待续。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值