python算法与数据结构:08排序算法

  • Post author:
  • Post category:python


在这里插入图片描述

稳定性定义:相同的值在排序结束之后的相对次序不变。



1. 冒泡排序

每一轮相邻两个值之间比较,大小顺序相反的交换位置,最后的值总是最大。

稳定性:稳定

def BubbleSort(alist):
    """冒泡排序 """
    #最坏时间复杂度= O(n^2)
     #用count控制如某一层从头到尾的比较中没有做任何交换,说明顺序已经正确,不用继续比,可使最优复杂度变为 O(n)
    n = len(alist)
    for i in range(n-1) # 外层控制一共走多少次       
        count = 0 
        for j in range(n-1-i):
            # 内层控制从头走到尾
            if alist[j] > alist[j+1]:#因为没有等号,所以相邻值相等时不改变次序,是稳定的算法
                alist[j],alist[j+1] = alist[j+1],alist[j]
            	count += 1
        if count == 0:
            return


if __name__ == "__main__":
    li = [32,54,12,45,23,13,67,12,43]
    print(li)
    BubbleSort(li)
    print(li)



2. 选择排序

思路:认为左边有序右边无序。将右边无序的数值中找到最小的,第二小的依次与左边的交换位置。

稳定性:考虑从小到大排列,第一个元素是10,被最小值调换之后就排到列表中的10后面.所以不稳定

def SelectSort(alist):
    """选择排序""" # 最优和最坏时间复杂度都是O(n^2) 不稳定
    n = len(alist)
    for i in range(n-1):
        min_index = i
        for j in range(i+1,n):
            if alist[j] < alist[min_index]:
                min_index = j
        alist[i],alist[min_index] = alist[min_index],alist[i]

if __name__ == "__main__":
    li = [32,54,12,45,23,13,67,12,43]
    print(li)
    SelectSort(li)
    print(li)



3. 插入排序

假设左边有序,右边无序。

先拿出第一个元素,每一轮将右边剩下的元素与左边比较,依次往前挪直到前面一个比他小

  • 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
  • 最坏时间复杂度:O(n^2) (升序排列,序列处于降序状态)
  • 稳定性:稳定
# def InsertSort(alist):
#     n = len(alist)
#     for i in range(1,n):
#         j = i  # j代表内层循环起始值
#         while j > 0:
#             if alist[j] < alist[j-1]:
#                alist[j],alist[j-1] = alist[j-1],alist[j]
#                j -= 1
#             else:
#                 break
""" 等价2个for循环:"""
def InsertSort(alist):
    n = len(alist)
    for i in range(n-1):
        for j in range(i+1,0,-1):
            if alist[j] < alist[j-1]: #因为没有等号,所以相邻值相等时不改变次序,是稳定的算法
                alist[j], alist[j-1] = alist[j-1], alist[j]

if __name__ == "__main__":
    li = [32,11,54,129,45,23,13,67,12,43]
    print(li)
    InsertSort(li)
    print(li)



4. 希尔排序

是不需要递归的简单算法中执行效率较高的排序算法。属于插入排序的优化算法。

稳定性:不稳定

在这里插入图片描述

def ShellSort(alist):
    """希尔排序"""
    n = len(alist)
    gap = n // 2
    while gap >= 1:
        for i in range(gap, n):
            j = i
            while j > 0:
                if alist[j] < alist[j-gap]:
                    alist[j], alist[j-gap] = alist[j-gap], alist[j]
                    j = j -gap
                else:
                    break
        gap = gap // 2

if __name__ == "__main__":
    li = [32,11,54,129,45,23,13,67,12,43]
    print(li)
    ShellSort(li)
    print(li)



5. 快速排序

  • 最优时间复杂度:O(nlogn)

  • 最坏时间复杂度:O(n^2)

  • 稳定性:不稳定

  • 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列.

  • 把第一个元素拿出来,设为中间元素mid,两头各一个游标,各自和中间元素mid比较,右边边必须比mid大,否则和左边游标处空出来的位置交换,并且换左边移动,满足条件则不动,继续往中间移动。指定左右两个游标都移动到中间,这时两边分别递归继续快速排序。

def QuickSort(alist,start,end):
    """快速排序"""
    #start和end是不会变的开头和末尾元素,left和right会移动
    if start >= end:
        return
    mid = alist[start]
    left = start
    right = end

    while left < right:
        while left < right and alist[right] >= mid:
            right -= 1
        alist[left] = alist[right]

        while left < right and alist[left] < mid:
            left += 1
        alist[right] = alist[left]

    alist[left] = mid
    QuickSort(alist,start,left-1)
    QuickSort(alist, left+1, end)

if __name__ == "__main__":
    li = [32,11,3,129,45,9,13,67,12,43]
    print(li)
    QuickSort(li,0,len(li)-1)
    print(li)

快排时间复杂度计算:

一共递归了logN次,每次O(N), 一共(NlogN)

最坏情况:有序数组,每次只搞定一个数O(N^2)

问题:与数据状况有关,如果分偏了,对复杂度有影响。



6. 归并排序

先将数组分成两组,各自用递归的方法排好序,然后用left和right两个游标各自指向第一个元素,哪个小就添加到最后排序数组,并且把游标向右移动一个。

