排序算法的Python实现

排序算法的Python实现

1. 冒泡排序

多次遍历列表。

比较相邻元素,将不和顺序的交换。

每一轮遍历都将下一个最大值放在正确的位置上(如末尾)。

冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

def bubbleSort(alist): # 升序排列
    for passnum in range(len(alist) - 1, 0, -1):
        for i in range(passnum):
            if alist[i] < alist[i+1]: # exchange
                temp = alist[i]
                alist[i] = alist[i+1]
                alist[i+1] = temp

冒泡排序通常被认为是效率最低的排序算法,因为在确定最终的位置前必须交换元素。“多余”的交换操作代价很大。不过,由于冒泡排序要遍历列表中未排序的部分,因此它具有其他排序算法没有的用途。特别是,如果在一轮遍历中没有发生元素交换,就可以确定列表已经有序。可以修改冒泡排序函数,使其在遇到这种情况时提前终止。对于只需要遍历几次的列表,冒泡排序可能有优势,因为它能判断出有序列表并终止排序过程。这种排序通常被称作短冒泡

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
                temp = alist[i]
                alist[i] = alist[i+1]
                alist[i+1] = temp
        passnum = passnum - 1

2. 选择排序

每次遍历列表时只做一次交换。

在每次遍历时寻找最大值,并在遍历完之后将它放到正确位置上(如末尾)。

选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

相比于冒泡排序算法,选择排序算法通常更快。

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
        
        temp = alist[fillslot]
        alist[fillslot] = alist[positionOfMax]
        alist[positionOfMax] = temp

3. 插入排序

在列表较低的一端维护一个有序的子列表,并逐个将每个新元素按照大小“插入”这个子列表。

插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

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 = position - 1

        alist[position] = currentvalue

4. 希尔排序

也称为“递减增量排序”。

对插入排序进行了改进,将列表分成数个子列表,并对每一个子列表应用插入排序。

关键是如何切分:使用步长i选取所有间隔为i的元素组成子列表。

列表已经由增量的插入排序做了预处理,所以最后一步插入排序不需要进行多次比较或移动。也就是说,每一轮遍历都生成了“更有序”的列表,这使得最后一步非常高效。

希尔排序的时间复杂度介于 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) 之间。通过改变增量,比如采用 2 k − 1 2^k-1 2k1 的步长,希尔排序的时间复杂度可以达到 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23)

def shellSort(alist):
    sublistcount = len(alist) // 2 # 步长为列表长度的1/2
    while sublistcount > 0:
        for startposition in range(sublistcount):
            gapInsertionSort(alist, startposition, sublistcount)
        
        print("After increments of size", sublistcount, "The list is", alist)

        sublistcount = sublistcount // 2

def gapInsertionSort(alist, start, gap):
    for i in range(start + gap, len(alist), gap):

        currentvalue = alist[i]
        position = i

        while position >= gap and alist[position - gap] > currentvalue:
            alist[position] = alist[position - gap]
            position = position - gap
        
        alist[position] = currentvalue
>>> alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
>>> shellSort(alist)
After increments of size 4 The list is [20, 26, 44, 17, 54, 31, 93, 55, 77]
After increments of size 2 The list is [20, 17, 44, 26, 54, 31, 77, 55, 93]
After increments of size 1 The list is [17, 20, 26, 31, 44, 54, 55, 77, 93]

5. 归并排序

使用分治策略改进排序算法。

递归:基本情况为列表为空或者只有一个元素,则认为列表是有序的。分治策略:每次将列表一分为二,并对两部分都递归调用归并排序,当两部分都有序后就进行归并(将两个小的有序列表并为一个大的有序列表)。

归并排序算法的时间复杂度为 O ( n log ⁡ ( n ) ) O(n\log(n)) O(nlog(n))

但是,mergeSort() 函数需要额外的空间来存储切片操作得到的两半部分。

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

        # 归并 —— 每次从有序列表中取出最小值,放回初始列表alist
        i = 0
        j = 0
        k = 0
        while i < len(lefthalf) and j < len(righthalf):
            if lefthalf[i] < righthalf[j]:
                alist[k] = lefthalf[i]
                i = i + 1
            else:
                alist[k] = righthalf[j]
                j = j + 1
            k = k + 1

        while i < len(lefthalf):
            alist[k] = lefthalf[i]
            i = i + 1
            k = k + 1
        
        while j < len(righthalf):
            alist[k] = righthalf[j]
            j = j + 1
            k = k + 1
    
    print("Merging ", alist)
>>> b = [54, 26, 93, 17, 77, 31, 44, 55, 20]
>>> mergeSort(b)
Splitting  [54, 26, 93, 17, 77, 31, 44, 55, 20]
Splitting  [54, 26, 93, 17]
Splitting  [54, 26]
Splitting  [54]
Merging  [54]
Splitting  [26]
Merging  [26]
Merging  [26, 54]
Splitting  [93, 17]
Splitting  [93]
Merging  [93]
Splitting  [17]
Merging  [17]
Merging  [17, 93]
Merging  [17, 26, 54, 93]
Splitting  [77, 31, 44, 55, 20]
Splitting  [77, 31]
Splitting  [77]
Merging  [77]
Splitting  [31]
Merging  [31]
Merging  [31, 77]
Splitting  [44, 55, 20]
Splitting  [44]
Merging  [44]
Splitting  [55, 20]
Splitting  [55]
Merging  [55]
Splitting  [20]
Merging  [20]
Merging  [20, 55]
Merging  [20, 44, 55]
Merging  [20, 31, 44, 55, 77]
Merging  [17, 20, 26, 31, 44, 54, 55, 77, 93]

6. 快速排序

使用分治策略。但不需要使用额外空间。

首先选出一个基准值(比如第一个元素),帮助切分列表。在最终的有序列表中,基准值的位置通常被称作分割点。算法在分割点切分列表,以进行对快速排序的子调用。

下一步是分区操作。首先找到两个目标:leftmark和rightmark,它们分别位于列表剩余元素的开头和末尾。首先加大leftmark,直到遇到一个大于基准值的元素;然后减小rightmark,直到遇到一个小于基准值的元素。互换这两个元素的位置,然后重复上述过程。

当rightmark小于leftmark时,过程终止。此时rightmark的位置就是分割点。将基准值与当前分割点的位置互换,即可使基准值位于正确位置。此时,分割点左边的元素都小于基准值,右边的元素都大于基准值。

接着,在分割点处将列表一分为二,针对左右两部分进行递归调用快速排序函数。

对于长度为 n n n 的列表,如果分区操作总是发生在列表的中部,就会切分 log ⁡ ( n ) \log(n) log(n) 次,则时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn) ;如果切分得极不均匀,如分割点在列表的某一端,则时间复杂度变为 O ( n 2 ) O(n^2) O(n2)

可以采用三数取中法来避免切分不均匀。即在选择基准值时考虑列表的头元素、中间元素与尾元素。这种方法的思路是,如果头元素的正确位置不在列表中部附近,那么三元素的中间值将更靠近中部。

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 = leftmark + 1
        while rightmark >= leftmark and alist[rightmark] >= pivotvalue:
            rightmark = rightmark - 1

        if rightmark < leftmark:
            done = True
        else:
            temp = alist[leftmark]
            alist[leftmark] = alist[rightmark]
            alist[rightmark] = temp
        
    temp = alist[first]
    alist[first] = alist[rightmark]
    alist[rightmark] = temp

    return rightmark
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值