排序算法-堆排序-归并排序-快排-冒泡排序-选择排序-希尔排序

特别鸣谢:来自夸夸群的 醉笑陪公看落花@知乎王不懂不懂@知乎QFIUNE@csdn
感谢醉笑陪公看落花@知乎 倾囊相授,感谢小伙伴们督促学习,一起进步

推荐一个有动图解释的博客:十大排序算法-快排-希尔-堆排-归并-冒泡-桶排-选择-插入-计数-基数-1


tips

  • 稳定排序 (相同元素排序之后相对先后不变)
    • 冒泡排序
    • 归并排序
    • 插入排序
  • 不稳定的排序
    • 快排排序
    • 选择排序
    • 希尔排序
      在这里插入图片描述

上图来源 https://www.cnblogs.com/xiaochun126/p/5086037.html

下面介绍集中排序算法的实现,从小到大排序

归并排序 O(n*log2(n))

  • 分:把列表一分为二,得到两个有序列表
  • 治:合并两个有序列表

递归地拆分列表,当拆分出来的列表只含有单个元素的时候,自然是有序的,可以作为递归的终止条件

在这里插入图片描述

'''
归并排序
'''
def mergeSort(nums):
    if len(nums)==1:
        return nums
    s = len(nums)//2
    sa,sb = mergeSort(nums[:s]),mergeSort(nums[s:])
    ans = doMerge(sa,sb)
    return ans
def doMerge(sa,sb):
    ans = []
    i = j = 0
    while(i<len(sa) and j <len(sb)):
        if sa[i]<=sb[j]:  # 等号保证排序稳定(元素相等的时候,sb中的元素插在后面)
            elem = sa[i]
            i+=1
        else:
            elem = sb[j]
            j+=1
        ans.append(elem)
    ans.extend(sa[i:])
    ans.extend(sb[j:])
    return ans

堆排序

堆的性质

