目录
一、树与二叉树
树
二叉树
二叉树存储方式
二、堆排序
堆的向下调整,调整为大根堆:
调整为小根堆:
堆排序思路
三、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=' ')