快速排序及其思想应用(求第K大)

本文内容纯干货,假设读着有一定的基础,重在总结。整体行文逻辑如下:

  1. 如何优雅的写快排
  2. 第k大问题的几种解法
  3. 快排思想解第K大

快排:

快排的思想随便找本书就可以找到,二分思想,递归实现。

算法理解起来简单,但在面试时想优雅的写出来却不容易。下面是我见过的比较优雅的python实现:

def partition(arr, i, j):
    if i >= j:
        return
    loc, start, end = i, i, j
    while i < j:
        while i < j and arr[j] >= arr[loc]: j -= 1
        while i < j and arr[i] <= arr[loc]: i += 1
        if i < j:
            arr[i], arr[j] = arr[j], arr[i]

    arr[i], arr[loc] = arr[loc], arr[i]
    partition(arr, start, i - 1)
    partition(arr, i + 1, end)

arr = [3,1,4,5,7,2,1,4,5,0]
partition(arr, 0, len(arr) - 1)
print(arr)

实现中有几点需要注意:

  1. 记得备份锚点、开始、结束
  2. 先从后往前找,始终保持i
  3. 不要忘了把锚点的值移动到i的位置

第K大:

这个问题被面试问到的概率非常大,几乎和未来一周可能会下雨的概率接近。下面列出几种常见解法及说明:

先降序排序,再取第K个:排序时间复杂度最优是O(NlogN),取第K个是O(1),因此平均时间复杂度是O(NlogN)。代码略。

使用小顶堆,维护topK,最后取堆顶元素:小顶堆的维护是O(logK),虽然在遍历N的过程中可以通过条件判断,减少小顶堆的维护,实际平均时间复杂度是O(NlogK)。code如下:

import heapq


def getK(arr, K):
    min_hq = []
    for v in arr:
        if len(min_hq) < K or min_hq[0] < v:
            heapq.heappush(min_hq, v)
        if len(min_hq) > K:
            heapq.heappop(min_hq)
    return min_hq[0]


arr = [3, 1, 4, 5, 7, 2, 1, 4, 5, 0]
print(getK(arr, 3))

快排解第K大:

前面的解法平均到O(NlogK)了,我们是否可以期待平均时间复杂度更低的算法,如O(N)? yes.

利用快排的思想,找锚点,数据降序移动到两侧,看锚点是否是第K个,如果锚点靠左则去右侧递归查找,反之亦然。

复杂度???

平均时间复杂度应该是这样一个式子:O(N)+O(N/2)+O(N/4)+O(N/8)+....    

所以平均时间复杂度是O(N)

牛逼哥拉斯!那它是最好的吗?no, 我们只看到了平均情况,但它有最坏情况。

如:数据是升序的,假设每次锚点都取最左侧,那每次递归只缩小了1个数据的范围,相应时间复杂度会到O(N*(N-K))

类似的如果是降序的,锚点仍然在最左侧,每次只扩大了1个数据范围,时间复杂度会是O(N*K)

talk is cheap, show code:

def getK(arr, i, j, K):
    # if i >= j:
    #     return None
    loc, start, end = i, i, j
    while i < j:
        while i < j and arr[j] <= arr[loc]: j -= 1
        while i < j and arr[i] >= arr[loc]: i += 1
        if i < j:
            arr[i], arr[j] = arr[j], arr[i]

    arr[i], arr[loc] = arr[loc], arr[i]
    if i + 1 == K:
        return arr[i]
    elif i + 1 > K:
        return getK(arr, start, i - 1, K)
    else:
        return getK(arr, i + 1, end, K)


arr = [3, 1, 4, 5, 7, 2, 1, 4, 5, 0]
# print(getK(arr, 0, len(arr) - 1, 1))
print(getK(arr, 0, len(arr) - 1, 3))
# print(getK(arr, 0, len(arr) - 1, 4))
# print(getK(arr, 0, len(arr) - 1, 6))

综上:

  1. 快排思想是厉害,但是人都会有缺点。
  2. 有些东西看起来容易,动手写才会发现细节。
  3. 打工人,加油

 

 

 

 

 

 

 

你可能感兴趣的:(数据挖掘与机器学习,面试,数据结构,算法,python)