堆的实现通过构造二叉堆(binary
heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质。

  • 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
  • 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

来源维基百科 堆积 性质

由此可知,大根堆的子树也是大根堆

用数组存一棵完全二叉树,通过下标获取某个已知节点的关联节点

若数组下标从0开始

  1. 已知父节点为i, 则左孩子为:2i+1,右孩子为 2i+2
  2. 已知孩子节点为j,则父节点为 (j-1)//2

若数组下标从1开始

  1. 已知父节点为i, 则左孩子为:2i,右孩子为 2i+1
  2. 已知孩子节点为j,则父节点为 j//2

在这里插入图片描述

本文大根堆数组下标都从0开始

方案1 自底向上调整大根堆O(n^2)

从最后一个非叶结点开始调整,让这个节点比后裔大,一直到root节点比后裔大,再将root节点放在末尾。 这样,最大的数就排在了数组的末尾。再把倒数第二大的数字,放在数组倒数第二的位置,依此类推。

'''
堆排序 -- 自底向上调整
目前时间复杂度:O(n^2)
'''
def heapSort(nums):
    for end in range(len(nums)-1,-1,-1):
        doheapSort(nums,end)
    return nums

def doheapSort(nums,end):
    root = (end-1) // 2  # 最后一个非叶节点的下标
    while(root>=0):
        left = root*2+1
        right = root*2+2
        if nums[left]>nums[root]:
            nums[left], nums[root] = nums[root],nums[left]
        if nums[right] > nums[root] and right<=end:
            nums[right], nums[root] = nums[root], nums[right]
        root-=1
    # root -1 # 倒数第二个非叶节点的下标
    nums[0], nums[end] = nums[end], nums[0]

方案2 自顶向下调整大根堆O(n*log2(n))

在这里插入图片描述

先构建一个大根堆(每一个节点都大于等于后裔),把root和数组末尾的值交换,再自顶向下调整(末尾这个数字已经是确保是最大值,因此下一次大根堆的数组不包括原来数组基础上的末尾元素)

'''
O(n*log2(n))
'''
def heapSort(nums):  # O(n * log2(n))
    generateBigheap(nums) # O(n * log2(n))
    for end in range(len(nums)-1,-1,-1): # O(n * log2(n))
        nums[0], nums[end] = nums[end], nums[0]
        down_adjust(nums,0,end-1)
    return nums

def generateBigheap(nums): # O(n * log2(n))
    for root in range((len(nums)-2)//2,-1,-1):
        left = root*2+1
        right = left+1
        maxelem = left if right>len(nums)-1 or nums[right]<nums[left] else right
        if nums[root]>nums[maxelem]:continue
        nums[maxelem], nums[root] = nums[root], nums[maxelem]
        down_adjust(nums, maxelem,len(nums)-1)
def down_adjust(nums,root,end): # O(log2(n))
    while root * 2 + 1 <end:
        left = root * 2 + 1
        right = left + 1
        maxelem = left if right>end or nums[right]<nums[left] else right
        if nums[root] > nums[maxelem]: break
        nums[maxelem], nums[root] = nums[root], nums[maxelem]
        root = maxelem

递归函数 down_adjust(nums,root,end) 可以修改成非递归函数

def downAdjust(nums,root,end):
    if root * 2 +1<=end:
        left = root * 2 +1
        right = left +1
        maxelem = left if right>end or nums[left]>nums[right] else right
        if nums[root]>nums[maxelem]:return
        nums[root],nums[maxelem] = nums[maxelem],nums[root]
        downAdjust(nums,maxelem,end)

进一步优化生成大顶堆的代码

def generateBigheap(nums):
    end = len(nums)-1
    for root in range((end-1)//2,-1,-1):
        downAdjust(nums,root,end)

快排 [O(n*log2(n)) ~ O(n^2)]

主要用到了基准元素和双指针,以及递归思想

  • 双指针移动和 leetcode 11,42,407, 容器,接雨水,二维雨水,坑 文中对墙的移动相似。本文中每次数组被修改位置的指针,向着另一个指针的方向挪动1位
  • 双指针每一次碰头的时候,基准元素到达了最终位置。草图中展示了双指针一次碰头的过程。
  • 找到了最终位置的基准元素将列表一分为二,下一步需要递归地去找下一个基准元素的位置,直到找到所有的基准元素

最坏情况是 原数组从大到小排序时,基准元素无法把列表一分为二(拿到的基准元素是最小的或者最大的)

快排草图

在这里插入图片描述
在这里插入图片描述

如果基准元素总是能把上一个数组分成2份,就是最好情况,时间复杂度O(n*log2(n))
如果基准元素总是无法把上一个数组分成2份,就是最差情况,时间复杂度O(n*n)

def quickSort(nums,start,end):
    i = start
    j = end
    if start>=end:return
    base = i
    b = nums[base]
    while i<j:
        while nums[j] >= b and i<j:j-=1
        nums[i] = nums[j]
        while nums[i]<=b and i<j:i+=1
        nums[j] = nums[i]
    nums[i] = b
    quickSort(nums,start,j-1)
    quickSort(nums,j+1,end)

ans = quickSort(nums,0,len(nums)-1)

冒泡排序

每次都从第一个元素开始,相邻元素进行交换,大的元素会被交换到相对靠后面的位置,最大的元素在末尾。如果把这个过程称做冒泡的话,第i次冒泡就是把第i大的元素放在了正确的位置上。

如果某一次冒泡,未发生一次交换,说明数组已经排好序了。

下面是完成一次冒泡,最大的元素放到了最后面
在这里插入图片描述

def bubbleSort(nums):
    for i in range(len(nums)-1):
        flag = False
        for j in range(len(nums)-i-1):
            if nums[j]>nums[j+1]:
                nums[j],nums[j+1] = nums[j+1],nums[j]
                flag = True
        if not flag:break
    return nums

选择排序

每次选择第i小的元素,放在i号位置

def selectSort(nums):
    for i in range(len(nums)-1):
        mi = i
        for j in range(i,len(nums)):
            if nums[mi]>nums[j]:
                mi = j
        nums[i],nums[mi] = nums[mi],nums[i]
    return nums

插入排序

把数组分为有序[0,i]和无序部分[i:],把第i个待插入的元素,从i-1处开始比较直到比较到0,决定插入有序部分[0,i]某个位置
在这里插入图片描述

def insertSort(nums):
    for i in range(1,len(nums)): # 第i个待插入的元素,可选择的位置 [0,i]
        for j in range(i,0,-1):
            if nums[j]<nums[j-1]:
                nums[j-1],nums[j] = nums[j],nums[j-1]
            else:break
    return nums

希尔排序

gap(n) 产生增量列表(从大到小,最好是互质,最小是1),以增量为gap,步长为1,进行滑窗并交换元素值

在这里插入图片描述

def shellSort(nums):
    for s in gap(len(nums)):
        for i in range(len(nums)-s):
            if nums[i]>nums[i+s]:nums[i],nums[i+s] = nums[i+s],nums[i]
def gap(n):
    gaps = []
    i = 1
    while pow(2,i)-1<n:
        gaps.append(pow(2,i)-1)
        i+=1
    gaps.reverse()
    return gaps
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值