L5:排序-2(快速、堆)

快速排序(Quick Sort)

  • 意思:取一个元素p,使元素p归位,使得列表被p分为两部分,左边都比p小,右边都比p大,递归完成排序
  • 时间复杂度:O(nlogn)(partition的复杂度是O(n),一共递归logn次)
  • 原地排序
  • 缺点:递归有最大深度,会消耗一定的系统资源;最坏情况(倒序的情况,每次取的元素都是最大的那个数),那么每次都只有左边部分或右边部分,时间复杂度变为O(n^2)
  • 尽量避免最坏情况的方法:取第一个数前,在待排序区随机取一个数,与第一个数交换位置,再开始排序

代码:

def partition(li, left, right):
    temp = li[left]
    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 #把temp归位
    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)

  • 树是一种数据结构(比如目录结构),是一种可以递归定义的数据结构
  • 树是由n个节点组成的集合:
    • 如果n=0,那这是一棵空树
    • 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树

树的一些概念 

  • 根节点:A
  • 叶子节点:没有分叉的节点:B、C、H、I、P、Q、K、L、M、N
  • 树的深度(高度):4,有4层
  • 节点的度:分了几个叉,例如E的度为2
  • 树的度:6,分叉最多的那个节点的度
  • 孩子节点/父节点:E为I的父节点,I为E的子节点
  • 子树:E-IJ-PQ为一个子树

二叉树

  • 度不超过2的树
  • 每个节点最多有2个孩子节点
  • 2个孩子节点被区分为左孩子节点和右孩子节点

满二叉树和完全二叉树

  • 满二叉树:每一层的节点数都达到最大值
  • 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置

 

二叉树的存储方式

  • 链式存储方式(还没学到)
  • 顺序存储方式:在计算机里的顺序存储为从上到下,从左到右

 

  • 从父节点找左孩子节点:i➡2i+1
  • 从父节点找右孩子节点:i➡2i+2
  • 从左孩子节点找父节点:i➡(i-1)//2
  • 从右孩子节点找父节点:i➡(i-2)//2

堆 

  • 意思:一种特殊的完全二叉树结构
  • 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
  • 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
  • 堆的向下调整性质:假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整来将其变成一个堆
  • 构造堆:从最后一个有孩子的节点开始,从下到上,从右到左检查该节点是否及其下属是否满足正确的大小关系,农村包围城市

堆排序(Heap Sort)

  • 步骤
  1. 建立堆
  2. 得到堆顶元素,为最大元素
  3. 去掉堆顶,将堆的最后一个元素放到堆顶,此时可通过一次向下调整使堆变有序
  4. 堆顶元素为第二大元素
  5. 重复步骤3,直到堆变空
  • 时间复杂度:O(nlogn),sift()函数的复杂度是O(logn)

代码: 

#调整函数-大根堆
def sift(li, low, high): #列表,堆的根节点位置,堆的最后一个元素的位置
    i = low #i最开始指向根节点
    j = 2 * i + 1 #j开始是i的左孩子
    temp = li[low] #把堆顶存起来
    while j <= high: #只要j位置有数,没有越界
        if j+1 <= high and li[j+1] > li[j]: #如果右孩子有,且比左孩子大
            j += 1 #把j指向右孩子
        if li[j] > temp:
            li[i] = li[j]
            i = j 往下看一层
            j = 2 * i +1
        else:
            break
    li[i] = temp #把temp放到叶子节点上

#堆排序函数
def heap_sort(li):
    n = len(li)
    for i in range((n-2)//2, -1, -1): #(n-2)//2为最后一个父节点的位置
        #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)

堆排序在python的内置模块——heapq

  • 常用函数
    • heapify(x)
    • heappush(heap, item)
    • heappop(heap)

堆排序的应用——topk问题

  • 问题:有n个数,设计算法得到前k大的数
  • 解决思路:
    • 排序后切片,时间复杂度O(nlogn)
    • 3种普通排序方法,排k趟就排完了,时间复杂度O(kn)
    • 堆排序,时间复杂度O(nlogk)
      • 取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数
      • 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并对堆进行一次调整
      • 遍历列表所有元素后,倒序弹出堆顶

代码:

#调整函数-小根堆
def sift(li, low, high): #列表,堆的根节点位置,堆的最后一个元素的位置
    i = low #i最开始指向根节点
    j = 2 * i + 1 #j开始是i的左孩子
    temp = li[low] #把堆顶存起来
    while j <= high: #只要j位置有数,没有越界
        if j+1 <= high and li[j+1] < li[j]: #如果右孩子有,且比左孩子小
            j += 1 #把j指向右孩子
        if li[j] < temp:
            li[i] = li[j]
            i = j 往下看一层
            j = 2 * i +1
        else:
            break
    li[i] = temp #把temp放到叶子节点上

def topk(li,k):
    heap = li[:k]
    for i in range((k-2)//2, -1, -1): #(k-2)//2为最后一个父节点的位置
        sift(heap, i, k-1)
    #建堆完成
    for i in range(k, len(li)):
        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(li, 0, i-1)
    return heap
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值