目录
一、堆排序的两种做法
对堆排序完全没有了解的同学,可以先看一下B站上的这个视频
https://www.bilibili.com/video/BV1Eb41147dK/
同时,堆排序的第一种做法也是基于这个视频的。
1、堆排序的第一种做法
def heapify(arr,n,i):
large = i #父节点的下标为i
left_child = 2*i+1 #左孩子下标
right_child = 2*i+2
if left_child<n and arr[left_child]>arr[large]:#如果左孩子的值大于父节点的值,将下标互换
large = left_child
if right_child<n and arr[right_child]>arr[large]:
large = right_child
if large != i:#如果下标换了,将最大子节点和父节点的值互换
arr[large],arr[i] = arr[i],arr[large]
heapify(arr,n,large)#将最大值的那个子树继续递归
def build_heap(arr,n):
last_child = n-1
last_parent = (last_child-1)//2 #从最后一个叶子节点的父节点开始以此向上调整
for i in range(last_parent,-1,-1):
heapify(arr,n,i)
def heap_sort(arr,n):
build_heap(arr,n)
for i in range(n-1,-1,-1):
arr[0],arr[i] = arr[i],arr[0] #每次将最大的节点(根节点)与最后一个节点交换,然后去掉最后一个节点,继续调整为最大堆
heapify(arr,i,0)
return arr
arr = [0, 8, 6, 2, 4, 9, 1, 4, 6]
n = len(arr)
res = heap_sort(arr,n)
print(res)
2、堆排序的第二种做法:(掌握)
def heapify(arr):
n = len(arr)
for i in range(n//2,-1,-1):
shiftDown(arr,n,i)
def shiftDown(arr, n, k):
while 2 * k + 1 < n:
j = 2 * k + 1
if j + 1 < n and arr[j + 1] > arr[j]: #保证右孩子不超过数组长度,同时找出左右孩子中最大的那个孩子。这里大顶堆用>, 小顶堆用<
j += 1 #如果右孩子大于左孩子,就使得使得j指向最大的那个元素,即指向右孩子
if arr[k] >= arr[j]: #如果左孩子是最大的那个元素,但是父节点又大于左孩子,这样就满足大顶堆条件,就不需要换,直接退出即可。同样的,大顶堆用>=, 小顶堆用<=·
break
arr[k], arr[j] = arr[j], arr[k] #将最大的子节点和父节点互换
k = j #互换后它的子树可能就不满足大顶堆,所以将子节点的下标赋值给父节点,继续循环,保证它的子树也是大顶堆 5
def heapSort(arr):
n=len(arr)
heapify(arr)
print("堆化:",arr)
for i in range(n-1):
arr[n-i-1],arr[0] = arr[0],arr[n-i-1]
# print("交换最小值后:",arr)
shiftDown(arr,n-i-1,0)
# print("调整后:",arr)
arr = [3,2,1,9,4,7,8]
heapSort(arr)
print("排序后:",arr)
二、面试的相关题目
1、最小的k个数
这道题可以使用堆排序,不过如果在面试的时候,面试官一般是希望使用快排来完成,下面就写出这两种写法:
堆排序的第一种方法实现:
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
n = len(arr)
self.build_heap(arr, n)
pos = k
for i in range(n - 1, pos-1, -1):
arr[0], arr[i] = arr[i], arr[0] # 每次将最大的节点(根节点)与最后一个节点交换,然后去掉最后一个节点,继续调整为最大堆
self.heapify(arr, i, 0)
return arr[:k]
def heapify(self, arr, n, i):
large = i # 父节点的下标为i
left_child = 2 * i + 1 # 左孩子下标
right_child = 2 * i + 2
if left_child < n and arr[left_child] > arr[large]: # 如果左孩子的值大于父节点的值,将下标互换
large = left_child
if right_child < n and arr[right_child] > arr[large]:
large = right_child
if large != i: # 如果下标换了,将最大子节点和父节点的值互换
arr[large], arr[i] = arr[i], arr[large]
self.heapify(arr, n, large) # 将最大值的那个子树继续递归
def build_heap(self,arr, n):
last_child = n - 1
last_parent = (last_child - 1) // 2 # 从最后一个叶子节点的父节点开始以此向上调整
for i in range(last_parent, -1, -1):
self.heapify(arr, n, i)
堆排序的第二种方法实现:
第二种的思路可以运用在后面的一些题目,所以也要掌握
1、取 nums 前 K 个元素建立大小为 K 的最大堆
2、剩余 k+1 到 N 个元素依次和堆顶比较,如果比堆顶小,则替换当前堆顶,并维护最大堆
3、最终数组里前k个就是最小的k个数
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
n = len(arr)
self.build_heap(arr,k)
for i in range(k,n):
if arr[0]>arr[i]:
arr[0] = arr[i]
self.heapify(arr,k,0)
return arr[:k]
def heapify(self,arr,n,i):
while 2*i+1<n:
j=2*i+1
if j+1<n and arr[j+1]>arr[j]:
j+=1
if arr[i]>=arr[j]:
break
arr[i],arr[j] = arr[j],arr[i]
i = j
def build_heap(self,arr,n):
for i in range(n//2,-1,-1):
self.heapify(arr,n,i)
快速排序的实现:(重点掌握)
class Solution:
def smallestK(self, arr: List[int], k: int) -> List[int]:
if k>=len(arr):
return arr
low = 0
high = len(arr)-1
while low<high:
index = self.partition(arr,low,high)
if index == k-1:
break
if index<k-1:
low = index+1
else:
high = index-1
return arr[:k]
def partition(self,num,low,high):
temp = num[low]
while low<high:
while low<high and temp<=num[high]:
high-=1
num[low] = num[high]
while low<high and temp>=num[low]:
low+=1
num[high] = num[low]
num[low]=temp
return low
2、数组中的第 K 个最大元素
堆排序,这里还是使用第二种写法的思路,不过这次要换成最小堆。
注意:前 k 大,用小根堆,求前 k 小,用大根堆
思路:
1、取 nums 前 K 个元素建立大小为 K 的最小堆
2、剩余 k+1 到 N 个个元素依次和堆顶比较,如果比堆顶大,则替换当前堆顶,并维护最小堆
3、最终最小堆里是前 K 大的元素,堆顶为前 K 大的元素中最小的元素,即 Kth 大的元素
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
n = len(nums)
self.heapify(nums,k)
for i in range(k,n):
if nums[0]<nums[i]:
nums[0] = nums[i]
self.shift(nums,k,0)
return nums[0]
def heapify(self,arr,k):
for i in range(k//2,-1,-1):
self.shift(arr,k,i)
def shift(self,arr,n,i):
while 2*i+1<n:
j = 2*i+1
if j+1<n and arr[j+1]<arr[j]:
j+=1
if arr[i]<=arr[j]:
break
arr[i],arr[j] = arr[j],arr[i]
i = j
快速排序实现:
题目说的是数组排序后的第k个最大的元素,如果按照升序排序的话,那么从右边往左边数第 k 个元素(从 11 开始),那么从左向右数就是 len(arr)-k
所以快排的一般写法如下:
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
left = 0
right = len(nums)-1
target = len(nums)-k
while True:
index = self.partition(nums,left,right)
if index==target:
return nums[index]
if index<target:
left = index+1
else:
right = index-1
def partition(self,nums,left,right):
temp = nums[left]
while left < right:
while left<right and temp<=nums[right]:
right-=1
nums[left]=nums[right]
while left<right and temp>=nums[left]:
left+=1
nums[right] = nums[left]
nums[left] = temp
return left
这样运行是可以通过的,但是时间却很慢
这是由于测试用例会出现极端的例子,如顺序和逆序,这样快排的复杂度就变成了O(n²),所以对快排进行一下优化
就是对 temp 随机挑选,仔细看partition的实现。
import random
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
left = 0
right = len(nums)-1
target = len(nums)-k
while True:
index = self.partition(nums,left,right)
if index==target:
return nums[index]
if index<target:
left = index+1
else:
right = index-1
def partition(self,nums,left,right):
random_index = random.randint(left, right)
nums[random_index], nums[left] = nums[left], nums[random_index]
pivot = nums[left]
j = left
for i in range(left + 1, right + 1):
if nums[i] < pivot: #小于pivot的元素都被交换到前面
j += 1
nums[i], nums[j] = nums[j], nums[i]
nums[left], nums[j] = nums[j], nums[left] #在之前遍历的过程中,满足 [left + 1, j] < pivot,并且 (j, i] >= pivot, 交换以后 [left, j - 1] < pivot, nums[j] = pivot, [j + 1, right] >= pivot
return j
3、前 K 个高频元素
这道题用一般的解法是可以写出来的,代码如下,代码很简单,就不做解释了
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
dict = {}
for i in nums:
if i not in dict:
dict[i] = 1
else:
dict[i] += 1
dict_sort = sorted(dict.items(), key = lambda x: x[1],reverse = True)
res = []
for i in range(k):
res.append(dict_sort[i][0])
return res
这道仍然可以用堆排序的第二种方法和上两题的思路:
class Solution(object):
def topKFrequent(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
def shift(i,k):#维护最小堆
while True:
t=2*i+1
if t >= k :
return
if t+1<k and hashlist[t][1]>hashlist[t+1][1]:
t=t+1
if hashlist[t][1]<hashlist[i][1]:
hashlist[t],hashlist[i]=hashlist[i],hashlist[t]
i=t
else:
return
#建立哈希表
hashmap={}
for i in nums:
hashmap[i]=hashmap.get(i,0)+1
#print(hashmap)
#将哈希表转为二维列表
hashlist=[ [key,v] for key, v in hashmap.items() ]
#print(hashlist)
#建立K个元素的最小堆
for i in range(k/2,-1,-1):
shift(i,k)
#剩余依次和堆顶比较
for i in range(k,len(hashlist)):
if hashlist[i][1]>=hashlist[0][1]:
hashlist[0]=hashlist[i]
shift(0,k)
return [hashlist[i][0] for i in range(k)]
部分代码参考: