数据结构与算法-排序(2)

1.快速排序(原地排序)

思路:取一个元素p(第一个元素),使p归位;列表被p分为两部分,左边都比p小,右边都比p大;递归完成排序。

关键代码:归位的实现。

def partition(li,left,right):
    tmp=li[left]
    while left<right:#列表中至少两个值
        while left<right and li[right]>=tmp:#一旦left和right重合,就退出
            right-=1
        li[left]=li[right]#把右边比第一个值小的数放到左边空位
        while left<right and li[left]<=tmp:
            left+=1
        li[right]=li[left]#把左边比第一个值大的数放到右边空位
    li[left]=tmp
    return left
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)

时间复杂度:O(nlogn)

快速排序的问题:递归:递归最大深度;最坏情况:时间复杂度O(n^2)

修改最大深度代码:

import sys
sys.setrecurtionlimit(10000)

2.推排序(原地排序)

·堆:一种特殊的完全二叉树结构

·大根堆:一个完全二叉树,满足任一节点都比其他孩子节点大,根节点为最大元素

·小根堆:一个完全二叉树,满足任一节点都比其他孩子节点小,根节点为最小元素

·堆的向下调整:假设节点的左右子树都是堆,但本身不是堆,通过向下调整根节点使其变成堆。

·堆排序过程:

(1)建立堆;(农村包围城市,从最下面开始调整,直到整个堆完成)

(2)得到堆顶元素,为最大元素;

(3)去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序;

(4)堆顶元素为第二大元素;

(5)重复步骤(3),直到堆变空。

关键代码:向下调整和出数

向下调整代码:

def sift(li,low,high):
    i=low#i开始指向根节点
    j=2*i+1#j开始是左孩子
    tmp=li[i]
    while j<=high:#只要j位置有效
        if j+1<=high and li[j+1]>li[j]:#如果右孩子有并且比较大
            j=j+1
        if li[j]>tmp:
            li[i]=li[j]
            i=j#往下看一层
            j=2*i+1
        else:#tmp更大,把tmp放在i的位置上
            li[i]=temp#把tmp放在某一级领导位置上
            break
    else:
        li[i]=temp#把tmp放到叶子节点上
#大根堆

时间复杂度:O(logn)

构造堆+出数:

def heap_sort(li):
    n=len(li)
    for i in range((n-1-1)//2,-1,-1):#i表示所需调整的堆的根节点的位置
        sift(li,i,n-1)
    #建堆完成
    for i in range(n-1,-1,-1):#i指向当前堆的最后一个元素
        li[0],li[i]=li[i],li[0]
        sift(li,0,i-1)#i-1是新的high    

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

堆的内置模块:

import heapq
import random

li=list(range(100))
random.shuffle(li)
heapq.heapify(li)#建堆(小根堆)
n=len(li)
for i in range(n):
    print(heapq.heappop(li),end=',')#每次弹出一个最小值

堆排序-topk问题:现在有n个数,设计算法得到前k大的数(k<n)

解决方法:

(1)排序后切片:O(nlogn)

(2)简单排序三人组:O(kn)

(3)堆排序:O(nlogk)

解决思路:

(1)取列表前k个元素建立一个小根堆,堆顶就是目前第k个大的数;

(2)依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整;

(3)遍历列表所有元素之后,倒序弹出堆顶。

代码实现:

def sift(li,low,high):
    i=low#i开始指向根节点
    j=2*i+1#j开始是左孩子
    tmp=li[i]
    while j<=high:#只要j位置有效
        if j+1<=high and li[j+1]<li[j]:#如果右孩子有并且比较小
            j=j+1
        if li[j]<tmp:
            li[i]=li[j]
            i=j#往下看一层
            j=2*i+1
        else:#tmp更小,把tmp放在i的位置上
            li[i]=temp#把tmp放在某一级领导位置上
            break
    else:
        li[i]=temp#把tmp放到叶子节点上
#小根堆
def topk(li,k):
    heap=li[0:k]#取前k个数
    sift(heap,0,k-1)#建堆
    for i in range(k,len(li)-1):
        if li[i]>heap[0]:
            heap[0]=li[i]
            sift(heap,0,k-1)
    for i in range(k-1,-1,-1):#出数
        heap[0],heap[i]=heap[i],heap[0]#将根节点的最小值与堆的最后一个节点交换
        sift(heap,0,i-1)
    return heap

3.归并排序(非原地排序)

归并:假设现在有两个有序的列表,如何合并成一个有序的列表,这种操作称为一次归并。

思路:两个有序列表的开头分别有一个指针,比较两个指针所指位置值的大小,将小的先放到新的列表,另一个再放,直到其中一个列表变为空,将另外一个列表的剩余元素放到这个新列表中即可。

归并代码实现:

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

归并排序的思路:(递归)

分解:将列表越分越小,直至分成一个元素

终止条件:一个元素是有序的

合并:将两个有序列表归并,列表越来越大

 代码实现:

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)

时间复杂度:O(nlogn)

空间复杂度:O(n)

总结:

(1)快速排序、堆排序、归并排序的时间复杂度都是O(nlogn);

(2)一般情况下,就运行时间而言:快速排序<归并排序<堆排序;

(3)三种排序算法缺点:

        快速排序:极端情况下排序效率低

        归并排序:需要额外的内存开销

        堆排序:在快的排序算法中相对较慢

(4)递归有空间开销;

(5)稳定的排序能保证相等元素的相对位置不变(相邻元素依次比较就是稳定排序)。

说明:

本篇博客只是作为个人的听课笔记使用。具体课程为b站上清华大学博士讲解的数据结构与算法,课程讲的非常详细易懂,有需要的可以移步观看。

清华大学博士讲解Python数据结构与算法(完整版)全套100节_哔哩哔哩_bilibili

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值