快排扩展,Partition函数的应用(二)

快排扩展,Partition函数的应用

  本文将介绍快排中partition函数的另一种常见的应用,如果对上篇文章感兴趣,可以点击回去看一看:快排扩展,Partition函数的应用(一)。在上一篇文章中,我结合题目讲解了问题的转换过程,如何分析问题把问题转换成我们已知的算法,乃至是排序算法,然后尽可能少的改动已经熟悉的代码解决问题。
  我们这篇文章则对partition函数再上一个层次,我们通过对架构的更改,partition函数还是快排里的函数,但是调用者变了,这个函数就能干其他不同的事情。这也是算法扩展里一个很重要的步骤。我们仍然从问题中引入。

问题描述

  输入n个整数,找出其中第k小的数。例如输入4,5,1,6,2,7,3,8这8个数字,则第4小的数字是4。

问题分析

  如果要选择出第k小的数字,大家往往会把这类问题和排序联系起来,排序中有一类排序,基于选择的排序,就很适合选择出第k小的数字。首先是简单选择的排序,就是每次从剩下的序列中选出最小的,然后把选择出来的数字移除序列,放在数组的最前面。这个过程可能大家很容易想到。第一次选择出来的自然就是第1小的,第k次选择的自然就是第k小的。这个过程没什么好讲的,而且分析也很简单,每次选择的过程时间复杂度是 O ( n ) O(n) ,选k次,总的复杂度就是 O ( k n ) O(kn)
  基于选择的排序里第二种就是堆排序,堆排序的特点就是最好情况和最坏情况一致,都是 O ( n l g n ) O(nlgn) 的原地排序。而且这个堆操作也很有意思,后面有机会会专门讲解。但是堆排序在这里也只能是建堆,从堆里每次挑出当前最小的,再把挑出来最小的元素拿出堆,继续调整堆,然后依次循环。
  这个堆排序的特点大家一定要记住的,上述过程就是堆排序的子过程。建堆的过程复杂度是 O ( n ) O(n) 的,然后每次拿出最小的元素,需要调整堆,调整堆的过程复杂度是 O ( l g n ) O(lgn) ,这个调整需要进行k次,所以整个过程的复杂度就是 O ( n ) + O ( k l g n ) O(n)+O(klgn)
  这两种基于选择的排序有一个共同的特点就是在找第k小的元素的时候,顺便把比它更小的元素也都找出来了。这里就会有小技巧,如果找的第k小的元素,k非常大的话,我们可以从大往小找,因为找第k小的元素就等价于找n-k+1大的元素。
  但是除了这两种基于选择的排序能想到外,其他的排序,每次能够确保把一个元素放到正确位置上的排序也能干这个事情。因为这个问题就是给这个序列排序,直到把第k个位置的元素找到之后,这个排序就提前终止。这么一分析,又多出两种排序,冒泡排序和快排。
  冒泡排序的分析和简单选择排序很类似,这里就不做分析。需要注意的就是冒泡是稳定的排序,而选择排序都是不稳定的,简单选择排序移动的次数少,冒泡排序移动元素的次数多。
  再来分析快排就很有意思了,快排每次把一个元素放到自己排序的最终位置上,这个过程是通过partition函数来完成的,但是这个过程是不稳定的,不能确定到底是把第几个元素放到最终位置上的。但是这个partition函数不只是把一个元素放到最终位置上这么简单,它同时做到了这个元素前面的比它小,后面的比它大。这么一来,我们就可以简化问题了,我们要找第k小的元素,结果一趟partition之后,我们找到了第i个元素,这就有三种情况了:

  1. i和k相等,也就是说找到的元素就是第k小的,可以直接返回了。
  2. k<i 也就是说,要找的元素在i元素的前面,那么我们就可以直接丢掉i以及后面的元素,然后在前i-1个元素之中查找。
  3. i<k 这种情况下,要找的元素在第i个元素后面,我们就不需要查找前i个元素,只需要查找第i个元素之后的序列。
      可见这是一个把问题规模缩小的典型过程,这就是典型的分治算法了。我们每次去执行这个过程,然后把问题规模缩小,直到找到第k小的元素。我们可以很容易的根据思路写出代码,当然如果为了方便调用,可以把一个函数写成两个。

python代码

def findKthSmallNumber(array,k,start,end):
	# 这里k从0开始,如果需要从1开始,第一次调用的时候,查找k-1
    if k>=len(array) or k<0:
        return
    while True:
        i=partition(array,start,end)
        if i==k:
            return array[i]
        if i<k:
            return findKthSmallNumber(array,k,i+1,end)
        else:
            return findKthSmallNumber(array,k,start,i-1)

  最容易的是根据思路写出如上的递归代码,该代码可以写成非递归的。

