算法入门之NB三人组---快速排序、堆排序、归并排序 【python版-详解】

开心

目录

NB三人组

快速排序

快速排序原理:我们设首元素为我们要归位(partition)的元素,partition完(这里指升序,降序反之),左边(left,mid-1)为比mid(首元素归位后的下标即索引)小的数(mid左边的数无序的,但都比mid小),右边(mid+1,right)为比mid大的数(mid右边的数是无序,但都比mid大);再将左边当成一个整体,进行归位,归位完又被分为左部分和右部分… 利用递归,可将列表不断进行归位,实现快速排序。

时间复杂度为O(nlogn),空间复杂度为O(logn),不稳定,代码较复杂

import sys
sys.setrecursionlimit(10000)    # 更改递归深度,默认(999)

def partition(li, left, right):     # 归位函数
    temp = li[left]     # 我们将首元素作为归位目标,实现左边比temp小,右边比temp大。降序反之。
    while left < right:
        while left < right and li[right] >= temp:  # 将后面比temp小的移到前面
            right -= 1
        li[left] = li[right]
        while left < right and li[left] <= temp:   # 将前面比temp大的移到后面
            left += 1
        li[right] = li[left]
    li[left] = temp     # while循环终止,此时left=right,temp归位到left(right),满足左边比temp小,右边比temp大。
    return left


def quick_sort(li, left, right):    # 利用递归的排序函数
    if left < right:    # 进行递归要有条件终止
        print(li)
        mid = partition(li, left, right)
        # 进行递归
        quick_sort(li,left,mid-1)   # 将左边递归归位,实现大小排序
        quick_sort(li,mid+1,right)  # 将右边边递归归位,实现大小排序
    return li


b = [4,5,2,3,1]
quick_sort(b, 0, len(b)-1)
print(b)
"""
将首元素4归位得第2步,[1,3,2]为左边,[5]为右边(此时left=right,归位结束)
[1,3,2] 将首元素1归位得:[]空列表为左边(right<left,归位结束),[3,2]为右边
[3,2] 将首元素3归位为得:[2]为左边(此时left=right,归位结束),[]空列表为右边(right<left,归位结束)
1、 [4, 5, 2, 3, 1]
2、 [1, 3, 2, 4, 5]
3、 [1, 3, 2, 4, 5]
4、 [1, 2, 3, 4, 5]
"""

堆排序

首先我说明一下 写法一 的堆排序的实现过程,方便小伙伴 们理解:我们先了解一下 向下调整函数 sift 的作用:列表li,把他看堆来理解,他是一个 根节点low不是最大,其余元素都满足大根堆型的 大根堆型的完全二叉树(这种说法是错误的,为了方便理解叫大根堆),sift向下调整就是将根节点low 放到他本来该按照大小的堆排序位置,即实现 正确的大根堆型完全二叉树,然后通过循环,将列表(堆)li的各个部分实现大根堆,从小的部分到大的部分,依次实现,最后完成整体实现 大根堆型完全二叉树。总结一下:sift函数将列表li实现为大根堆型完全二叉树;为了满足能省则省,原地排序原则,可将大根堆的根根节点low与最后一个元素调换位置,再进行深度减去1的循环,将堆里面最大的元素放的堆顶,再调换位置,深度减小…即可实现升序的堆排序

堆排序原理:原地排序写法:将列表li建立成大根堆完全二叉树(这是指升序,降序则小根堆),通过循环,将根节点low与最后一个元素n-1调换位置,深度减少,通过sift将最大元素归位到根节点low,再与最后一个元素n-2调换位置,深度减少…最大的元素不断的归位到列表后面,实现原地排序

时间复杂度为O(nlogn),空间复杂度为O(1),不稳定,速度相比快排,归并较慢,十分难写

写法一:原地排序

