1、树
1)树的定义
树是一种数据结构
树是一种可以递归定义的数据结构
树是由n个节点组成的集合如果n=0,那么这是一棵空树
如果n>0,那么存在1个节点作为树的根节点,其他节点可以分位m个集合,每个集合本身又是一棵树
2)根节点、叶子节点
如下A为根节点
B、C、H、I、P、Q、K、L、M、N为叶子节点(没有孩子)
3)树的深度(高度)
如下该树的深度为4
4)树的度
度:节点中孩子节点的个数(如A节点的度为6,F节点的度为3)
树的度:整个树中节点的最大的度(该树的度为6)
5)孩子节点/父节点
A是B-G的父节点
B-G是A的孩子节点
2、二叉树
1)定义:
度不超过2的树
每个节点最多有两个孩子节点
两个孩子节点被区分为左孩子节点和右孩子节点
2)满二叉树:
一个二叉树,如果每一层的节点树都达到最大值,则这个二叉树就是满二叉树
3)完全二叉树:
叶子节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树
4)对于二叉树父亲节点找孩子节点
将下图二叉树变为列表
i为父节点的下标
此时:
左孩子下标=2i+1
右孩子下标=2i+2
如根节点9找左右孩子节点,9的下标为0,则左孩子下标为2*0+1=1,为8, 右孩子的下标为2*0+2=2 ,为7
5)对于二叉树孩子节点找父亲节点
n为孩子节点的下标
父亲节点下标=(n-1)//2 (//整除向下取整)
如0、1节点找父亲节点7
此时0、1节点的下标为5、6。那么(5-1)// 2=2,(6-1)//2=2。则可得父亲节点的下标为2也就是7
3、堆
堆:一种特殊的完全二叉树结构
大根堆:一颗完全的二叉树,满足任意节点都比其他孩子节点大
小根堆:一颗完全的二叉树,满足任意节点都比其他孩子节点小
4、堆的向下调整
如果根节点的左右子树都是堆,但整棵树不是堆,那么我们可以通过一次向下的调整来将其变换成一个堆。如下图就是一个二叉树,但不是一个堆
列表:li = [2, 9, 7, 8, 5, 0, 1, 6, 4, 3]
通过向下调整可以将其变成大根堆,如下动态图:
1、根节点比两个孩子节点都小,且第二层的节点又大于它的两个孩子节点,此时该二叉树既不是大根堆也不是小根堆
2、我们可以通过向下调整将该二叉树变成一个大根堆
代码实现(结合上面的动图看代码)
def shit(li, low, height):
"""
:param li: 列表
:param low: 列表的第一个下标(堆顶元素的位置)
:param height: 列表的最后一个下标(堆顶的最后一个元素的位置)
:return:
"""
i = low # 当前i指向2的位置(i用于标识当前树或子树的堆顶)
j = i * 2 + 1 # 当前j指向i的左孩子9的位置(j用于指向i的左孩子和右孩子,谁大指向谁)
tmp = li[low] # 暂存堆顶tmp=2
while j <= height: # 如果j>height证明i到了最后一层,此时退出循环(1<=9)
# 判断当前i的左孩子和右孩子的大小
if j + 1 <= height and li[j + 1] > li[j]: # 判断右孩子是否存在且比左孩子大(2<=9 and 7>9)
j = j + 1 # 当前j指向右孩子
# 判断当前堆顶的右孩子是否大于堆顶
if li[j] > tmp: (9>2)
li[i] = li[j] # 将右孩子至于堆顶(9)
i = j # 现在将i指向j的位置(指向原来9的位置)
j = i * 2 + 1 # 将j指向i的左孩子(j指向8)---继续循环
else: # 证明当前堆顶比它的右孩子或者左孩子大,则退出循环
break
# 当循环结束时,将tmp至于当前i的位置
li[i] = tmp
li = [2, 9, 7, 8, 5, 0, 1, 6, 4, 3]
shit(li, 0, len(li) - 1)
print(li)
# 结果
[9, 8, 7, 6, 5, 0, 1, 2, 4, 3]
5、堆排序的过程
1)构造堆
1、构造堆相比于向下调整来说,它是一个逆序的过程。当我们对一个列表进行堆排序时,此时该列表并不是堆,所以我们需要通过构造堆的方法进行构造大根堆或者小根堆,构造堆其实就是对二叉树的每一个子树进行向下调整。
2、如图
先从最后一个子树(3:5)开始进行向下调整,然后依次对(9:2-4)(1:0-7)(8:9-3)(6:8-1)进行向下调整
li = [6, 8, 1, 9, 3, 0, 7, 2, 4, 5]
代码:
def heap_sort(li):
n = len(li) - 1 # 列表的长度
# 倒序遍历所有的父亲节点,步长 -1, 依次进行向下调整
for i in range((n - 1) // 2, -1, -1): # 最后一个叶子节点的位置为(n),它的父亲节点为(n-1)//2
# 对当前的的子树进行向下调整
shit(li, i, n)
# 构建堆完成
li = [6, 8, 1, 9, 3, 0, 7, 2, 4, 5]
heap_sort(li)
print(li)
# 结果
[9, 8, 7, 6, 5, 0, 1, 2, 4, 3]
[9, 8, 7, 6, 5, 0, 1, 2, 4, 3]建堆后为大根堆
2)挨个出数进行排序
第一次得到堆顶元素,为最大元素,然后去掉堆顶,将堆最后一个元素放到堆顶,此时可以通过一次向下调整重新使堆有序,然后此时堆顶元素为第二大元素,重复步骤,直到堆变空
def heap_sort(li):
n = len(li) - 1 # 列表的长度
# 1、构建堆
# 倒序遍历所有的父亲节点,步长 -1, 依次进行向下调整
for i in range((n - 1) // 2, -1, -1): # 最后一个叶子节点的位置为(n),它的父亲节点为(n-1)//2
# 对当前的的子树进行向下调整
shit(li, i, n)
# 2、挨个出数排序
for i in range(n, -1, -1): # 倒序循环整个堆
li[0], li[i] = li[i], li[0] # 每次取出的堆顶元素和都和当前堆尾元素交换
shit(li, 0, i - 1) # 当前i位置就是最大的数,再对堆尾为i-1的堆进行向下调整
li = [6, 8, 1, 9, 3, 0, 7, 2, 4, 5]
heap_sort(li)
print(li)
# 结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
堆排序的时间复杂度
O(nlogn)