def MergeSort(alist):
    n = len(alist)
    mid = n // 2
    if 1 == n:
        return alist

    #对左伴部分进行归并排序
    left_li = MergeSort(alist[:mid]) #T(N/2)
    #对右伴部分进行归并排序
    right_li = MergeSort(alist[mid:])#T(N/2)
    #左神解法:T(N)=2T(N/2)+O(N) 所以根据master公式 复杂度是NlogN
    #只考虑单个分的情况是对半,不考虑一共对半了多少次
    
    #合并两个有序集合
    left, right = 0, 0
    result = []
    while left < len(left_li) and right < len(right_li):
        # 此处等号意味着左右相等的2个值,左边还是先添加进来,保证稳定性
        if left_li[left] <= right_li[right]: 
            result.append(left_li[left])
            left += 1
        else:
            result.append(right_li[right])
            right += 1

    
    #下俩其实只执行了一个
    result += left_li[left:]
    result += right_li[right:]

    return result

if __name__ == "__main__":
    li = [32,11,3,129,45,9,13,67,12,43]
    print(li)
    sorted_li=MergeSort(li)
    print(sorted_li)



7. 根排序

不稳定

def HeapSort(alist):
    """堆排序"""
    if alist ==[] or len(alist) < 2:
        return alist

    for i in range(len(alist)):
        heapInsert(alist, i) #建立大根堆结构
    heapSize = len(alist)
    alist[0], alist[heapSize - 1] = alist[heapSize - 1], alist[0] #大根堆首尾交换
    heapSize -= 1 #去掉最末尾的节点(也就是此时大根堆的最大值)
    while heapSize > 0:
        heapify(alist, 0, heapSize)
        alist[0], alist[heapSize - 1] = alist[heapSize - 1], alist[0]
        heapSize -= 1
    return alist


def heapInsert(alist, index):
    """建立大根堆结构: 当前节点比他的父节点大的时候,交换,跳到index的父节点的位置,再往上跟他的父节点比较,直到整个完全形成大根堆的结构"""
    """ 大根堆:每一个子树的根节点都是这个树里的最大值 """
    # 考虑父节点不能小于0
    while alist[index] > alist[(index - 1) // 2] and ((index - 1) // 2 >= 0): 
        alist[index], alist[(index - 1) // 2] = alist[(index - 1) // 2], alist[index]
        index = (index - 1) // 2 #跳到index的父节点的位置


#判断根节点的数字是不是最大,不是就一直往下沉,直到重新恢复到大根堆的结构
def heapify(alist, index, heapSize):
    left = index * 2 + 1
    while left < heapSize:
        # 取到左右两个叶节点的最大值
        largest = left + 1 if left + 1 < heapSize and alist[left + 1] > alist[left] else left  
        # 将左右两个叶节点的最大值跟其父节点比较取最大值
        largest = largest if alist[largest] > alist[index] else index 
        # 等价于上一行代码中 alist[largest] <= alist[index] 父节点比两个子节点大或相等
        if largest == index:  
            break             # 不需再往下沉了
        # largest != index 父亲往下沉
        alist[largest], alist[index] = alist[index], alist[largest] 
        index = largest #父亲index变成了他较大孩子的下标
        left = index * 2 + 1

if __name__ == "__main__":
    li = [32,11,54,129,45,23,13,67,12,43]
    print(li)
    HeapSort(li)
    print(li)



8.桶排序

def maxGap(alist):
    """不是桶排序 给定一个数组,求如果排序之后,相邻两数的最大差值,
    要求时间复杂度O(N),且要求不能用非基于比较的排序。"""
    if alist is None or len(alist) < 2:
        return 0

    n = len(alist)
    minNum = min(alist)
    maxNum = max(alist)

    if minNum == maxNum:
        return 0

    bucketBool = [False] * (n + 1)
    bucketMax = [0] * (n + 1)
    bucketMin = [0] * (n + 1)

    for i in range(n):
        bid = bucket(alist[i], n, minNum, maxNum)  # 得出来自几号桶
        bucketMin[bid] = min(bucketMin[bid], alist[i]) if bucketBool[bid] else alist[i]
        bucketMax[bid] = max(bucketMax[bid], alist[i]) if bucketBool[bid] else alist[i]
        bucketBool[bid] = True
    res = 0
    lastMax = bucketMax[0]
    # 找到每一个非空桶和离他最近的左边的非空桶,用当前最小减前一个桶的最大
    for i in range(n + 1):
        if bucketBool[i]:
            res = max(res, bucketMin[i] - lastMax)
            lastMax = bucketMax[i]
    return res

#确定这个数来自几号桶 n:桶的个数
def bucket(num, n, minNum, maxNum):
    return int((num - minNum) * n / (maxNum - minNum))



时间复杂度和稳定性比较:

在这里插入图片描述

稳定的:冒泡排序,插入排序,归并排序

不稳定的:选择排序,希尔排序,堆排序,快速排序



排序算法的选择:

基本数据类型(int,double,char,float)相同值无差异,不用在意稳定性:

快速排序

自定义字段,在意稳定性:

归并排序

不管什么类型,数组长度很短(长度<60)代码极其简单,忽略常数项,样本量很低的情况下:

插排

O(n^2)

参考:

王道考研算法与数据结构



版权声明:本文为lvqiqi93原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。