建堆的时间复杂度是O(N), 维护堆 heapify的时间复杂度是O(logn)–N个节点–共nlogn,故总的时间复杂度是nlogn.
堆可以看成近似的完全二叉树。堆排序的时间复杂度是O(nlgn)。—输入的其实还是数组,只是一种结构–思想。
最上面的根结点i从序号0开始算起的话,那么左节点是2i+1, 右节点是2i+2;从1开始算起的话,那么就是2i, 2i+1。
堆的高度是lgn,堆包括大顶堆和小顶堆。
大顶堆每一个根结点都比其子节点大–最上面的根结点最大,小顶堆每一个根节点都比子节点小----最上面的根节点最小。
Max-Heapify就是在维护最大堆的过程,让A[i]逐级下降,做一个临近根结点和子节点的交换只需要O(1)的时间复杂度。
每个孩子节点的子树的大小至多为2/3n。
通过递归式子:T(n) <= T(2n/3) + O(1)
可以得: 最大堆的时间复杂度是T(n) = O(lgn)
也就是对于一个树高为h的最大堆来说,时间复杂度是O(h)。
构造一个最大堆的时间复杂度是O(n)
堆排序用的是最大堆,优先队列用的是最小堆。
堆排序的顺序就是 建堆—排除根----建堆(建堆就是从尾部开始heapify)
建堆
建最大堆的顺序是从左到右,从下到上,每一个向下移动都要跟最大的比,依次递归,直到比下面的大。
伪代码:输入数组A, 假定left(i)和right(i)都是最大堆,但是A(i)不一定构成最大堆。
—反正就是选择一个最大的作为largest,放在每棵树的根结点。
def Max_heapify(A, i):
l = left(i)
r = right(i)
if l <= A.heap_size and A[l] > A[i]:
largest = l
else:
largest = i
if r <= A.heap_size and A[r] > A[largest]:
largest = r
if largest != i:
exchange A[i] with A[largest]
max_heapify(A, largest)
def Build_max_heap(A):
A.heap_size = A.length
for i in range(A.length/2, 1):
Max_heapify(A, i)
建堆是从左下角开始,依次往上,但是每次都会判断是否交换,交换后子树是否为最大堆,不是就递归下去。
堆排序
def Heap_sort(A):
Build_max_heap(A)
for i in range(A.length, 2):
A[1], A[i] = A[i], A[1]
A.heap_size = A.heap_size - 1
Max_heapify(A, 1)
交换之后还要麻烦Max_heapify(A, i)再排列成最大堆的形式。
序号很关键,通过画图可以得知,满二叉树每层的最左的节点的序号是上层的两倍或是两倍加1。
最大堆
def max_heapify(A, i, heap_size):
l = 2*i+1
r = 2*i+2
if l < heap_size and A[l] > A[i]:
largest = l
else:
largest = i
if r < heap_size and A[r] > A[largest]:
largest = r
if largest != i:
A[i], A[largest] = A[largest], A[i]
max_heapify(A, largest, heap_size)
def build_max_heap(A, heap_size):
for i in range(heap_size//2, -1, -1):
# 前面一半逆序,因为后面一半一定是叶子结点,自底向上。
max_heapify(A, i, heap_size)
def heapsort(A):
heap_size = len(A)
build_max_heap(A, heap_size)
for i in range(len(A)-1, 0, -1):
A[0], A[i] = A[i], A[0]
heap_size -= 1
max_heapify(A, 0, heap_size)
A = [27,17,3,16,13,10,1,5,7,12,4,8,9,0]
heapsort(A)
print(A)
最小堆
下面多用了个空间,其实不需要,如上即可。
def heapify(nums, index):
left = 2 * index + 1
right = 2 * index + 2
minest = index
if left < len(nums) and nums[minest] > nums[left]:
minest, left = left, minest
if right < len(nums) and nums[minest] > nums[right]:
minest, right = right, minest
if minest != index:
nums[index], nums[minest] = nums[minest], nums[index]
heapify(nums, minest)
def build_heap(nums):
for i in range(len(nums) // 2, -1, -1):
heapify(nums, i)
def Min_heap(nums, k):
out = []
while len(nums) > 0:
build_heap(nums) # 每删除一个就要选出一个小老弟。所以要重新排序。
out.append(nums[0])
nums.pop(0)
return out
应用
对于 topk 问题:最大堆求topk小,最小堆求 topk 大。
topk小:构建一个 k 个数的最大堆,当读取的数小于根节点时,替换根节点,重新塑造最大堆
topk大:构建一个 k 个数的最小堆,当读取的数大于根节点时,替换根节点,重新塑造最小堆—当剩余的数目等于K的时候,那么就得到了想要的topk.
前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回
套用库
class Solution(object):
def topKFrequent(self, nums, k):
num_dict = {}
for num in nums:
if num not in num_dict:
num_dict[num] = 1
else:
num_dict[num] += 1
num_list = sorted(num_dict, key = lambda x : -num_dict[x])
# 加了负号就是从大到小, 注意此处变成了列表!!
return num_list[0:k]
最小堆
# 最小堆
class Solution(object):
def topKFrequent(self, nums, k):
num_dict = {}
for num in nums:
if num not in num_dict:
num_dict[num] = 1
else:
num_dict[num] += 1
# 次数不一定是唯一的
key_dict = {}
num_list = []
for j in num_dict:
if num_dict[j] not in key_dict:
key_dict[num_dict[j]] = [j]
else:
key_dict[num_dict[j]].append(j)
num_list.append(num_dict[j])
def Min_heap(nums, k):
def heapify(nums, index):
left = 2*index+1
right = 2*index+2
minest = index
if left < len(nums) and nums[minest] > nums[left]:
minest, left = left, minest
if right < len(nums) and nums[minest] > nums[right]:
minest, right = right, minest
if minest != index:
nums[index], nums[minest] = nums[minest], nums[index]
heapify(nums, minest)
def build_heap(nums):
for i in range(len(nums)//2, -1, -1):
heapify(nums, i)
while len(nums) > k:
build_heap(nums) # 注意是一上来就建堆。
nums.pop(0)
return nums
nums_out = Min_heap(num_list, k)
out = []
for i in nums_out:
out.append(key_dict[i][0])
if len(key_dict[i]) > 1:
key_dict[i].pop(0)
return out
快速排序
## 快速排序
class Solution(object):
def topKFrequent(self, nums, k):
def quick_partition(nums, index1, index2):
index = index1 - 1
num_cmp = nums[index2]
for i in range(index1, index2+1):
if nums[i] <= num_cmp:
index += 1
nums[index], nums[i] = nums[i], nums[index]
return index
num_dict = {}
for num in nums:
if num not in num_dict:
num_dict[num] = 1
else:
num_dict[num] += 1
# 次数不一定是唯一的
key_dict = {}
num_list = []
for j in num_dict:
if num_dict[j] not in key_dict:
key_dict[num_dict[j]] = [j]
else:
key_dict[num_dict[j]].append(j)
num_list.append(num_dict[j])
index1 = 0
index2 = len(num_list)-1
index = -1
while index != len(num_list)-k:
index = quick_partition(num_list, index1, index2)
if index < len(num_list)-k:
index1 = index + 1
else:
index2 = index - 1
choose_num = num_list[index::]
out = []
for i in choose_num:
out.append(key_dict[i][0])
if len(key_dict[i]) > 1:
key_dict[i].pop(0)
return out