数据结构中排序方法2(附python代码)

希尔排序

     希尔排序是D.L.shell于1959年提出来的排序算法,在这之前(见排序1),排序算法的时间复杂度基本都是O(n^2),希尔排序是突破这个时间复杂度的第一批算法之一。

   基本原理

     上面讲的插入排序,在记录本身比较有序或者当记录较少时,比较高效。而希尔排序就是先使记录基本有序然后再对全体记录进行一次插入排序。所谓基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2,1,3,6,4,7,5,8,9}这样的就可以称为基本有序了。但像{1,5,9,3,7,8,2,4,6}这样的9在第三位,2在倒数第三位就谈不上基本有序

   python代码

def shell_sort(arr):
    count = len(arr)
    step = 2
    group = int(count / step)
    while group > 0:
        for i in range(0,group):
            j = i + group
            while j < count:
                k = j - group
                key = arr[j]
                while k >= 0:
                    if arr[k] > key:
                        arr[k+group] = arr[k]
                        arr[k] = key
                    k -= group
                j += group
        group  = int(group/step)
    return arr

   希尔排序算法

    考虑到是记录基本有序,可以采用增量比较法,arr[i]先不直接与arr[i+1]比较,而是与arr[i+increment]比较,这个increment就称之为增量,在python代码中用group表示。假设初始记录arr为{9,1,5,8,3,7,4,6,2},初始增量group=4

      i = 0时,比较arr[0]=9与arr[4]=3的大小,因为arr[0]>arr[4],所以交换9,3位置,记录为{3,1,5,8,9,7,4,6,2}

同时比较arr[4]>arr[8],故交换,arr[0]>arr[4],交换,得到记录{2,1,5,8,3,7,4,6,9}

      i= 1时,比较arr[1]=1与arr[5]=7的大小,前者小于后者,不交换

      i= 2时,比较arr[2]=5与arr[6]=4的大小,前者大于后者,故交换,记录为{2,1,4,8,9,7,5,6,9}

      i= 3时,比较arr[3]=8与arr[7]=6的大小,前者大于后者,故交换,记录为{2,1,4,6,3,7,5,8,9}

     此时序列已经达到基本有序,接下来减少增量group=2,循环一轮后,再次缩小group=1,便可以使记录达到有序

    希尔排序复杂度分析

      经过上面剖析,发现希尔排序是将相隔某个"增量"的记录组成一个子序列,实现移动式的跳跃,使得效率升高,那么增量的选择就十分重要,当增量序列为2^(t+k+1)-1时,可以获得不错效果,时间复杂度是O(n^(3/2)),优于直接排序,另外希尔排序并不是一种稳定的排序算法


     堆排序

    之前的简单选择排序,选取最小的记录需要比较n-1次,可惜这样的操作并没有把每一趟的结果都保存下来,后面的比较中又执行了一次,因而记录的比较次数较多。如果能做到在每次选择最小记录的同时,根据比较结果对其他记录做出相应调整,那么总体的效率就会非常高了。

      基本概念

    堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子结点的值,称为大顶堆(下图左);或每个节点的值都小于或等于其左右孩子结点的值,称为小顶堆(下图右)


      堆排序算法

     将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造一个堆,这样就会得到n个元素中次小值。如此反复执行,便能得到一个有序序列了。

     python代码

def heap_sort(lst):
    for start in range((len(lst) - 2) // 2, -1, -1):
        siftdown(lst, start, len(lst) - 1)
    for end in range(len(lst) - 1, 0, -1):
        lst[end], lst[0] = lst[0], lst[end]
        siftdown(lst, 0, end - 1)
    return lst


def siftdown(lst, start, end):
    root = start
    while True:
        child = 2 * root + 1
        if child > end: break
        if child + 1 <= end and lst[child] < lst[child + 1]:
            child += 1
        if lst[child] > lst[root]:
            lst[child], lst[root] = lst[root], lst[child]
            root = child
        else:
            break
heap_sort(l)


归并排序(Merging Sort)

   基本思想: 利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或1的有序子序列;再两两归并,......,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

   排序过程


                                                                                                                              图来自《大话数据结构》

   python代码

def merge_sort(arr):
    if len(arr) <= 1: #子序列
        return arr
    mid = (len(arr) // 2)
    left = merge_sort(arr[:mid])#递归的切片操作
    right = merge_sort(arr[mid:len(arr)])
    result = []

    while len(left) > 0 and len(right) > 0:
        if (left[0] <= right[0]):
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
            #j+= 1

    if (len(left) > 0):
        result.extend(merge_sort(left))
    else:
        result.extend(merge_sort(right))
    return result

merge_sort(a)

  复杂度分析

    一趟归并需要将记录中相邻的长度为h的有序序列进行两两归并,这需要将待排序序列中的所有记录扫描一遍,因此需要耗费

O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行logn次,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。空间复杂度为O(n+logn)

   归并排序不存跳跃,因此算是一种稳定的排序算法

     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章