堆排序
深度之眼讲解笔记:
二叉堆
堆的自我调整
插入节点:
以最小堆为例子,不满足规则的向下调整。插入0,比较0和5,不符合最小堆的规则,交换0和5,在0,3,6中判断,不满足规则,交换0和3,检查3和5,满足不用交换。在1,0,2,中判断,不满足规则,交换1和0.检查1,6,3,满足规则不交换。
删除节点:
从堆中删除节点一般是pop出堆顶,即弹出堆顶数。
交换1和10,删除末尾的1,(其实不需要管末尾这个数字就好)
对比10,2,3,将10与最小的数2交换(若与3交换,2和3依旧不满足),对比10和7,8,与最小的7 做交换。
构建二叉堆:
两种常见方法:自下而上,自上而下。这里讲最常见的自下而上操作。
首先看最下面的树,对比10,6,9,第一个堆顶是10,将10与6交换,10前一个数是3,对比3,2,8,交换3与2。再看1,6,5满足,不变。看7,1,2,交换1和7.
7交换下后进行检查,7,6,5不满足最小堆定义,向下调整。交换7和5.
堆的代码实现
数组存储:根据下标关系
当下标是从0开始:0,1,2……
比如父节点6,下标n为3,子节点9和10下标分别为7,8。关系为2n+1,2n+2.
知道子节点下标求父节点:-1或者-2后除以二向下取整。例如:子节点下标为n,则父节点为
当下标从1开始,1,2,3,……
则下标关系变化为:得到父节点下标n,子节点为2n, 2n+1
相应求父节点下标也要变化。
堆排序
最大堆,删除10时,跟2交换,排序。
复杂度
代码实现堆排序
方法1:
参考链接:https://www.jianshu.com/p/d174f1862601
from collections import deque
def swap_param(L, i, j):
L[i], L[j] = L[j], L[i]
return L
def heap_adjust(L, start, end):
temp = L[start]
i = start
j = 2 * i
while j <= end:
if (j < end) and (L[j] < L[j + 1]):
j += 1
if temp < L[j]:
L[i] = L[j]
i = j
j = 2 * i
else:
break
L[i] = temp
def heap_sort(L):
L_length = len(L) - 1 # 10-1=9
first_sort_count = int(L_length / 2)
# 4 这是最后一个父节点的下标(堆下标从1开始,但是python从0开始)
for i in range(first_sort_count): # 0,1,2,3
heap_adjust(L, first_sort_count - i, L_length)
for i in range(L_length - 1):
L = swap_param(L, 1, L_length - i)
heap_adjust(L, 1, L_length - i - 1)
return [L[i] for i in range(1, len(L))]
def main():
L = deque([50, 16, 30, 10, 60, 90, 2, 80, 70])
L.appendleft(0)
print(heap_sort(L))
if __name__ == '__main__':
main()
方法2:见下题
LeetCode215题目:数组中第k个最大元素
题目:
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-largest-element-in-an-array
class Solution:
# def __init__(self):
def findKthLargest(self, nums, k):
# # 快速排序
# self._k = len(nums) - k
# 10 - 2 = 8 求第2大的话就是下标为8(下标从0开始)
# return self.quicksort(nums, 0, len(nums)-1)
# 堆排序
self._k = len(nums) - k # 下标为8
return self.heapsort(nums)
def heapsort(self, nums):
self.build_heap(nums)
for i in range(len(nums)-1, self._k-1, -1): # range(9, 7, -1) # 即9,8
nums[i], nums[0] = nums[0], nums[i]
#这里排除最后一个元素
self.max_heapify(nums, 0, i)
# 0的位置是父节点下标,i的位置是排序的总长度
# 循环结束后,下标8存储倒数第2大元素
return nums[self._k] # 返回下标为8的元素
def build_heap(self, nums):
length = len(nums) # 长度是10
for i in range((length-1)//2, -1, -1): # 最后一个-1为倒序意思
# 节点的父节点是(长度-1)/2向下取整
# range(10,0,-1)意思是从10倒序,取到不到0的数。即10-1
# 这里就是循环从最后一个父节点(length-1)//2到第一个父节点0
# 建堆:从最后一个节点的父节点开始向上调整:
# 最后一个父节点下标为(length-1)//2
self.max_heapify(nums, i, length) # i是父节点下标
# 调整操作
def max_heapify(self, nums, i, length): # 最大堆 # 是父节点下标
left = 2 * i + 1 # 左孩子
right = 2 * i + 2 # 右孩子
if left < length and nums[left] > nums[i]:
# 如果做孩子存在且大于父节点
largest = left
else:
largest = i
if right < length and nums[right] > nums[largest]:
largest = right
if largest != i:
nums[i], nums[largest] = nums[largest], nums[i]
#子树也要调整
self.max_heapify(nums, largest, length)
if __name__=='__main__':
s = Solution()
print(s.findKthLargest([3,2,3,1,2,4,5,5,6], 4))