排序之牛逼三人组及其python代码实现

排序

快速排序 时间复杂度:O(nlogn)

思路

总体思路

将第一个元素并使之归位(使用partition函数), 随后列表被分为两部分且左边均比该元素小,右边均比该元素大,随后左右两边均进行递归

partition函数

从列表left处开始找比tmp值小的元素,找到后放入空位置,
随后从列表right处开始找比tmp值大的元素,找到后放入空位置直到left=right

代码实现

partition函数

每次找到后输出一次列表,最终right与left相等时再输出一次列表

def partition(li,left,right): #left right表示列表需要操作的范围
    tmp = li[left]
    while left < right:
        while left < right and li[right] >= tmp: #列表right处开始找比tmp值小的元素
            right -= 1
        li[left] = li[right]
        print(li, 'r')
        while left < right and li[left] <= tmp:
            left += 1
        li[right] = li[left]
        print(li, 'l')
    li[left] = tmp #循环结束时 left和right相等
    return left

li = [5,7,4,6,3,1,2,9,8]
print('原列表:',li)
print(partition(li,0,len(li)-1))

在这里插入图片描述

主函数
def quick_sort(li,left,right):
    if left < right:
        mid = partition(li,left,right)
        quick_sort(li,left,mid-1)

        quick_sort(li,mid+1,right)
    return li

li = [5, 7, 4, 6, 3, 1, 2, 9, 8]
print(quick_sort(li,0,len(li)-1))

在这里插入图片描述

堆排序 时间复杂度:O(nlogn)

相关知识点

二叉树:度不超过2的树
满二叉树:一个每一层结点数达到最大的二叉树
完全二叉树:满二叉树最后一层从右到左减少叶子结点
图a满二叉树 图b完全二叉树
在这里插入图片描述

大根堆:每一个父节点都比其孩子结点大
小根堆:每一个父节点都比其孩子结点小

思路

首先化成大根堆,需要用到向下调整函数(除去根节点外是两个大根堆,此函数可以把根节点放入组成一个包含所有元素的大根堆)
在这里插入图片描述
然后把根节点(9)拿出,将最后一个元素(3)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将3放入
在这里插入图片描述
然后把根节点(8)拿出,将最后一个元素(3)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将3放入
在这里插入图片描述
然后把根节点(7)拿出,将最后一个元素(2)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将2放入
在这里插入图片描述
然后把根节点(6)拿出,将最后一个元素(1)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将1放入
在这里插入图片描述
然后把根节点(5)拿出,将最后一个元素(0)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
在这里插入图片描述然后把根节点(4)拿出,将最后一个元素(0)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
在这里插入图片描述
然后把根节点(3)拿出,将最后一个元素(1)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将1放入
在这里插入图片描述
然后把根节点(2)拿出,将最后一个元素(0)放到根节点的位置
在这里插入图片描述
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
在这里插入图片描述
然后拿出1,结束

向下调整函数
代码实现

把2进行向下调整
在这里插入图片描述