def sift(li, low, high):
    """
    向下调整函数:列表li是满足 除了根节点low不是最大的 完全二叉树(大根堆型),注:实质上 li的low不是最大的 不可以叫大根堆,这里为了便于理解。
    :param li: 需要调整为(大根堆)完全二叉树的堆(此时只有 堆li 的根节点 不满足最大,即需将low向下调整实现大根堆)
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的下标
    :return: 根节点low向下调整所实现的  大根堆型的完全二叉树
    """
    i = low
    j = 2*i + 1     # 开始时 j 为一个左孩子
    temp = li[low]  # 将根节点值存起来,方便将low实现向下调整
    while j <= high:
        if j+1 <= high and li[j] < li[j+1]:  # 如果右孩子存在(即在high深度以内)且比左孩子大,那么将左孩子指向右孩子,实现将大的元素移想对顶
            j = j + 1  # 不可将左孩子和右孩子的值互换,因为右孩子的子孩子有可能比左孩子大,这样会破坏大根堆原则
        if li[j] > temp:
            li[i] = li[j]
            i = j
            j = 2*i +1  # 将 i(low) 往下一层深度
        else:   # 存在比low小的父亲节点,那么将low存放
            li[i] = temp
            break
    else:   # j已经超出最大堆深度high,while结束,将low存放在最深深度,因为此时low比父亲节点都小了
        li[i] = temp


def heap_sort(li):
    n = len(li)
    # 建立 大根堆型的完全二叉树
    for i in range((n-1-1)//2, -1, -1):  # i表示调整的时候,调整的部分 的堆的根节点下标
        sift(li, i, n-1)  # n-1 可以作为 各个堆的深度
    # 通过循环,减小深度,将堆的最大值放到堆深度的外面(仍然在li的范围)实现原地排序,能省则省原则
    for i in range(n-1, -1, -1):
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i-1)    # i-1为新的深度high,将新的堆再次向下调整,实现将最大的元素放到堆顶


import random

b = list(range(1, 10))
random.shuffle(b)
print(b)
heap_sort(b)
print(b)

写法二:利用内置函数heapq


import heapq
import random

b = list(range(1, 15))
random.shuffle(b)
print(b)
heapq.heapify(b)    # heapify 建堆

b_new = []
for i in range(len(b)):
    b_new.append(heapq.heappop(b))  # heappop 会每次抛出堆里面的最小值
print(b_new)
"""
输出结果
[14, 3, 12, 11, 1, 7, 6, 10, 13, 9, 8, 4, 5, 2]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
"""

归并排序

归并排序原理:我们假设mid为列表的某一索引值,mid左边的元素都是有序的,mid右边的元素都是有序的,那我们只需将左边和右边的元素依次比较大小(我们这里左升序排序,降序反之),循环将小的存入新的列表中,循环结束时,新列表就是我们所预期的升序排序。上述的前提是mid左右两边都是有序的,我们可以通过递归实现mid左右两边的有序。

时间复杂度为O(nlogn),空间复杂度为O(n),稳定,代码较复杂

def merge(li, low, mid, high):  # mid左边为有序元素,mid右边为有序元素
    i = low
    j = mid + 1
    temp = []   # 创建一个新列表
    while i <= mid and j <= high:   # i,j不能越界,将较小的数依次append进temp
        if li[i] > li[j]:
            temp.append(li[j])
            j += 1
        else:
            temp.append(li[i])
            i += 1
    # 上述while执行完,两个部分肯定有一个部分为空,越界了
    while i <= mid:     # 若左部分仍有数即 i<=mid且(j>high越界了),那么将左部分剩下的有序数依次append进temp,此时temp为升序列表
        temp.append(li[i])
        i += 1
    while j <= high:    # 此时左部分为空即越界了,将右部分剩下的有序数append进temp
        temp.append(li[j])
        j += 1
    li[low:high+1] = temp


def merge_sort(li, low, high):
    mid = (low+high)//2
    if low < high:	# 递归结束条件
        merge_sort(li, low, mid)
        merge_sort(li, mid+1, high)
        merge(li, low, mid, high)


b = [5,3,4,6,2,1,7]
merge_sort(b, 0, len(b)-1)
print(b)

为了方便大家理解递归的过程,我把他整理了出来。‘’纸上得来终觉浅,绝知此事要躬行‘’,同时也是建议还没理解的同学自己拿起笔运算一下递归的过程,很值得回味!

下面是归并排序的递归过程,以 b = [5,3,4,6,2,1,7] 为例

在这里插入图片描述

若您对上述三种排序代码思路仍模糊,请您前往b站的**python版快速、堆、归并排序视频讲解**,我认为该视频讲的挺好的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值