堆排序
- 数与二叉树
- 树是一种数据结构 比如目录结构
- 树是一种可递归定义的数据结构
- 树是由n个节点组成的集合:
- 如果n=0, 那这是一颗空树
- 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树。
一些概念:
根节点,叶子节点
树的深度
树的度:度是该节点分叉的数量,树的度则是该树当中有着最多分叉的节点的分叉数量,如上图树的度为3,因为在这个树当中,A和C有着最多的三个分叉,所以这个树的度为3
孩子节点,父节点: 节点之间的关系,C为H的父节点,H为C的子节点
子树: HIJK为子树
二叉树
度不超过2的树
每个节点最多只有两个子节点
左子节点和右子节点
- 满二叉树: 一个二叉树,如果每一层的节点数都达到最大值,则这个二叉树就是满二叉树
- 完全二叉树: 叶节点只能出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的若干位置的二叉树。
二叉树的储存方式(表达方式) - 链式存储方式
- 顺序存储方式 : 用列表存
堆: 一种特殊的完全二叉树结构
堆排序
堆的向下调整性质
- 假设根结点的子树都是堆,但是根结点不满足堆的性质。
- 可以通过一次向下调整使其变成一个堆
将根节点转移到合适的位置
堆排序的过程
- 1.建立一个堆
- 2.得到堆顶元素为最大元素
- 3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整使得堆变得有序
- 4.堆顶元素为第二大元素
- 5.重复步骤3,直到堆变空
构造堆
由下至上,由叶至根。先调整叶子节点,再调整根节点。
调整函数:
def sift(li, low, high):
# param li:列表(用列表表示堆)
# param low:堆的根节点位置
# param high: 堆的最后一个元素的位置
# return:
# 用i和j来代表我们当前所观察的两个位置
i = low # i最开始指向根结点
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 li[j] > tmp:
li[i] = li[j]
i = j # 往下看一层
j = 2 * i + 1
else: # tmp > li[j],把tmp放到i的位置上
li[i] = tmp
break
else:
li[i] = tmp # i位于最下面的子节点,并且下面没有继续的子节点。将堆顶的tmp放到i的位置上
def heap_sort(li):
n = len(li)
# 构造堆,由子节点到根节点
for i in range((n-2)//2, -1, -1):
# 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
时间复杂度:
sift函数是logn,走一个树的高度的过程。
heap_sort函数时间复杂度是O(nlogn)
内置模块
import heapq # q-> queue 优先队列
li = list(range(100))
random.shuffle(li)
heapq.heapify(li) # 建堆
heapq.heappop(li) # 向外弹出最小元素
topk问题
现在有n个数,设计算法得到前k大的数(k<n)
- 解决思路:
- 排序后切片 O(nlogn)
- 简单的排序算法三人组 O(kn)
- 堆排序思路 O(nlogk)
堆排序 topk问题思路
取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数。
依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略钙元素,如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。
比那里列表所以元素后,倒序弹出堆顶。
小根堆:
def sift(li, low, high):
# param li:列表(用列表表示堆)
# param low:堆的根节点位置
# param high: 堆的最后一个元素的位置
# return:
# 用i和j来代表我们当前所观察的两个位置
i = low # i最开始指向根结点
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 li[j] < tmp:
li[i] = li[j]
i = j # 往下看一层
j = 2 * i + 1
else: # tmp > li[j],把tmp放到i的位置上
li[i] = tmp
break
else:
li[i] = tmp # i位于最下面的子节点,并且下面没有继续的子节点。将堆顶的tmp放到i的位置上
def heap_sort(li):
n = len(li)
# 构造堆,由子节点到根节点
for i in range((n-2)//2, -1, -1):
# 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
def topk(li, k):
heap = li[0:k]
# 1. 建堆
for i in range((k-2)//2, -1, -1):
sift(heap, i, k-1)
# 2. 遍历
for i in range(k, len(li)):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
# 3. 出数
for i in range(k-1, -1, -1):
# i指向当前堆的最后一个元素
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i - 1) # i-1是新的high
return heap