算法篇02---堆排序及其应用

目录

二叉树

二叉树的存储方式(表示方式):怎么实现一个二叉树

一. 堆排序:

 1. 堆: 一种特殊的完全二叉树结构(全满但最好一排后边可以少)。

    父节点找子节点和子节点找父节点:

 2. 堆的向下调整----构建堆的基础:

 3. 堆构建好之后如何排序: 挨个出数

 4.如何构造堆

 5.堆排序过程

 6. 代码实现过程

 7.堆排序内置模块---heapq

 8.topk问题-------堆排序问题


1. 树 是一种数据结构,比如目录结构

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

3. 树 是由n个节点组成的集合:

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

根节点:A就是根节点

叶子节点: 不能分叉的节点(叶子)

树的深度(高度):4

节点E的度:2

树的度: 树中节点最多的度,6

孩子节点/父节点: E 为 I 的父节点,I 为 E 的节点

子树: 树的某个分支

二叉树

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

堆是一个特殊的二叉树 

二叉树的存储方式(表示方式):怎么实现一个二叉树

  • 链式存储方式(数据结构部分讲解)
  • 顺序存储方式(堆排序中)

一. 堆排序:

1. 堆: 一种特殊的完全二叉树结构(全满但最好一排后边可以少)。

  • 大堆根:一棵完全二叉树,满足任一节点都比其他孩子节点大。
  • 小堆根:一棵完全二叉树,满足任一节点都比其孩子节点小。

可以将其形象的看作 市长、县长、村长、村民 。以下以大根堆为例。

父节点找子节点和子节点找父节点:

         知道孩子节点下标为n,则其父结点下标为:(n-1)//2。

2. 堆的向下调整----构建堆的基础:

        假设:根节点的左右子树都是堆,但根节点不满足根的性质,那么可以通过一次向下的调整将其变成一个堆...... 向下调整顺序如下:

---->--->---->

 当根节点的左右子树都是堆时,但自身不满足堆的性质. 可以通过一次向下调整来将其变换成一个堆

3. 堆构建好之后如何排序: 挨个出数

--拿出堆顶9----向下调整--

--再拿出堆顶----向下调整----再拿出堆顶,将2放到堆顶

改进:

    堆 相当于一个列表, 拿出来的堆顶元素放在另一个列表里很废内存, 秉持着能省则省的原则, 我们不建议再创建一个列表去放堆顶元素。  我们可以将 堆顶元素和最后的元素互换位置,并且将堆最后的位置向前移动一位,如图:

------>

 说明:3原本在最后一个元素,让3和9互换,然后将堆的位置下标变到4的位置下标(下标减1)。

 4.如何构造堆

        要建造堆,  首先要让下级先有序,  因此需要从最下面的根节点开始, 即最后一个 非叶子节点开始.   "农村包围城市战略"

        先对最后一个非叶子节点做 向下调整, 让他变成堆 ; 然后依次将前面的每个非叶子节点 通过向下调整 变成堆.

 5.堆排序过程

  1. 建立堆(利用向下调整+“农村包围城市”)。
  2. 得到堆顶元素, 为最大元素。(挨个出数)
  3. 去掉堆顶, 将堆最后一个元素 放到堆顶, 此时可通过一次向下调整重新使堆有序。
  4. 堆顶元素为第二大元素。
  5. 重复步骤3, 直到堆元素变空。

 6. 代码实现过程

向下调整函数:复杂度:O(log n)

# 向下调整函数(大根堆,小根堆只改两个符号)
def sift(li,low,high):  #low表示二叉树的顶;high的作用是:防止j越界
    '''
    堆排序中的向下调整函数
    :param li:列表
    :param low: 堆的根节点位置
    :param high:堆的最后一个元素的位置,唯一的作用是防止 j 越界
    :return:
    '''
    i=low
    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 tmp < li[j]:   # 小根堆:>
            li[i]=li[j]
            i=j   # i 指向下一层
            j=2*i+1
        else:  # 表示tmp>li[j]   
            li[i]=tmp
            break
    else:   # 如果i到达叶子节点,最后一层,j已经出界
        li[i]=tmp

构建堆和排序(挨个出数):复杂度:O(nlog n)

def heap_sort(li):
    # 构造堆:农村包围城市
    n=len(li)
    for i in range((n-2)//2,-1,-1): # i表示建堆的时候调整部分的根节点下标        #将这个小分支变成堆:进行向下调整
        sift(li,i,n-1)  # j唯一的作用就是防止j越界,high为最后一个元素时,也能满足该条件
    # 堆构造完成
    print(li)
    # 接下来就是排序:挨个出数
    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(nlog n)

7.堆排序内置模块---heapq

import heapq   # q代表queue 优先队列(小的或者大的先出)。q模块是先进先出
import random

li=list(range(100))
random.shuffle(li)  # 生成一个随机的列表
print(li)

heapq.heapify(li)  #建一个小根堆

heapq.heappop(li)  # heappop执行一次,往外弹出一个最小元素

n=len(li)
for i in range(n):
    print(heapq.heappop(li),end=' ')

8.topk问题-------堆排序问题

        现在有n个数,设计算法得到前k大的数(k<n),解决思路:

  • 排序后切片                        O(nlog n)+k (舍去k)
  • 排序LowB三人组              O(kn)
  • 堆排序                               O(nlog k)

堆排序解决思路:

  1. 取列表前k个元素 建立一个小根堆。堆顶就是目前第k大的数。
  2. 依次向后遍历原列表,对于列表中的元素。如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶元素更换为该元素,并且对堆进行一次向下调整。
  3. 遍历完所有元素之后, 倒序弹出堆顶。

def sift(li,low,high):  # 小根堆
    '''
    小根堆:向下调整
    :param li: 列表
    :param low: 堆顶元素的下标
    :param high: 最后一个元素的下标
    :return:
    '''

    tmp=li[low]
    high=len(li)-1
    i=low
    j=2*i+1
    while j<=high:
        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:  # li[j]>tmp
            li[i]=tmp
            break
    else:
        li[i]=tmp


def topk(li,k):
    heap=li[0:k]
    for i in range((k-2)//2,-1,-1):
        sift(heap,i,k-1)
    # 1.建堆:选择列表前k个元素,并建一个小根堆---农村包围城市
    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(1000))
random.shuffle(li)

print(topk(li,10))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值