Python算法入门day4——常见列表算法分析3 堆排序详解

【堆排序前言】

树与二叉树

1.树是一种数据结构

2.树是一种可以递归定义堆数据结构

3.树是由n个节点组成的集合:
        1.如果n=0,则是一个空树

2.如果n>0,那存在一个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。

树的基本概念

1.根节点(图中的A)

2.叶子节点(图中的B,C,H,I,P,Q,K,L,M,N)没有分叉就是叶子的节点

3.树的深度(看树的最深有多少层数) P,Q就是4

4.节点的度(对于E这个节点,是有两个度)几个分叉就有几个度

5.树的度:就是整个树里最大的节点的度(A分叉最多,所以树的度是6)

6.孩子节点/父节点:上面的是父节点,下面的是子节点(E是I的父节点,I是E的子节点)

7.子树:从树中单独拎出来的叫做子树(E,I,J,P,Q)
 

二叉树基本概念

1. 二叉树:度不超过2的树

2. 每个节点最多有两个孩子节点

3. 两个孩子节点被区分为左孩子节点和右孩子节点

4. 深度为h的非空二叉树最多有2^h-1个节点(深度为4的非空二叉树最多有15个节点)

满二叉树

一个二叉树,如果每一层的结点树都达到最大值,则这个二叉树是满二叉树

完全二叉树

简单来说,就是在满二叉树的前提下,最后一层少几个节点,但中间不能断,得连续(8,9,10,11,12 可以少11,12,不能中间少10或者别的树)。

叶节点只能出现在最下面一层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 

(c)4节点下少了两个结点;(d)3节点下少了一个结点 

二叉树的存储方式(表示方式)

1.链式存储方式

2.顺序储存方式(堆排序)

二叉树的顺序储存方式

1.首先将完全二叉树从左往右依次插入到列表中 

2.观察父节点和左孩子节点的下标编号有什么关系?

        可以观察到 0==>1, 1==>3, 3==>7

        父亲找左孩子:

        即规律:左孩子节点是父节点的两倍+1 (i==>2*i+1)

2.父节点和右孩子节点的编号下标同左孩子类似。

        0==>2 ,2==>6, 3==>8,4==>10

        父亲找右孩子:

        即规律是:右孩子节点是父节点的两倍+2 (i==>2*i+2)

3.左右孩子节点n找父亲父亲节点:(n-1)//2

【堆排序】

1. 堆:一种特殊的完全二叉树结果

        1.大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大(父亲比孩子都大)

        2.小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小(父亲比孩子都小)

 2.堆的向下调整

假设:节点堆左右子树都是堆,但自身不是堆

则可以通过一次向下调整来将其变化成一个堆。

实现方式:从下一层中最大的节点和父节点交换

直到满足大小堆的条件,结束调整

3.向下调整函数的实现

【思路】

1.创建两个参数i,j,tmp

i表示父节点,先获取堆顶的下标

j表示左孩子的子节点

tmp表示堆顶的值

2.判断tmp和li[j]的大小,然后确定位置

3.判断完成,将i变到j的位置,将j变到下面的位置

【具体代码】

#向下调整函数
def sift(li,low,high): #列表,堆的根节点位置,堆的最后一个元素位置(用于判断是否越界的作用)
    i=low #i指最开始指向的根节点
    j=i*2+1 #指最开始的左孩子节点
    tmp=li[low] #将堆顶存起来
    while j<=high: #当孩子节点大于堆的最后一个元素,则代表父节点i是最后一层
        if j+1<=high and li[j+1]>li[j]: #如果右节点存在并且大于左节点
            j=j+1 #将j改为右节点
        if li[j] > tmp:#如果子节点比堆顶大
            li[i]=li[j] #将子节点和堆顶换
            i=j #将子节点的值赋值给i
            j=2*i+1 #重新计算子节点
        else:  #tmp更大,把tmp放到i的位置上
            li[i]=tmp 
            break #向下调整结束
    else: #当j>high,i是最后一行,则将堆顶存放到i的位置
        li[i]=tmp

4.堆排序的实现

  一、堆排序过程/步骤

1.建立堆。(构造堆,从最后的节点出发,依次和父节点交换,看下图)

2.得到堆顶元素i,为最大元素。(找到3,9,1,8,6这样的根节点)

        (根节点的下标由子节点计算出)

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

4.堆顶元素为第二大元素。

5.重复步骤3,直到堆变空

构造堆图解

首先做3,5这个子堆的堆排序,依次类推

 

二、实现建堆的代码(还没有完成) 初步完成

【实现代码】

