[ python ] 快速选择算法 + leetcode最小K个数

部分内容转载:https://blog.csdn.net/dacixie/article/details/79477421
感谢博主的整理

快速选择:

快速选择(英语:Quickselect)是一种从无序列表找到第k小元素的选择算法。它从原理上来说与快速排序有关。与快速排序一样都由托尼·霍尔提出的,因而也被称为霍尔选择算法。同样地,它在实际应用是一种高效的算法,具有很好的平均时间复杂度,然而最坏时间复杂度则不理想。快速选择及其变种是实际应用中最常使用的高效选择算法。

快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。

快速排序一样,快速选择一般是以原地算法的方式实现,除了选出第k小的元素,数据也得到了部分地排序。

如果不理解快速选择的过程,因为快速选择和快速排序的过程可以说是很相近,所以建议大家先看一个短短10分钟由北京大学陈斌老师的快速排序算法讲解视频:

快速排序算法讲解
在这里插入图片描述

快速选择的总体思路与快速排序一致,选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。不同的是,快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。这降低了平均时间复杂度,从O(n log n)至O(n),不过最坏情况仍然是O(n2)。

与快速排序一样,快速选择一般是以原地算法的方式实现,除了选出第k小的元素,数据也得到了部分地排序。

Quick Select的目标是找出第k大元素,所以

若切分后的左子数组的长度 > k,则第k大元素必出现在左子数组中;
若切分后的左子数组的长度 = k-1,则第k大元素为pivot;
若上述两个条件均不满足,则第k大元素必出现在右子数组中。

代码函数如下:

def qselect(A,k):    
    if len(A)<=k:return A    #若数组长度小于k,则输出前k个最小数构成的数组
    pivot = A[0]    #中位数
    left = [pivot] + [x for x in A[1:] if x<=pivot]  #中位数左边的数组
    llen = len(left)    #中位数左边数组的长度,全是比中位数小的数
    if llen==k:    #如果刚好为k个,则第k个是第k个最小数
        return left    #输出数组
    if llen > k:    #如果比k大,则需要对这个数组进一步切分
        return qselect(left, k)    #递归继续划分
    else:    
        right = [x for x in A[1:] if x > pivot] #如果长度小于k,那肯定在中位数右边的数组,也就是比中位数大
        return left + [pivot] + qselect(right, k-llen-1) #返回左边+中位数+右边划分
#所以划分一共有三种情况,恰好k个,比k个少,比k个多,分别对应左、中、右;
# 划分函数需要两个参数:k/k-llen,以及被划分的数组。

刚好leetcode有一道题:
设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]

class Solution:
    def smallestK(self, arr: List[int], k: int) -> List[int]:
        def helper(arr, k):
        	if k == 0:#个人感觉很有必要
        		return []
            if len(arr) <= k:
                return arr
            # 快速选择
            midnum = arr[0]  # 中位数取数组第一位
            left = [e for e in arr[1:] if e <= midnum]
            len_left = len(left)  # 左数组的长度
            if len_left == k:
                return left
            elif len_left > k:
                return helper(left, k)  # 继续分
            else:  # 长度比k还小,只能把中位数右边的也加进来了
                right = [e for e in arr[1:] if e > midnum]
                return left + [midnum] + helper(right, k - len_left-1)  # left已经被算进里面了,所以只需要在right里面找k-lenleft-1就好
        return helper(arr,k)

如果要找出最大的K个数呢?魔改一下代码

def helper(arr, k):
    if k == 0:#个人感觉很有必要
        return []
    if len(arr) <= k:
        return arr
    # 快速选择
    midnum = arr[0]  # 中位数取数组第一位
    right = [e for e in arr[1:] if e >= midnum]
    len_right = len(right)  # 右数组的长度
    if len_right == k:
        return right
    elif len_right > k:
        return helper(right, k)  # 继续分
    else:  # 长度比k还小,只能把中位数左边的也加进来了
        left = [e for e in arr[1:] if e < midnum]
        return right + [midnum] + helper(left, k-len_right-1)  # left已经被算进里面了,所以只需要在right里面找k-lenleft-1就好

猜你喜欢

转载自blog.csdn.net/Sgmple/article/details/110928514
今日推荐