堆的定义:
父子结点是可以相等的,比如压入的都是相同的数也可以在堆里。
堆排序:
堆排序的原理:
堆排序是利用数组实现的。
大根堆和小根堆:
堆排序的步骤:
根据堆的性质(以最大堆举例):堆的根节点肯定是所有数据中最大的,但是他的所有子结点不是完全有序的,也就是不知道谁是第二个大的结点。这样就每次都拿出去根结点,剩下所有的子结点再重新组成堆,递归执行之前取根节点和维护堆的两个步骤。
堆的实现:
堆的实现主要是两个操作:上浮和下沉。
通过基础的上浮和下沉就可以实现堆的插入和删除操作:
删除最大元素:
插入元素:
堆的实现代码:
通知上面的分析已经知道,实现堆要实现四个操作:上浮、下沉、通过上浮实现的插入、通过下沉实现的添加。
class MaxHeap(object):
def __init__(self, maxsize=0):
self.maxsize = maxsize
self._elements = [None]*maxsize
self._count = 0
def __len__(self):
return self._count
# 添加
def add(self, value):
if self._count >= self.maxsize:
raise Exception('full,maxsize:{}'.format(self.maxsize))
self._elements[self._count] = value
self._count += 1
self._siftup(self._count-1) # 维持堆的特性
# 上浮
def _siftup(self, ndx):
if ndx > 0:
parent = int((ndx-1)/2)
if self._elements[ndx] > self._elements[parent]: # 如果插入的值大于 parent,一直交换
self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]
self._siftup(parent) # 递归
# 删除/提取最大值
def extract(self):
if self._count <= 0:
raise Exception('empty')
value = self._elements[0] # 保存 root 值
self._count -= 1
self._elements[0] = self._elements[self._count] # 最右下的节点放到root后siftDown
self._siftdown(0) # 维持堆特性
return value
# 下沉
def _siftdown(self, ndx):
'''
由于是最大堆,所以当一个结点要下沉时候,一定要和它两个子结点中更大的那一个交换位置
'''
left = 2 * ndx + 1
right = 2 * ndx + 2
# determine which node contains the larger value
tmp_ndx = ndx
# 左子结点大于根结点而且大于右子结点 才让左子结点上位
if (left < self._count and # 有左孩子
self._elements[left] >= self._elements[tmp_ndx] and
self._elements[left] >= self._elements[right]): # 原书这个地方没写实际上找的未必是largest
tmp_ndx = left
# 先判断左子结点不大于右子结点,所以只要右子结点大于根结点 右子结点就上位
elif right < self._count and self._elements[right] >= self._elements[tmp_ndx]:
tmp_ndx = right
# 如果试探的索引没有改变 也就是根结点比两个子结点都大 就不用交换位置了
# 如果改变了 就交换位置
if tmp_ndx != ndx:
self._elements[ndx], self._elements[tmp_ndx] = self._elements[tmp_ndx], self._elements[ndx]
# 递归调用 一个父结点下沉之后 不意味就不用再下沉了
# 所以要递归调用
self._siftdown(tmp_ndx)
def test_maxheap():
import random
n = 5
h = MaxHeap(n)
for i in range(n):
h.add(i)
for i in reversed(range(n)):
assert i == h.extract()
if __name__=="__main__":
test_maxheap()
堆排序的代码实现:
另外一种写法:
如果是给出一组数,直接给他们排序,那也就是没有了插入操作了。
由于直接就是一堆数,在建立一个堆的时候,就不用一个一个往里面插入元素并且上浮了,直接从最下面依次下沉找到它应该在的位置。建立堆之后,再依次删除根结点,用最下面的结点放到根结点的位置,再下沉产生新的根结点也就是最大值。
# 下沉操作
def heapify(arr, n, i):
'''
n:数组里属于堆数值个数 [0,n]这些都是属于堆
i:要下沉的结点索引
'''
largest = i
l = 2 * i + 1 # left = 2*i + 1
r = 2 * i + 2 # right = 2*i + 2
# 先看看左子结点是不是大
if l < n and arr[i] < arr[l]:
largest = l
# 右子结点是否大于左子结点或者根节点
if r < n and arr[largest] < arr[r]:
largest = r
# 如果发生了交换
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i] # 交换
# 继续下沉
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
# 建立最大堆
# 从最下面(数组属于堆的最有一个)的结点开始 让每个结点下沉
for i in range(n, -1, -1):
heapify(arr, n, i)
# 一个个交换元素
for i in range(n-1, 0, -1):
# arr[0]是最大的 把这个最大的和最后面的交换
arr[i], arr[0] = arr[0], arr[i] # 交换
# 让新的根结点下沉
# 新的堆的结点树是i
heapify(arr, i, 0)
arr = [ 12, 11, 13, 5, 6, 7]
heapSort(arr)
n = len(arr)
print ("排序后")
for i in range(n):
print ("%d" %arr[i])