堆排序
树与二叉树
-
树是一种数据结构 比如:目录结构
-
树是一种可以递归定义的数据结构
-
树是由n个节点组成的集合:
- 如果n=0,那这是一棵空树;
- 如果n>0,那么存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
树结构的一些基本定义
示意图
- 结点的度:结点拥有的子树的数目。eg:结点 A 的度为3
- 树的度:树种各结点度的最大值。eg:树的度为3
- 根节点:最上面那个节点 A
- 叶子结点:度为 0 的结点。eg:E、F、C、G 为叶子结点
- 孩子结点:一个结点的子树的根节点。eg:B、C、D 为 A 的子结点
- 父结点:B 为 A 的子结点,那么 A 为 B 的父结点
- 兄弟结点:一个双亲结点结点的孩子互为兄弟结点。eg:B、C、D 为兄弟结点
- 结点的层次:根节点为第一层,子结点为第二层,依次向下递推…eg:E、F、G 的层次均为 3
- 树的深度:树种结点的最大深度。eg:该树的深度为 3
- 森林:m 棵互不相交的树称为森林
二叉树
二叉树的定义
二叉树是一种特殊的树:它或者为空,或者由一个根节点加上根节点的左子树和右子树组成,这里要求左子树和右子树互不相交,且同为二叉树,很显然,这个定义是递归形式的。
- 二叉树:度不超过2的树
- 每个节点最多有两个孩子节点
- 两个孩子节点被区分为左孩子节点和右孩子节点
满二叉树与完全二叉树
满二叉树: 如果一棵二叉树的任意一个结点或者是叶子结点,或者有两棵子树,同时叶子结点都集中在二叉树的最下面一层上,这样的二叉树称为满二叉树
完全二叉树: 若二叉树中最多只有最下面两层结点的度小于 2 ,并且最下面一层的结点(叶子结点)都依次排列在该层最左边的位置上,具有这样结构特点的树结构称为完全二叉树。
完全二叉树的特性
当前节点:i (在数组中的位置)
父节点的位置 = (i-1) // 2
左边子节点(左孩子)位置 = 2i + 1
右边子节点(右孩子)位置 = 2i + 2
堆的定义(什么是堆?)
-
堆:一种特殊的完全二叉树结构
- 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
- 小根堆: 一棵完全二叉树,满足任一节点都比其孩子节点小
堆排序的过程
- 1.建立堆
- 2.得到堆顶元素,为最大元素
- 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
- 4.堆顶元素为第二大元素
- 5.重复步骤3,直到堆变空
构建堆:从倒数第二层开始,依次往上变
算法描述
- 初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个 堆,这时堆的根节点的数最大
- 然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆
- 依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列
堆排序动图演示
代码实现
代码1>
def sift(arr, low, high):
"""向下调整
:param arr: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
"""
i = low # i最开始指向根节点
j = 2 * i + 1 # j最开始是左孩子
tmp = arr[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and arr[j + 1] > arr[j]: # 如果右孩子有并且比左孩子大
j = j + 1 # 让j指向右孩子
if arr[j] > tmp:
arr[i] = arr[j]
i = j # 往下看一层
j = 2 * i + 1
else:
break
arr[i] = tmp # 把tmp放到叶子节点上
def heap_sort(arr):
"""推排序"""
n = len(arr)
# 1.建堆
for i in range((n - 2) // 2, -1, -1):
# i 表示建队的时候调整部分的根的下标
sift(arr, i, n - 1)
# 2.挨个出数
for i in range(n - 1, -1, -1): # i 指向当前堆的最后一个元素
# 交换 i 和堆顶位置的元素
tmp = arr[i]
arr[i] = arr[0]
arr[0] = tmp
sift(arr, 0, i - 1) # i-1是新的high
if __name__ == "__main__":
numbers = [2, 9, 7, 6, 8, 10, 3, 5, 2, 4, 0, 1, 13]
heap_sort(numbers)
print(numbers)
代码 2>
def heapify(tree, n, i):
"""
维护堆的性质(向下调整)
:param tree: 完全二叉树数组(存储堆的数组)
:param n: 树中的节点个数,即数组的长度
:param i: 待维护节点的下标,即对哪一个节点做heapify的操作
:return:
"""
if i >= n:
return
left = 2 * i + 1 # 左孩子节点
right = 2 * i + 2 # 右孩子节点
max_index = i
if left < n and tree[left] > tree[max_index]: # left < n:保证左孩子节点不出界
max_index = left
if right < n and tree[right] > tree[max_index]:
max_index = right
if max_index != i:
tree[i], tree[max_index] = tree[max_index], tree[i]
heapify(tree, n, max_index)
def build_heap(tree, n):
last_node = n - 1
parent = (last_node - 1) // 2
for i in range(parent, -1, -1):
heapify(tree, n, i)
def heap_sort(tree, n):
build_heap(tree, n)
for i in range(n - 1, -1, -1):
tree[0], tree[i] = tree[i], tree[0]
heapify(tree, i, 0) # i 表示树中节点的个数,每交换一次,树中节点个数就减少一个
# 即:树中节点个数最开始为n,当堆顶元素和最后位置的元素交换后,树中节点个数=n-1,所以每次循环树中节点个数是i
if __name__ == "__main__":
numbers = [10, 2, 5, 8, 9, 6, 3, 1, 0, 25, 41, 26]
heap_sort(numbers, len(numbers))
print(numbers)