堆排序的两种方法实现(Python)以及面试中关于堆排序的相关题目

目录

一、堆排序的两种做法

1、堆排序的第一种做法

2、堆排序的第二种做法:(掌握)

二、面试的相关题目

1、最小的k个数

2、数组中的第 K 个最大元素

3、前 K 个高频元素 


一、堆排序的两种做法

对堆排序完全没有了解的同学,可以先看一下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)]

部分代码参考:

https://leetcode-cn.com/problems/top-k-frequent-elements/solution/quan-shou-xie-jian-li-kge-yuan-su-de-zui-xiao-dui-/

https://leetcode-cn.com/problems/kth-largest-element-in-an-array/solution/partitionfen-er-zhi-zhi-you-xian-dui-lie-java-dai-/

https://www.runoob.com/python3/python-heap-sort.html

猜你喜欢

转载自blog.csdn.net/Matrix_cc/article/details/106606612