堆排序
堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。结构如下:
在Python中使用层序遍历的结果将完全二叉树保存在数组中如:
(上面两个图来自: 博客园 dreamcatcher-cx)
使用堆排序,特征是使得最小或最大的元素总是在根部,即在数组的头部。所以也可以用作优先级队列。
实现代码如下
class HeapSort:
def __init__(self, mode, data=None):
"""
堆排序
使用头节点作为哨兵,索引从1开始才是数据
:param mode: 1 or 0
1: 大堆模式
0: 小堆模式
:param data: 数组: (不是堆排序的自动转化为堆排序)
"""
if isinstance(data, list):
self.data = [0] + data
else:
self.data = [0]
# 堆排序模式
self.mode = mode
# 堆内元素个数
self.length = len(self.data) - 1
# 初始化堆
self.build_heap()
def info(self):
"""
显示堆内的的元素
:return:
"""
print('堆顶->', self.data[1:], '<-堆尾')
def build_heap(self):
"""
初始化堆, 堆是一个完全二叉树属性。并用数组的层序遍历表示完全二叉树
:return:
"""
pos = self.length // 2
while pos > 0:
self.adjust_down(pos)
pos -= 1
def adjust_down(self, root: int):
"""
下沉算法:
大堆: 父节点大于孩子节点,退出。
父节点小于孩子节点,最大的孩子节点和父节点交换值,父节点位置变为原先孩子节点的位置,继续下沉。
小堆: 父节点小于孩子节点,退出。
父节点大于孩子节点,最小的孩子节点和父节点交换值,父节点位置变为原先孩子节点的位置,继续下沉。
:param root: 当前父节点位置
:return:
"""
# 将第一个的节点放入哨兵
self.data[0] = self.data[root]
# 找它儿子
pos = 2 * root
# 如果它有儿子
while pos <= self.length:
if self.mode:
# 大堆算法
# 如果有右儿子 且 右儿子大
conditions1 = pos < self.length and self.data[pos] < self.data[pos + 1]
# 它爸比它大
conditions2 = self.data[0] >= self.data[pos]
else:
# 大堆算法
# 如果有右儿子 且 右儿子小
conditions1 = pos < self.length and self.data[pos] > self.data[pos + 1]
# 它爸比它小
conditions2 = self.data[0] <= self.data[pos]
if conditions1:
pos += 1
if conditions2:
break
else:
self.data[root] = self.data[pos]
root = pos
# 继续找它儿子
pos *= 2
# 找到不能再找了 最后那个节点就是它应该在的地方
self.data[root] = self.data[0]
def adjust_up(self, child):
"""
上浮算法:
大堆: 插入节点小于父节点,退出。
插入节点大于父节点,插入节点和父节点交换值,插入节点位置变为父节点的位置,继续上浮。
小堆: 插入节点大于父节点,退出。
插入节点小于父节点,插入节点和父节点交换值,插入节点位置变为父节点的位置,继续上浮。
:param child: 插入节点的孩子的位置
:return:
"""
def conditions(mode, data):
if mode:
# 大堆算法
# 如果它爸不是哨兵(有爸) 而且 它大于它爸
return pos > 0 and data[pos] < data[0]
else:
# 小堆算法
# 如果它爸不是哨兵(有爸) 而且 它小于于它爸
return pos > 0 and data[pos] > data[0]
# 将新加入的节点放入哨兵
self.data[0] = self.data[child]
# 找到它爸
pos = child // 2
while conditions(self.mode, self.data):
# 它爸站在它的位置上
self.data[child] = self.data[pos]
# 它站在它爸的位置上
child = pos
# 继续找它爸
pos = child // 2
# 找到不能再找了 最后站在的那个地方 就是该节点要呆的地方
self.data[child] = self.data[0]
def push(self, x: int):
"""
堆排序插入。
插入操作完成后,长度属性自增1,执行当前位置的上浮算法
:param x: 整数值
:return:
"""
self.data.append(x)
self.length += 1
self.adjust_up(self.length)
def pop(self):
"""
堆排序弹出根元素
弹出操作完成后,删除该位置,长度属性自减1,重新建立堆
:return:
"""
if self.length:
result = self.data[1]
del self.data[1]
self.length -= 1
self.build_heap()
return result
else:
raise ValueError('heap have no element.')
if __name__ == '__main__':
t = [87, 45, 78, 32, 17, 65, 53, 9]
# mode: 0使用小堆 1使用大堆
# data: 若传入数组,则自动转为堆排序;若不传,则是一个空的堆排序容器
h = HeapSort(mode=0, data=t)
h.info()
h.pop()
h.info()
h.pop()
h.info()
h.pop()
h.info()
>> 堆顶-> [9, 17, 53, 32, 87, 65, 78, 45] <-堆尾
>> 堆顶-> [17, 53, 32, 87, 65, 78, 45] <-堆尾
>> 堆顶-> [32, 53, 45, 65, 78, 87] <-堆尾
>> 堆顶-> [45, 53, 65, 78, 87] <-堆尾