堆排序前传
树与二叉树
树的简单定义
树是一种数据结构,比如:目录结构。
树是一种可以递归定义的数据结构。
树是由于 n 个节点组成的集合:
- 如果 n=0,则那就是一棵空树;
- 如果 n>0,那就是存在一个节点作为树的根几点,其它节点可以分为 m 个集合,每个集合本身又是一棵树。如下图所示:
树的一些概念
- 根节点:树的第一个节点,上图中就是 A 节点;
- 叶子节点:树中不能再分叉的节点,上图中 B、C、H、I、..等;
- 树的深度:最深有几层,就是树的高度,上图中树的深度为 4;
- 树的度:整棵树中哪个节点分叉最多,即为树的度,上图中树的度为 6,A 节点的分叉最多;
- 父节点/孩子节点:上层节点即为下层节点的父节点,下层节点即为上层节点的子节点。
二叉树
二叉树定义
- 度不超过 2 的树
- 每个节点最多有两个孩子节点
- 两个孩子节点被区分为左孩子节点和右孩子节点
如下图所示:
满二叉树和完全二叉树
满二叉树
一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树。
完全二叉树
叶子节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层坐左边的若干位置的二叉树。
其中:(a)为满二叉树,(b)为完全二叉树,(c)和(d)为非完全二叉树。
二叉树存储方式(表达方式)
- 链式存储方式
- 顺序存储方式(重点讲)
顺序存储方式
简单来树就是用列表来存,如下图所示:
父节点和左孩子节点的编号下标的关系:
- 0->1, 1->3, 2->5, 3->7, 4->9
- i->2i+1
父节点和右孩子节点的编号下标的关系:
- 0->2, 1->4, 2->6, 3->8, 4->10
- i->2i+2
堆排序
什么是堆
一种特殊的完全二叉树结构
- 大根堆(大顶堆):一棵完全二叉树,满足任意一节点都比其孩子节点都大
- 小根堆(小顶堆):一棵完全二叉树,满足任意一节点都比其孩子节点都小
大根堆和小根堆如下图所示:
堆的向下调整性质
假设跟节点的左右子树都是堆,但根节点不满足堆的性质,则可以通过一次向下调整来将其变成一个堆。
堆排序-构造堆
构造堆过程:农村包围城市
- 从树的最后一级有孩子节点开始看起(即最后一个非叶子节点),每次先调整小的子树,再调整大的子树,最后调整整棵树。
其过程如下: 1)从树的最后一级有孩子节点开始看起
2)对子树作调整(3和5交换,可以看作换村长)
3)继续对另一颗子树调整,可以看到该子树符合大根堆,无需再做调整
4)看完村级别的,继续看县级的,并做调整
5)最后做进一步的调整
可以看到,通过以上过程,一颗大根堆就已经构建好了。
接下来,进行挨个出数(出数的过程会进行向下调整),即可拿到排序好的这些数,即我们所说的堆排序。
堆排序-挨个出数
挨个出数
对下面一颗树(上面构造好的一棵大根堆树)进行挨个出数,树如下:
挨个出数的过程如下:
1)出数 9,把做最后一个节点 3 放在根节点
2)然后进行向下调整
3)出数 8,把最后一个节点 3 放在根节点
4)接着再进行向下调整
5)以此类推,直到这棵树为空。
最后出数的即为有序的数,也就是排好序的数,这就是堆排序。
总的来说,堆排序过程如下:
- 建立堆
- 得到堆顶元素,为最大元素
- 去掉堆顶元素,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
- 堆顶元素为第二大元素
- 重复步骤3,直到堆变空。
堆排序实现
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author : Scoefield
@File : heap_sort.py
@Time : 2021/05/23 20:26:35
'''
from collections import deque
import random
def swap_param(L, i, j):
# 交换父子节点的值, 让父节点大于两个子节点
L[i], L[j] = L[j], L[i]
return L
# 构造大根堆
def heap_adjust(L, start, end):
# 将父节点的值赋值给temp
temp = L[start]
# 将父节点的索引赋值给i
i = start
# 左子节点的索引
j = 2 * i
# 当j在L的长度范围之内时
while j <= end:
# 如果子节点在范围内并且左子节点小于右子节点
if (j < end) and (L[j] < L[j + 1]):
# 将j切换成右子节点
j += 1
# 如果父节点的值比子节点中比较的节点小
if temp < L[j]:
# 将子节点的值赋值给父节点
L[i] = L[j]
# 将子节点看成父节点
i = j
# 将子节点的子节点看成子节点
j = 2 * i
else:
break
L[i] = temp
# 堆排序
def heap_sort(L):
# 数组的元素个数中多了辅助位置, 需要减去1
L_length = len(L) - 1
# 完全二叉树的节点按照层次并按从左到右的顺序从0开始编号,那么第n个节点的父节点为n//2
first_sort_count = L_length // 2
for i in range(first_sort_count):
# 构造大根堆
heap_adjust(L, first_sort_count - i, L_length)
for i in range(L_length - 1):
# 将堆顶元素与堆最右方下方的元素交换
L = swap_param(L, 1, L_length - i)
# 构造大根堆
heap_adjust(L, 1, L_length - i - 1)
return [L[i] for i in range(1, len(L))]
def main():
# 将列表生成链表
# L = deque([17 , 13, 40 , 22, 31, 14, 33, 56, 24, 19, 10, 41, 51, 42, 26])
# 因为堆得节点从1开始, 引入辅助位置, 使数组的下标从1开始
# L.appendleft(0)
# 随机生成列表
li = [i for i in range(20)]
random.shuffle(li)
# 堆排序并打印排序结果
res = heap_sort(li)
print(res)
if __name__ == '__main__':
main()
堆排序的时间复杂度:O(nlogn)。