Python--树的遍历和堆排序

二叉树的遍历

遍历

  • 迭代所有元素一遍

树的遍历

对树中所有元素不重复地访问一遍,也称作扫描

广度优先遍历

  • 层序遍历

深度优先遍历

  • 前序遍历
  • 中序遍历
  • 后序遍历

遍历序列

  • 将树中所有元素遍历一遍后,得到的元素的序列。将层次结构转换成了线性结构

层序遍历

  • 按照树的层次,从第一层开始,自左向右遍历元素
  • 遍历序列
    • ABCDEFGHI
      在这里插入图片描述

深度优先遍历

  • 设树的根结点为D,左子树为L,右子树为R,且要求L一定在R之前,则有下面几种遍历方式:
    • 前序遍历,也叫先序遍历、也叫先根遍历,DLR
    • 中序遍历,也叫中根遍历,LDR
    • 后序遍历,也叫后根遍历,LRD
前序遍历DLR
  • 从根结点开始,先左子树后右子树
  • 每个子树内部依然是先根结点,再左子树后右子树。递归遍历
  • 遍历序列
    • A BDGH CEIF
      在这里插入图片描述
中序遍历LDR
  • 从根结点的左子树开始遍历,然后是根结点,再右子树
  • 每个子树内部,也是先左子树,后根结点,再右子树。对遍历
  • 遍历序列
    • 左图:GDHB A IECF
    • 右图:GDHB A EICF
      在这里插入图片描述
后序遍历LRD
  • 先左子树,后右子树,再根结点
  • 每个子树内部依然是先左子树,后右子树,再根结点。递归遍历
  • 遍历序列
    GHDB IEFC A
    在这里插入图片描述

堆排序Heap Sort

堆Heap

  • 堆是一个完全二叉树
  • 每个非叶子结点都要大于或者等于其左右孩子结点的值称为大顶堆
  • 每个非叶子结点都要小鱼或者等于其左右孩子结点的值称为小顶堆
  • 根结点一定是大顶堆中的最大值,一定是小顶堆中的最小值

大顶堆

  • 完全二叉树的每个非叶子结点都要大于或者等于其左右孩子结点的值称为大顶堆
  • 根结点一定是大顶堆中的最大值
    在这里插入图片描述

小顶堆

  • 完全二叉树的每个非叶子结点都要小于或者等于其左右孩子结点的值称为小顶堆
  • 根结点一定是小顶堆中的最小值
    在这里插入图片描述

1. 构建完全二叉树

  • 待排序数字为 30,20,80,40,50,10,60,70,90
  • 构建一个完全二叉树存放数据,并根绝性质5对元素编号,放入顺序的数据结构中
  • 构造一个列表为[0,30,20,80,40,50,10,60,70,90]

2. 构建大顶堆

核心算法
  • 度数为2的结点A,如果它的左右孩子结点的最大值比它大的,将这个最大值和该结点交换
  • 度数为1的结点A,如果它的左孩子的值大于它,则交换
  • 如果结点A被交换到新的位置,还需要和其孩子结点重复上面的过程
起点结点的选择
  • 从完全二叉树的最后一个结点的双亲结点开始,即最后一层的最右边叶子结点的父结点开始
  • 结点数为n,则起始结点的编号为n//2(性质5)
下一个结点的选择
  • 从起始结点开始向左找其同层结点,到头后再从上一层的最右边结点开始继续向左逐个查找,直至根结点
    在这里插入图片描述

3. 大顶堆的目标

  • 确保每个非叶子结点的都比其左右孩子结点的值大
    在这里插入图片描述

4. 排序

  • 将大顶堆根结点逐个最大值和最后一个叶子结点交换,那么最后一个叶子结点就是最大值,将这个叶子结点排除在待排序结点之外
  • 从根结点开始(新的根结点),重新调整为大顶堆后,重复上一步
    在这里插入图片描述
    在这里插入图片描述
  • 堆顶和最后一个结点交换,并排序最后一个结点
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

算法实现

import math

def print_tree(array,unit_width=2):
    '''
     i  前空格      元素间
   1 3  7=2**3-1  0 2*7+1 2*前空格+1
   2 2  3=2**2-1  7=2**3+1
   3 1  1=2**1-1  3=2**1+1
   4 0  0=2**0-1  1=2*0+1
    '''
    length = len(array)
    index = 1
    # 因为使用时前面补0了,不然应该是math.ceil(math.log2(len(array)+1)
    depth = math.ceil(math.log2(length)) # 4
    # print(depth)

    space = ' ' * unit_width
    for i in range(depth-1, -1, -1):
        pre = 2 ** i - 1
        print(pre * space, end="") # 前置空格
        offset = 2 ** (depth - i - 1)
        line = array[index:index+offset] # 取数字
        interval = (2 * pre + 1) * space # 间隔的空格
        print(interval.join(map(lambda x:"{:2}".format(x), line)))

        index += offset

# Heap Sort 堆排序

# 为了和编码对应,增加一个无用的0在首位
origin = [0, 30, 20, 80, 40, 50, 10, 60, 70, 90]

total = len(origin) - 1 # 初始待排序元素个数,即n
print_tree(origin)
print("="*50)

def heap_adjust(n ,i, array:list):
    '''
      调整当前节点(核心算法)

    调整的结点的起点在n//2,保证所有调整的结点都有孩子结点
    :param n: 待比较数个数
    :param i: 当前结点的下标
    :param array: 待排序数据
    :return: None
    '''
    while 2 * i <= n:
        # 孩子结点判断 2i为左孩子, 2i+1为右孩子
        lchile_index = 2 * i
        max_child_index = lchile_index # n=2i
        if n > lchile_index and array[lchile_index +1] > array[lchile_index] :# n>2i说明还有右孩子
            max_child_index = lchile_index + 1 # n=2i+1

        # 和子树的根结点比较
        if array[max_child_index] > array[i]:
            array[i], array[max_child_index] = array[max_child_index], array[i]
            i = max_child_index # 被交换后,需要判断是否还需要调整
        else:
            break
    # print_tree(array)

# 构建大顶堆、大根堆
def max_heap(total, array:list):
    for i in range(total // 2, 0, -1):
        heap_adjust(total, i, array)
    return array

print_tree(max_heap(total, origin))
print("=" * 50)


# 排序
def sort(total, array:list):
    while total > 1:
        array[1], array[total] = array[total], array[1] # 堆顶和最后一个结点交换
        total -= 1
        if total == 2 and array[total] >= array[total - 1]:
            break
        heap_adjust(total, 1, array)
    return array

print_tree(sort(total, origin))
print("="*50)
print(origin[1:])

打印结果

              30
      20              80
  40      50      10      60
70  90
==================================================
              90
      70              80
  40      50      10      60
20  30
==================================================
              10
      20              30
  40      50      60      70
80  90
==================================================
[10, 20, 30, 40, 50, 60, 70, 80, 90]

总结

  • 是利用堆性质的一种选择排序,在堆顶选出最大值或者最小值
  • 时间复杂度
    堆排序的时间复杂度为O(nlogn)
    由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)
    在这里插入图片描述

空间复杂度

  • 只是使用了一个交换用的空间,空间复杂度就是O(1)

稳定性

  • 不稳定的排序算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值