#堆排序
def heap_sort(li):
    n=len(li)-1 #代表最后一个元素
    #(n-1)//2 代表父元素
    for i in range((n-1)//2,-1,-1): #从最后一个父元素节点开始调整
        #i表示建堆的时候调整的部分的根下标(5,9,1,8,6)
        sift(li,i,n-1)#n-1的说明,看下面文字
    #建堆完成
    print(li)

#做个例子
li=[i for i in range(10)]
import random #导入随机元素
random.shuffle(li)#打乱列表元素
print(li)
heap_sort(li)

 *向下调整函数中的high是为了防止父元素i下标越界而定义的,所以在求7这个元素的子节点时,可以直接将最后的3定义为high,省去了大量时间去计算high的值,7的下标的子节点肯定不会跑到5下面(2*父元素+1)


【运算结果】 

[8, 5, 7, 6, 9, 4, 2, 0, 3, 1] #原列表
[9, 8, 7, 6, 5, 4, 2, 0, 3, 1] #做了建堆操作的列表

这样看可能不好看,前一个对应后两个(9的子节点是8和7)

三、实现挨个出数 

        1.首先遍历i,从最后一个元素到第一个元素

        2.为了减少内存,把堆顶max的数与i进行交换,存放在i原来的位置上

        3.通过向下调整的函数再将第二大的数放到堆顶

        4.将i向前挪一位

【整体代码实现】

#向下调整函数
def sift(li,low,high): #列表,堆的根节点位置,堆的最后一个元素位置(用于判断是否越界的作用)
    i=low #i指最开始指向的根节点
    j=i*2+1 #指最开始的左孩子节点
    tmp=li[low] #将堆顶存起来
    while j<=high: #当孩子节点大于堆的最后一个元素,则代表父节点i是最后一层
        if j+1<=high and li[j+1]>li[j]: #如果右节点存在并且大于左节点
            j=j+1 #将j改为右节点
        if li[j] > tmp:#如果子节点比堆顶大
            li[i]=li[j] #将子节点和堆顶换
            i=j #将子节点的值赋值给i
            j=2*i+1 #重新计算子节点
        else:  #tmp更大,把tmp放到i的位置上
            li[i]=tmp 
            break #向下调整结束
    else: #当j>high,i是最后一行,则将堆顶存放到i的位置
        li[i]=tmp
#堆排序
def heap_sort(li):
    n=len(li)-1 #代表最后一个元素
    #(n-1)//2 代表父元素
    for i in range((n-1)//2,-1,-1): #从最后一个父元素节点开始调整
        #i表示建堆的时候调整的部分的根下标(5,9,1,8,6)
        sift(li,i,n-1)#n-1的说明,看下面文字
    #建堆完成
    
    #挨个出数
    for i in range(n,-1,-1):#i指当前堆的最后一个元素
        li[0],li[i]=li[i],li[0] #交换位置
        sift(li,0,i-1)#i向前挪一位,表示新的high,而不是i的位置,i的位置上的数是用来存放堆顶元素的
    print(li)


#做个例子
li=[i for i in range(10)]
import random #导入随机元素
random.shuffle(li)#打乱列表元素
print(li)
heap_sort(li)

【运行结果】

[5, 9, 7, 1, 2, 4, 3, 8, 0, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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

四、堆排序——内置模块

1.Python内置模块——heapq

2.常用函数

        heapify(x) #建最小堆

       heappop(heap) #排序

【代码实现】

import random #导入随机元素
import heapq #q -> queue 优先队列
li=[i for i in range(10)]
random.shuffle(li)#打乱列表元素
print(li)
heapq.heapify(li)#建堆
for i in range(len(li)):
    print(heapq.heappop(li),end=',')

【实验结果】

[0, 4, 6, 2, 1, 8, 7, 9, 5, 3]
0,1,2,3,4,5,6,7,8,9

五、堆排序——topk问题 

【问题描述】

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

【解决思路】

        1.排序后切片        O(nlogn)

        2.排序LowB三人组        O(kn)

        3.堆排序思路        O(nlogk) 最快

【topk问题堆排序思路】

        1. 取列表前k个元素建立其一个小根堆。堆顶就是目前第k大的数。

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

        3.遍历列表所有元素后,倒序弹出堆顶

【实现代码】

只需要把上面写的向下调整函数的最大堆改为最小堆(把子节点大的数输出改成把子节点小的数输出即可)

#向下调整函数
def sift(li,low,high): #列表,堆的根节点位置,堆的最后一个元素位置(用于判断是否越界的作用)
    i=low #i指最开始指向的根节点
    j=i*2+1 #指最开始的左孩子节点
    tmp=li[low] #将堆顶存起来
    while j<=high: #当孩子节点大于堆的最后一个元素,则代表父节点i是最后一层
        if j+1<=high and li[j+1]<li[j]: #如果右节点存在并且大于左节点
            j=j+1 #将j改为右节点
        if li[j] < tmp:#如果子节点比堆顶大
            li[i]=li[j] #将子节点和堆顶换
            i=j #将子节点的值赋值给i
            j=2*i+1 #重新计算子节点
        else:  #tmp更大,把tmp放到i的位置上
            li[i]=tmp 
            break #向下调整结束
    else: #当j>high,i是最后一行,则将堆顶存放到i的位置
        li[i]=tmp

def topk(li,k):
    n=k-1 #最后一个元素
    heap=li[0:k] #前k的列表
    #建堆
    for  i in range((n-1)//2,-1,-1):
        sift(heap,i,n)
    #遍历k后面的元素
    for i in range(k,len(li)):
        if heap[0]<li[i]:
            heap[0]=li[i]
            sift(heap,0,n)
    #挨个出数
    for i in range(n,-1,-1):
        heap[0],heap[i]=heap[i],heap[0]
        sift(heap,0,i-1)
    return heap
import random #导入随机元素
import heapq #q -> queue 优先队列
li=[i for i in range(10)]
random.shuffle(li)#打乱列表元素
print(li)
print(topk(li,5))

【实验结果】

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值