def sift(li,low,high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high:  堆的最后一个元素的位置
    :return:
    """

    i = low #i最开始指向根节点
    j = 2 * i + 1# j开始指向左孩子
    tmp = li[low]
    while j <= high: #确保j位置有数
        if j+1 <= high and li[j+1] > li[j]:  #确保右孩子存在且比所对应的左孩子大
            j = j+1 #j指向右孩子
        if li[j] > tmp:
            li[i] = li[j] #如果父节点小,li[j]变为父节点,然后开启下一层
            i = j
            j = 2 * i + 1
        else:
            li[i] = tmp  #tmp已经是最大的 则直接变为父节点
            break
    else:
        li[i] = tmp  #如果j已经>high 直接结束
    return li

li = [2,9,7,8,5,0,1,6,4,3]
print(sift(li,0,len(li)-1))

在这里插入图片描述

主函数
思路

首先从最后一个元素及其父节点开始,最后一个元素下标为n-1,则其父节点为(i-1)//2 不论是左节点还是右节点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
依次下去

代码实现
def heap_sort(li):
    n = len(li)
    for i in range((n-2)//2,-1,-1):  #第i个节点对应的父节点为(i-1)//2 不论是左节点还是右节点
        sift(li,i,n-1)
    #完成建堆
    for i in range(n-1,-1,-1):
        li[0],li[i] = li[i],li[0]  #high对应的元素与堆顶元素交换
        sift(li,0,i-1) #i-1是新的high
    return li

li = [2,9,7,8,5,0,1,6,4,3]
print(heap_sort(li))

在这里插入图片描述

python内置函数

python存在关于堆的内置模块

import heapq
import random

li = list(range(10))
random.shuffle(li)
print('原列表为:',li)

heapq.heapify(li)  #建堆
print('小根堆为:',li)  #默认建的是小根堆

n = len(li)
print(heapq.heappop(li))  #每次抛出一个最小的
print('排序列表为:')
for i in range(n-1):
    print(heapq.heappop(li),end=',')

在这里插入图片描述

堆排序之topk问题

问题描述

现有n个数,设计算法得到前k大的数(k<n)

思路

首先取前k个元素建立一个小根堆,此时堆顶就是目前第k大的数,然后依次向后遍历原列表,对于每个元素,如果小于堆顶,则忽略,若大于,则将堆顶更换成该元素,并且对堆进行一次调整。
原列表取前k个元素建立一个小根堆
在这里插入图片描述
在这里插入图片描述
对于0,0<1,忽略
对于7,7>1,代替堆顶1并进行一次向下调整
在这里插入图片描述
对于2,2>3,忽略
对于4,5>3,代替堆顶3并进行一次向下调整
在这里插入图片描述
对于5,5>4,代替堆顶4并进行一次向下调整,结束
在这里插入图片描述

代码实现
#建立小根堆
def sift(li,low,high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high:  堆的最后一个元素的位置
    :return:
    """

    i = low #i最开始指向根节点
    j = 2 * i + 1# j开始指向左孩子
    tmp = li[low]
    while j <= high: #确保j位置有数
        if j+1 <= high and li[j+1] < li[j]:  #确保右孩子存在且比所对应的左孩子大
            j = j+1 #j指向右孩子
        if li[j] < tmp:
            li[i] = li[j] #如果父节点小,li[j]变为根节点,然后开启下一层
            i = j
            j = 2 * i + 1
        else:
            li[i] = tmp  #tmp已经是最大的 则直接变为父节点
            break
    else:
        li[i] = tmp  #如果j已经>high 直接结束
    return li

def topk(li,k):
    heap = li[0:k]
    for i in range((k-2)//2,-1,-1):
        sift(li,i,k-1)
    #1.完成建堆
    for i in range(k,len(li)):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap,0,k-1)
    #2.遍历列表后面的所有数
    for i in range(k-1,-1,-1):
        heap[0],heap[i] = heap[i],heap[0]
        sift(heap,0,i-1)
    #3.出数
    return heap

import random

li = list(range(10))
random.shuffle(li)
print('原列表为:',li)
print('前5大的数为:',topk(li,5))

在这里插入图片描述

归并排序 时间复杂度:O(nlogn) 空间复杂度:O(n)

总的思路

百度上的一张图就可以解释
在这里插入图片描述

归并

思路

首先说明归并的思想,即一个列表中前后两段均为排好序的序列:
3,6,8,10, 2,4,7,11
依次从两段中取元素进行比较并放入新的列表
第一次: 2 3,6,8,10, 4,7,11
第二次: 2,3 6,8,10, 4,7,11
第三次: 2,3,4 6,8,10, 7,11
第四次: 2,3,4,6 8,10, 7,11
第五次: 2,3,4,6,7 8,10, 11
第六次: 2,3,4,6,7,8 10, 11
第七次: 2,3,4,6,7,8,10 11
第八次: 2,3,4,6,7,8,10,11

代码实现
#归并函数
def merge(li,low,mid,high):
    i = low
    j = mid + 1
    ltmp = []
    while i<=mid and j<=high:
        if li[i] < li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    #此while结束后,肯定有一部分没数了
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high+1] = ltmp

li = [2,4,5,7,1,3,6,8]
merge(li,0,3,7)
print(li)

在这里插入图片描述

代码实现

首先把列表分成两部分,对每一部分依次分解,直到low>=hjgh,此时列表已经被分成两个排好序的部分,在对这两个排好序部分进行归并

def merge_sort(li,low,high):
    if low < high:
        mid = (low + high) // 2
        merge_sort(li,low,mid)
        merge_sort(li,mid+1,high)
        merge(li,low,mid, high)

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

在这里插入图片描述

总结

1.三种排序算法 时间复杂度都是O(nlogn)
2.一般情况下,就运行时间而言:
快速排序<归并排序<堆排序
3.缺点
快速排序:极端条件下排序效率低 (倒序)
归并排序:需要额外的内存开销
堆排序:在这三分钟较慢
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值