数据结构-堆-python
1、什么是堆
堆是用数组实现的二叉树,它没有使用指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
2、堆属性
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
例如一个大顶堆
3、堆的特点
-
堆顶一定是最大值或最小值,堆顶可以被快速访问,这也决定了堆常常被当做优先队列使用。
-
堆结构是完全二叉树, 因为在不使用指针情况下将一个一维数组的每个元素依次放入二叉树中所构建出的二叉树一定是完全二叉树。
-
当节点为 i 时,其左子节点为 2i + 1, 右子节点为2i + 2
既然是完全二叉树,那么对于每个节点来说,它的父节点和子节点在数组里的相对位置都可确定。经过简单分析1发现
节点在数组中的对应索引是该节点前所有节点总数。
节点所在行的节点总数是该行最左边节点的索引+1。
-
如果一个堆有 n 个节点,那么它的高度是 h = log2(n)
4、堆的方法
有两个原始操作用于保证插入或删除节点以后堆还是一个有效的最大堆或者最小堆:
- shiftUp(index): 如果index节点比它的父节点大(最大堆)或者小(最小堆),那么需要将它同父节点交换位置,然后进入父节点继续上操作。这样使这个节点在数组的位置上升。
- shiftDown(index): 如果index节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动。这个操作也称作“堆化(heapify)”。
shiftUp 或者 shiftDown 是一个递归的过程,而它的时间复杂度是 O(log n)。当然也可以是循环。
基于这两个原始操作还有一些其他的操作:
-
insert(value): 在堆的尾部添加一个新的元素,然后使用 shiftUp 来修复堆。
-
remove(): 移除并返回最大值(最大堆)或者最小值(最小堆)。为了将这个节点删除后的空位填补上,需要将最后一个元素移到根节点的位置,然后使用 shiftDown 方法来修复堆。
-
replace(index, value):将一个更小的值(最小堆)或者更大的值(最大堆)赋值给一个节点。由于这个操作破坏了堆属性,所以需要使用 shiftUp() 来修复堆属性。
-
peek() 方法,不用删除节点就返回最大值(最大堆)或者最小值(最小堆)。时间复杂度 O(1) 。
下面默认按最大堆(大顶堆)来写的
而且必然有瑕疵,望多多指出
5、shiftUp()与insert()
明确:shiftUp(index)只对一个数进行一不断修正,且这个数在数组的位置是明确的。
思路:将索引的数与父节点比较/交换大者,自下而上,当满足堆属性或到达顶部结束。
def _shiftUp1(self, index):
if index != 0:
parent = (index-1) >> 1 # num >> 1 -- num除2后向下取整
if self.Arg[index] > self.Arg[parent]:
self.Arg[index], self.Arg[parent] = self.Arg[parent], self.Arg[index]
self._shiftUp(parent)
def _shiftUp2(self, index):
while index != 0:
parent = (index-1) >> 1
if self.Arg[index] <= self.Arg[parent]:
return
else:
self.Arg[index], self.Arg[parent] = self.Arg[parent], self.Arg[index]
index = parent
def insert(self, value):
self.Arg.append(value)
index = self.__len__() - 1
self._shiftUp2(index)
_shiftUp1() O(logn) O(nlogn)
_shiftUp2() O(logn) O(logn)
6、shiftDown()与remove()
明确:remove()要移除堆顶元素,为保证堆结构不被破坏,将最后一个元素插到堆顶。然后用shiftDown()将堆顶修复到正确位置。
思路:shiftDown()与shiftUp()作用相反,将索引的数与子节点比较/交换最大值,其是自上向下,当满足堆属性或到达底部结束。
def _shiftDown(self, index):
Lnext = (index << 1) + 1
Rnext = (index << 1) + 2
leng = self.__len__()
if Rnext < leng and self.Arg[Lnext] > self.Arg[index] and self.Arg[Lnext] >= self.Arg[Rnext]:
self.Arg[Lnext], self.Arg[index] = self.Arg[index], self.Arg[Lnext]
self._shiftDown(Lnext)
elif Rnext < leng and self.Arg[Rnext] > self.Arg[index]:
self.Arg[Rnext], self.Arg[index] = self.Arg[index], self.Arg[Rnext]
self._shiftDown(Rnext)
def _shiftDown2(self, index):
leng = self.__len__()
while True:
Lnext = (index << 1) + 1
Rnext = (index << 1) + 2
if Rnext < leng and self.Arg[Lnext] >= self.Arg[index] and self.Arg[Lnext] >= self.Arg[Rnext]:
self.Arg[Lnext], self.Arg[index] = self.Arg[index], self.Arg[Lnext]
index = Lnext
elif Rnext < leng and self.Arg[Rnext] > self.Arg[index]:
self.Arg[Rnext], self.Arg[index] = self.Arg[index], self.Arg[Rnext]
index = Rnext
else:
return
def remove(self):
if self.__len__() > 0:
output = self.Arg[0]
self.Arg[0] = self.Arg.pop()
self._shiftDown2(0)
return output
_shiftDown() O(logN) O(nlogN)
_shiftDonw() O(logN) O(logN)
7、replace()
def replace(self, index, value):
if index <= self.__len__() - 1:
output = self.Arg[index]
self.Arg[index] = value
if value > output:
self._shiftUp(index)
elif value < output:
self._shiftDown(index)
return output
raise Exception('list index out of range')
8、类
class Head:
def __init__(self, Arg, mode='max'):
self.Arg = []
self._buildHead(Arg)
def _buildHead(self, arg):
if arg is not None:
for elem in arg:
self.insert(elem)
def peek(self):
if self.Arg:
return self.Arg[0]
def _shiftUp(self, index):
while index != 0:
parent = (index-1) >> 1
if self.Arg[index] <= self.Arg[parent]:
return
else:
self.Arg[index], self.Arg[parent] = self.Arg[parent], self.Arg[index]
index = parent
def _shiftDown(self, index):
leng = self.__len__()
while True:
Lnext = (index << 1) + 1
Rnext = (index << 1) + 2
if Rnext < leng and self.Arg[Lnext] >= self.Arg[index] and self.Arg[Lnext] >= self.Arg[Rnext]:
self.Arg[Lnext], self.Arg[index] = self.Arg[index], self.Arg[Lnext]
index = Lnext
elif Rnext < leng and self.Arg[Rnext] > self.Arg[index]:
self.Arg[Rnext], self.Arg[index] = self.Arg[index], self.Arg[Rnext]
index = Rnext
else:
return
def remove(self, index=0):
if self.__len__() > 1:
output = self.Arg[index]
self.Arg[index] = self.Arg.pop()
self._shiftDown(index)
return output
raise Exception('empty')
def insert(self, value):
self.Arg.append(value)
index = self.__len__() - 1
self._shiftUp(index)
def replace(self, index, value):
if index <= self.__len__() - 1:
output = self.Arg[index]
self.Arg[index] = value
if value > output:
self._shiftUp(index)
elif value < output:
self._shiftDown(index)
return output
raise Exception('list index out of range')
def __len__(self):
return self.Arg.__len__()
class MaxHeap(Head):
def __init__(self, Arg=None):
super().__init__(Arg, mode='max')
class MinHeap(Head):
def __init__(self, Arg=None):
super().__init__(Arg, mode='min')
def _shiftUp(self, index):
while index != 0:
parent = (index-1) >> 1
if self.Arg[index] >= self.Arg[parent]:
return
else:
self.Arg[index], self.Arg[parent] = self.Arg[parent], self.Arg[index]
index = parent
def _shiftDown(self, index):
leng = self.__len__()
while True:
Lnext = (index << 1) + 1
Rnext = (index << 1) + 2
if Rnext < leng and self.Arg[Lnext] <= self.Arg[index] and self.Arg[Lnext] <= self.Arg[Rnext]:
self.Arg[Lnext], self.Arg[index] = self.Arg[index], self.Arg[Lnext]
index = Lnext
elif Rnext < leng and self.Arg[Rnext] < self.Arg[index]:
self.Arg[Rnext], self.Arg[index] = self.Arg[index], self.Arg[Rnext]
index = Rnext
else:
return
def replace(self, index, value):
if index <= self.__len__() - 1:
output = self.Arg[index]
self.Arg[index] = value
if value < output:
self._shiftUp(index)
elif value > output:
self._shiftDown(index)
return output
raise Exception('list index out of range')
9、总结
从开始了解堆,到实现一个简单的堆。从递归到循环,从画图才能清晰结构到能打印一个简单的堆。
10、附上一个打印堆结构的方法
def __print(self):
import math
index = 0
deep = 0
l = L = int(math.log2(self.Arg.__len__()))
result = []
while index <= (2**L+1):
Temp = []
string = ''
mid = 3
for i in range(l):
mid = mid*2 + 2
mid = mid*' '
for i in range(2**deep):
try:
Temp.append(self.Arg[index+i])
except Exception:
Temp.append('~~')
if index != 0:
string += '{}' + mid
else:
string += '{}'
string = mid.__len__()//2*' ' + string + '\n'
string = string.format(*Temp)
result.append(string)
deep += 1
l -= 1
index = index * 2 + 1
print(*result)
11、说明
本文大量借鉴以下网站:
数据结构:堆(Heap)
数据结构-堆(heap)与堆的Python实现
数据结构之堆:堆的介绍与python实现——12
本文大量代码为自己写,可能错误一大堆。
完全二叉树的节点与左子节点在数组里的索引的关系是两者前面的所有节点求和的比值,而求和是一个等比求和,可以得出比值i/(2i+1) ↩︎