python树与二叉树、堆排序

目录

一、树与二叉树

二叉树

二叉树存储方式

二、堆排序

堆的向下调整,调整为大根堆:

调整为小根堆:

堆排序思路

三、python堆排序的内置模块heapq

四、堆排序案例——topk问题

解决思路:

代码实现:


一、树与二叉树

        根节点、叶子节点
        树的深度(高度)
        树的度:最大节点的度
        孩子节点/父节点
        子树

二叉树

        度不超过2的树,每个节点最多有两个孩子节点,两个孩子节点被区分为左孩子节点和右孩子节点
        满二叉树:一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树
        完全二叉树:叶子节点只能最下层和此下层,并且最下层的节点都集中在该层最左边的若干位置的二叉树

二叉树存储方式

链式存储方式
顺序存储方式: 用一个列表存储完全二叉树
父节点和左孩子结点下标关系:i--->2i+1
父节点和右孩子结点下标关系:i--->2i+2
孩子节点和父节点的的下标关系:i--->(i-1)//2

二、堆排序

堆:一种特殊的完全二叉树结构
大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小

堆的向下调整,调整为大根堆:

# 堆的向下调整
def sift(li, low, high):
    """
    :param li:  列表
    :param low: 当前堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low   # i最开始指向根节点
    j = 2 * i + 1  # j最开始指向i的左孩子节点
    tem = li[low]  # 把堆顶保存起来
    while j <= high:  # 当还有节点的时候,即j的位置还有数就一直循环
        if j+1 <= high and li[j+1] > li[j]:   # 如果右孩子比左孩子大
            j = j+1   # 让j指向右孩子节点
        if li[j] > tem:   # 如果孩子节点比tem大
            li[i] = li[j]   # 则孩子节点就放到父节点
            i = j
            j = 2 * i + 1
        else:
            li[i] = tem   # 如果孩子节点比tem小,则tem放在i的位置
            break
    else:
        li[i] = tem

调整为小根堆:

# 调整小根堆
def sift_down(li, low, high):
    """
    :param li:  列表
    :param low: 当前堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low   # i最开始指向根节点
    j = 2 * i + 1  # j最开始指向i的左孩子节点
    tem = li[low]  # 把堆顶保存起来
    while j <= high:  # 当还有节点的时候,即j的位置还有数就一直循环
        if j+1 <= high and li[j+1] < li[j]:   # 如果右孩子比左孩子小
            j = j+1   # 让j指向右孩子节点
        if li[j] < tem:   # 如果孩子节点比tem小
            li[i] = li[j]   # 则孩子节点就放到父节点
            i = j
            j = 2 * i + 1
        else:
            li[i] = tem   # 如果孩子节点比tem大,则tem放在i的位置
            break
    else:
        li[i] = tem

堆排序思路

1.建立堆
2.得到堆顶元素,为最大元素
3.去掉堆顶,将去掉的堆顶与堆的最后一个元素交换,此时可通过一次向下调整重新使堆有序,
4.堆顶元素为第二大元素
5,重复步骤3,直到堆变空

def heap_sort(li):
    n = len(li)
    # 建立堆
    for i in range((n-2)//2, -1, -1):   # 从最后一个节点的父节点开始向前遍历,第二个-1只会取到第0个位置
        # 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
    return li

测试代码:

import random

li = [i for i in range(100)]
random.shuffle(li)   # 打乱顺序
print(li)
print(heap_sort(li))

结果:

三、python堆排序的内置模块

import heapq   # 优先队列

内置函数:

def _siftdown(heap, startpos, pos):
    newitem = heap[pos]
    # Follow the path to the root, moving parents down until finding a place
    # newitem fits.
    while pos > startpos:
        parentpos = (pos - 1) >> 1
        parent = heap[parentpos]
        if newitem < parent:
            heap[pos] = parent
            pos = parentpos
            continue
        break
    heap[pos] = newitem
import heapq
import random
li = list(range(10))
random.shuffle(li)

print(li)   # [6, 8, 2, 9, 4, 0, 5, 3, 1, 7]
heapq.heapify(li)   # 建一个小根堆
print(li)   # [0, 1, 2, 3, 4, 6, 5, 8, 9, 7]
for i in range(len(li)):
    # 每次都输出最小的
    print(heapq.heappop(li), end=',')   # 0,1,2,3,4,5,6,7,8,9,

四、堆排序案例——topk问题

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

解决思路:

1.排序后切片 O(nlogn)
2.排序LowB三人组 O(kn) 冒泡排序k趟,选择排序选择k次
3.①去列表前k个元素建立一个小根堆,堆顶就是目前第k大的数;
   ②依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶, 则将堆顶更换为该元素,并且对堆进行一次调整,调整为小根堆; 
   ③遍历列表所有元素后,倒序弹出堆顶。
  复杂度:O(nlogk)

代码实现:

import random
import heapq

def topk(k, li):
    li01 = li[:k]   # 取出列表前k个元素
    heapq.heapify(li01)   # 建小根堆
    for i in range(k, len(li)):
        if li[i] > li01[0]:
            li01[0] = li[i]
            heapq.heapify(li01)   # 调整
    return li01

if __name__ == '__main__':
    li = list(range(100))
    random.shuffle(li)
    print(li)
    k = 10
    li_re = topk(k, li)
    # 依次输出
    for i in range(len(li_re)):
        print(heapq.heappop(li_re), end=' ')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TWAS@py

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值