目录
1. 堆: 一种特殊的完全二叉树结构(全满但最好一排后边可以少)。
树
1. 树 是一种数据结构,比如目录结构
2. 数 是一种可以递归定义的数据结构
3. 树 是由n个节点组成的集合:
- 如果n=0,那这是一棵空树
- 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
根节点:A就是根节点
叶子节点: 不能分叉的节点(叶子)
树的深度(高度):4
节点E的度:2
树的度: 树中节点最多的度,6
孩子节点/父节点: E 为 I 的父节点,I 为 E 的节点
子树: 树的某个分支
二叉树
度不超过 2 的树,每个节点最多有两个孩子节点。两个孩子节点被区分为左孩子结点 和 右孩子节点。
堆是一个特殊的二叉树
二叉树的存储方式(表示方式):怎么实现一个二叉树
- 链式存储方式(数据结构部分讲解)
- 顺序存储方式(堆排序中)
一. 堆排序:
1. 堆: 一种特殊的完全二叉树结构(全满但最好一排后边可以少)。
- 大堆根:一棵完全二叉树,满足任一节点都比其他孩子节点大。
- 小堆根:一棵完全二叉树,满足任一节点都比其孩子节点小。
可以将其形象的看作 市长、县长、村长、村民 。以下以大根堆为例。
父节点找子节点和子节点找父节点:
知道孩子节点下标为n,则其父结点下标为:(n-1)//2。
2. 堆的向下调整----构建堆的基础:
假设:根节点的左右子树都是堆,但根节点不满足根的性质,那么可以通过一次向下的调整将其变成一个堆...... 向下调整顺序如下:
---->--->---->
当根节点的左右子树都是堆时,但自身不满足堆的性质. 可以通过一次向下调整来将其变换成一个堆
3. 堆构建好之后如何排序: 挨个出数
--拿出堆顶9----向下调整--
--再拿出堆顶----向下调整----再拿出堆顶,将2放到堆顶
改进:
堆 相当于一个列表, 拿出来的堆顶元素放在另一个列表里很废内存, 秉持着能省则省的原则, 我们不建议再创建一个列表去放堆顶元素。 我们可以将 堆顶元素和最后的元素互换位置,并且将堆最后的位置向前移动一位,如图:
------>
说明:3原本在最后一个元素,让3和9互换,然后将堆的位置下标变到4的位置下标(下标减1)。
4.如何构造堆
要建造堆, 首先要让下级先有序, 因此需要从最下面的根节点开始, 即最后一个 非叶子节点开始. "农村包围城市战略"
先对最后一个非叶子节点做 向下调整, 让他变成堆 ; 然后依次将前面的每个非叶子节点 通过向下调整 变成堆.
5.堆排序过程
- 建立堆(利用向下调整+“农村包围城市”)。
- 得到堆顶元素, 为最大元素。(挨个出数)
- 去掉堆顶, 将堆最后一个元素 放到堆顶, 此时可通过一次向下调整重新使堆有序。
- 堆顶元素为第二大元素。
- 重复步骤3, 直到堆元素变空。
6. 代码实现过程
向下调整函数:复杂度:O(log n)
# 向下调整函数(大根堆,小根堆只改两个符号)
def sift(li,low,high): #low表示二叉树的顶;high的作用是:防止j越界
'''
堆排序中的向下调整函数
:param li:列表
:param low: 堆的根节点位置
:param high:堆的最后一个元素的位置,唯一的作用是防止 j 越界
:return:
'''
i=low
j=2*i+1 # j现在指的是左孩子结点
tmp=li[low]
while j <= high: # 表示 j 不越界
if j+1 <= high and li[j+1] > li[j]: # 右孩子结点存在且更大 #小根堆:改成 <
j=j+1 # j指向右孩子结点
if tmp < li[j]: # 小根堆:>
li[i]=li[j]
i=j # i 指向下一层
j=2*i+1
else: # 表示tmp>li[j]
li[i]=tmp
break
else: # 如果i到达叶子节点,最后一层,j已经出界
li[i]=tmp
构建堆和排序(挨个出数):复杂度:O(nlog n)
def heap_sort(li):
# 构造堆:农村包围城市
n=len(li)
for i in range((n-2)//2,-1,-1): # i表示建堆的时候调整部分的根节点下标 #将这个小分支变成堆:进行向下调整
sift(li,i,n-1) # j唯一的作用就是防止j越界,high为最后一个元素时,也能满足该条件
# 堆构造完成
print(li)
# 接下来就是排序:挨个出数
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
最终堆排序的时间复杂度:O(nlog n)
7.堆排序内置模块---heapq
import heapq # q代表queue 优先队列(小的或者大的先出)。q模块是先进先出
import random
li=list(range(100))
random.shuffle(li) # 生成一个随机的列表
print(li)
heapq.heapify(li) #建一个小根堆
heapq.heappop(li) # heappop执行一次,往外弹出一个最小元素
n=len(li)
for i in range(n):
print(heapq.heappop(li),end=' ')
8.topk问题-------堆排序问题
现在有n个数,设计算法得到前k大的数(k<n),解决思路:
- 排序后切片 O(nlog n)+k (舍去k)
- 排序LowB三人组 O(kn)
- 堆排序 O(nlog k)
堆排序解决思路:
- 取列表前k个元素 建立一个小根堆。堆顶就是目前第k大的数。
- 依次向后遍历原列表,对于列表中的元素。如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶元素更换为该元素,并且对堆进行一次向下调整。
- 遍历完所有元素之后, 倒序弹出堆顶。
def sift(li,low,high): # 小根堆
'''
小根堆:向下调整
:param li: 列表
:param low: 堆顶元素的下标
:param high: 最后一个元素的下标
:return:
'''
tmp=li[low]
high=len(li)-1
i=low
j=2*i+1
while j<=high:
if j+1<=high and li[j+1]<li[j]:
j=j+1
if li[j] < tmp:
li[i]=li[j]
i=j
j=2*i+1
else: # li[j]>tmp
li[i]=tmp
break
else:
li[i]=tmp
def topk(li,k):
heap=li[0:k]
for i in range((k-2)//2,-1,-1):
sift(heap,i,k-1)
# 1.建堆:选择列表前k个元素,并建一个小根堆---农村包围城市
for i in range(k,len(li)):
if li[i]>heap[0]:
heap[0]=li[i]
sift(heap,0,k-1)
# 2.遍历列表后面的数据,用大于堆顶的数代替堆顶,然后进行一次向下调整
for i in range(k-1,-1,-1):
heap[0],heap[i]=heap[i],heap[0]
sift(heap,0,i-1)
# 3.出数:挨个出数
return heap
import random
li=list(range(1000))
random.shuffle(li)
print(topk(li,10))