def findKthSmallNumber(array,k):
    if k>=len(array) or k<0:
        return
    start=0
    end=len(array)-1
    i=partition(array,start,end)
    while (i!=k):
        if i<k:
            end=i-1
        else:
            start=i+1
        i = partition(array, start, end)
    return i

  这里我没给出partition函数,因为partition写法有好几种,大家自行选择,我在上篇文章写了一种,有兴趣可以参考。
  到最后我们对这个算法来进行复杂度分析。这个算法的最好情况就是第一次直接找到了第k小的元素,直接返回,这个没有分析的必要。我们来分析最坏情况,最坏情况下,该问题退化为选择排序的解法,这一点和快排很类似,就是每次只能把一个元素放到正确的位置,而且这个元素不是我们要找的。这种情况下,复杂度也和选择排序类似,是O(nlgn)。复杂度一样,但是可能比选择排序更差,要在五个元素中找第三小的,选择排序找到第1小的,第2小的,第3小的。可能这种算法找到的是第1小的,第5小的,第4小的,第2小的,第3小的。
  好在是和快排一样最坏情况很少出现,平均复杂度接近最好的情况而不是最坏的情况。我们来分析平均情况。假设问题规模以一个比例缩小,1/2太好,我们就假设是1/10,据此我们可以对分治复杂度列出递推式 T ( n ) = T ( 9 n 10 ) + O ( n ) T(n)=T(\frac{9n}{10})+O(n) ,然后根据主定理得出算法的复杂度是 O ( n ) O(n) 。但是大家需要注意的是,这个 O ( n ) O(n) 是存在系数的,当k非常小的时候,这个算法的复杂度可能会大于基于选择的排序的那两种方法

思维扩展

  如果我们对题目进行修改:输入n个整数,找出其中最小的K个数。现在不是找第k小的,而是找最小的k个数。到这一步,上面介绍的几种算法依旧可以解决这个问题。进一步,要求返回的k个数字是有序的。

问题分析

  好在问题还是改动不是很大,我们可以在以往的基础上去改动算法获得思路。显然那两种基于选择的排序(冒泡排序类似选择排序)的算法原本就可以解决这个问题,而且返回的序列本来也就是有序的,所以复杂度还是上面分析的复杂度。
  不同的是这种基于partition的方法,因为partition只找到了第k小的数,也知道前k小的数就是这几个数,但是却不能保证是有序的了。所以我们就需要对这里做一个简单的排序。partition函数我们都有了,写个快排应该就很容易了。我们用排序中的插入排序来代替这里的复杂度,这个排序过程的复杂度就是 O ( k 2 ) O(k^2) ,当然也可以换其他复杂度更好的排序,插入排序适合处理少量数据的排序
  从n个数中找出k个最小的数字,我们还能想到那些查找数据结构,什么红黑树,败者树,胜者树,这些数据结构都可以在这里使用,要找出k个最小的元素,可以直接构建k个节点的树,然后插入和删除的复杂度是 O ( l g k ) O(lgk) ,但是考虑到整个序列的长度,每个元素都有可能插入一次,所以这种算法的复杂度就是O(nlgk)。
  这里就不贴代码了,对几种算法比较一下即可。基于内部排序变体的方式只能在操作数据在内存的情况,不适合处理从硬盘上读取数据的情形。细节特点见下表。

算法 时间复杂度(平均情况) 特点
简单选择/冒泡排序 O ( k n ) O(kn) 适用于k比较小的情况
堆排序 O ( n + k l g n ) O(n+klgn) 适用于k不大的情况,比第一种k稍大一些
partition的算法 O ( n + k l g k ) O(n+klgk) 平均情况下最快的方式
基于查找树的算法 O ( n l g k ) O(nlgk) 可以处理海量数据,适合处理k非常大的场合

  最后回归到正题上,本文介绍了对partition调用的其他用途,我们学习一个算法除了能向上一篇文章所说的那样要对算法进行接口扩展的能力,还需要培养的就是用已知算法来搭配其他操作,来完成新的任务。我们不光能改算法的代码,还要把算法玩出花样,通过不同的组装来求解不同的问题。
  路漫漫其修远,各位同仁加油。

发布了36 篇原创文章 · 获赞 4 · 访问量 47万+

猜你喜欢

转载自blog.csdn.net/m0_38065572/article/details/104374415