今天抽时间实现的是查找第 K K K小元素算法。
问题描述:给定线性序集中 n n n个元素和一个整数 k ( 1 < = k < = n ) k (1<=k<=n) k(1<=k<=n),要求找出这 n n n个元素中第 k k k小的元素。
对于这个问题,一般可以直接想到的方法就是,先对这 n n n个数字进行排序,然后直接选取第k个元素,排序算法可以用我们之前实现过的归并排序或者快速排序,这样实现的算法时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
我们最近一直在学习分治思想,所以就考虑能否将分治的思想应用到第 k k k个元素的选择问题中,将时间复杂度降下来呢?
我们可以对数组进行类似快排那样将数据分成两段,得到排序后随机元 i i i的位置 p p p,如果 k k k等于 p + 1 p+1 p+1,则说明 i i i就是第 k k k小的元素,如果 k k k小于 p + 1 p+1 p+1,说明第 k k k小的元素在随机元 i i i的左侧,否则,第 k k k小的元素在随机元 i i i的右侧。
对选中的一段重复前面类似快排的操作,直到找到全局第 k k k个元素。具体内容结合下面的代码来看:
def Select_K(nums:list,left:int,right:int,k:int) -> int:
if left > right:
return
if k > len(nums) or k <= 0:
return 'k is out of range'
p = getIndex(nums,left,right)
if k == p+1:
return nums[p]
if k < p+1:
return Select_K(nums,left,p-1,k)
else:
return Select_K(nums,p+1,right,k)
#Select_K函数中使用了尾递归,可以消除栈溢出的情况,具体什么是尾递归,以后会有开一个专题来说
def swap(nums:list,i:int,j:int):
temp = nums[i]
nums[i] = nums[j]
nums[j] = temp
def getIndex(nums:list,left:int,right:int) -> int:
n = nums[left]
i,j = left,right
while True:
while i < right and nums[i] <= n:
i += 1
while nums[j] > n:
j -= 1
if i >= j:
break
else:
swap(nums,i,j)
nums[left] = nums[j]
nums[j] = n
return j
通过上面的方法,可以得到第 k k k小的值,但是在算法的复杂度上来看只是快排的一半,时间复杂度并没有降多少,但是经过大佬证明:当我们使用随机取元(RandomGetIndex)方法获得随机元 i i i的时候,平均的搜索时间可以降到线性时间内,也就是平均时间复杂度为 O ( n ) O(n) O(n) 。
那么,能否找到一种方法,使得最坏情况下的搜索也能在线性时间内完成呢?答案是可以的,但是我们将在下篇博客中进行讲解实现。
路漫漫其修远兮,吾将上下而求索。