数据结构与算法分析(五) -- 排序

本系列为《Python数据结构与算法分析》第二版学习笔记,作者:布拉德利.米勒;戴维.拉努姆。译:吕能, 刁寿钧

排序是指将集合中的元素按某种顺序排列的过程。与搜索算法类似,排序算法的效率与待处理元素的数目相关。

1、冒泡排序

冒泡排序多次遍历列表,它比较相邻的元素,将不和顺序的交换,每一轮遍历都将下一个最大值放在正确的位置上。
特别的,如果在一轮遍历中没有发生元素交换,就可以确定列表已经有序,此时可以终止遍历。

冒泡排序函数

def shortBubbleSort(alist):
    exchanges = True
    passnum = len(alist) - 1
    while passnum > 0 and exchanges:
        exchanges = False
        for i in range(passnum):
            if alist[i] > alist[i+1]:
            	exchanges = True
                alist[i], alist[i + 1] = alist[i + 1], alist[i]
        passnum -= 1
    return alist

给定有n个元素的列表排序总共需要遍历n-1遍,则总的比较次数为:
在这里插入图片描述
所以,冒泡排序的时间复杂度为O(n2)。

2、选择排序

选择排序在冒泡排序的基础上做了改进,每次遍历列表只做一次交换。每次遍历时寻找最大值,并在遍历完之后将它放在正确的位置上。和冒泡排序一样,第一次遍历后,最大的元素就位,第二次遍历后,第二大的元素就位,依此类推。
选择排序函数:

def selectionSort(alist):
    for fillslot in range(len(alist)-1, 0, -1):
        positionOfMax = 0
        for location in range(1, fillslot+1):
            if alist[location] > alist[positionOfMax]:
                positionOfMax = location

        alist[fillslot], alist[positionOfMax] = alist[positionOfMax], alist[fillslot]
    return alist

选择排序的时间复杂度为O(n2)。

3、插入排序

插入排序在列表较低的一端维护一个有序的子列表,并逐个将每个新元素插入这个子列表。
插入排序函数:

def insertionSort(alist):
    for index in range(1, len(alist)):

        currentvalue = alist[index]
        position = index

        while position > 0 and alist[position-1] > currentvalue:
            alist[position] = alist[position-1]
            position -= 1

        alist[position] = currentvalue
    return alist

在最坏的情况下,插入排序算法的比较次数是前n-1个整数之和,对应的时间复杂度是O(n2)。在基准测试中,插入排序的性能很不错。

4、希尔排序

希尔排序也称“递减增量排序”,它对插入排序做了改进,将列表分成数个子列表,并对每一个子列表插入排序。
希尔排序函数

def shellSort(alist):
    sublistcount = len(alist) // 2
    while sublistcount > 0:
        for startpositon in range(sublistcount):
            gapInsertionSort(alist, startpositon, sublistcount)
        print("After increments of size", sublistcount, "The list is", alist)
        sublistcount = sublistcount // 2
    
    return alist

希尔排序的时间复杂度大概介于O(n)和O(n2)之间,通过改变增量,比如采用2k-1,希尔排序的时间复杂度可以达到O(n3/2)。

5、归并排序

归并排序是一种递归算法,每次将一个列表一分为二。如果列表为空或只有一个元素,那么从定以上来说它就是有序的。如果列表不止一个元素,就将列表一分为二,并对两部分都递归调用归并排序,当两部分都有序后,就进行归并这一基本操作。归并是指将两个较小的有序列表归并为一个有序列表的过程

归并排序函数

def mergeSort(alist):
    if len(alist) > 1:
        mid = len(alist) // 2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]

        mergeSort(lefthalf)
        mergeSort(righthalf)

        i = 0
        j = 0
        k = 0
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k] = lefthalf[i]
                i += 1
            else:
                alist[k] = righthalf[j]
                j += 1
            k += 1

        while i < len(lefthalf):
            alist[k] = lefthalf[i]
            i += 1
            k += 1

        while j < len(righthalf):
            alist[k] = righthalf[j]
            j += 1
            k += 1
            
    return alist

归并排序的时间复杂度是O(nlogn),mergeSort函数需要额外的空间来存储切片操作得到的两半部分。当列表较大时,使用额外的空间可能会使排序出现问题。

6、快速排序

和归并排序一样,快速排序也采用分治策略,但不适用额外的存储空间。不过,代价是列表可能不会被一分为二,这时,算法的效率会有所降低。

def quickSort(alist):
    quickSortHelper(alist, 0, len(alist)-1)
    
    
def quickSortHelper(alist, first, last):
    if first < last:
        splitpoint = partition(alist, first, last)
        quickSortHelper(alist, first, splitpoint-1)
        quickSortHelper(alist, splitpoint+1, last)
        
        
def partition(alist, first, last):
    pivotvalue = alist[first]
    
    leftmark = first + 1
    rightmark = last
    done = False
    
    while not done:
        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
            leftmark += 1
        
        while alist[rightmark] >= pivotvalue and rightmark >= leftmark:
            rightmark -= 1
            
        if rightmark < leftmark:
            done = True
        else:
            alist[leftmark], alist[rightmark] = alist[rightmark], alist[leftmark]
    
    alist[first], alist[rightmark] = alist[rightmark], alist[first]
    
    return rightmark

对于长度为n的列表,如果划分操作总是在列表的中部,就会切分logn次。为了找到分割点,n个元素都要与基准值比较。所以,时间复杂度是O(nlogn)。

另外,最坏情况下,分割点不在列表的中部,而是偏向某一端,这会导致切分不均匀。这种情况下,含有n个元素的列表可能被分成一个不含元素的列表与一个含有n-1个元素的列表,然后,含有n-1个元素的列表可能被分成不含元素的列表与一个含有n-2个元素的列表,依次类推,这会导致时间复杂度变为O(n2),因为还要加上递归的开销。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值