分治策略 - 典型实例 - 选择问题

选择问题最多见的问题有:算法

1.1选最大

选择算法
统一描述:设L是n个算法的集合,从L中选出第k小的元素,1<=k<=n,当L中元素按从小到大排好序后,排在第k个位置的数,就是第k小的数。
下面介绍 顺序比较法
算法Findmax
输入:n个数的数组L
输出:max,k数组

max <- L[1]; k <- 1
for i <- 2 to n do      //for循环执行n-1次
    if max < L[i]
    then max <- L[i]
         k <- i
return max, k

算法Findmax第二行,for循环执行n-1次,因此 \(W(n)=n-1\)
这个算法是选最大问题在时间上最优的算法(对于选最小问题,只需对算法稍加改动,就能够获得顺序比较的Findmin算法)性能

1.2同时选最大和最小的算法

设计思想:先选最大,而后把最大的从L中删除,接着选最小。
算法:(利用Findmax和Findmin)
输入:n个数的数组L
输出:max,minspa

if n=1 then return L[1]做为max和min
else Findmax
    从L中删除max
    Findmin

算法执行的比较次数:\(W(n)=n-1+n-2=2n-3\)设计

分组比赛的方法
基本思想:首先将L中的元素两两一组,分红\(\lfloor n/2 \rfloor\)组(当n是奇数时有一个元素轮空)。每组中的2个元素进行比较,获得组内较大和较少数,把至多\(\lfloor n/2 \rfloor + 1\)(当n为奇数时,把被轮空的元素加进来)个小组中较大的元素放在一块儿,运行Findmax,获得L中的最大元素,同理获得L中的最小元素。
算法FindMaxMin3d

将n个元素两两一组分红n/2(下取整)组
每组比较,获得n/2(下取整)个较小和n/2(下取整)个较大的数      //比较n/2(下取整)次
在n/2(下取整)个(n为奇数时,是n/2(下取整)+1)较小中找最小min
在n/2(下取整)个(n为奇数时,是n/2(下取整)+1)较大中找最大max

行3和行4都执行\(\lceil n/2 \rceil -1\)
因此\(W(n)=\lfloor n/2 \rfloor +2 \lceil n/2 \rceil-2=n+\lceil n/2 \rceil-2=\lceil 3n/2 \rceil-2\)
此算法效率更高,是全部同时找最大和最小算法中事件复杂度最低的算法。指针

1.3找第二大

2次调用Findmax算法
\(W(n)=n-1+n-2=2n-3\)code

锦标赛算法
把数组中的元素两两一组,划分为\(\lfloor n/2 \rfloor\)组(n为奇数时1个元素轮空),每组组内两个元素比大小,大的进入下一轮(n为奇数时,轮空的元素也进入下一轮)。
因此下一轮有\(\lceil n/2 \rceil\)个元素。继续每组组内比大小,而后大的进入下一轮,直至找出max。筛掉\(n-1\)个元素,比较\(n-1\)次。blog

找第二大,不可能再用Findmax了,若是用Findmax,就又比较n-2次了,和上面的算法两次调用Findmax同样。
因此,咱们能够利用找max时比较所产生的记录帮咱们减小比较次数。在比赛前为每一个元素设定一个指针,指向一个链表,把比较后比它小的元素都记录进它的链表中,找出max后,在max的链表中用Findmax找出整个数组第二大的元素。
算法FindSecond
输入:n个数的数组L
输出:second排序

k <- n
将k个元素两两一组,分红k/2(下取整)组
每组的两个数比较,找到较大的
将被淘汰的较小的数在淘汰它的数所指向的链表中作记录
if k为奇数 then k <- k/2(下取整)+1
           else k <- k/2(下取整)
if k>1 then goto 2
max <- 剩下的一个数
second <- max的链表中的最大

此时,第一轮两两比较的比较次数是\(n-1\),但还不知道max的链表中有多少个元素,
因此接下来求解max所淘汰掉的元素个数(这部分的工做量)
设本轮参与比较的有\(t\)个元素,通过分组淘汰后进入下一轮的元素数至可能是\(\lceil t/2 \rceil\),下下一轮就是\(\lceil \lceil t/2 \rceil /2 \rceil = \lceil t/2^2 \rceil\)
假设k轮淘汰后只剩max,则\(\lceil n/2^k \rceil = 1\).
\(n=2^d\),那么\(k=d=logn=\lceil logn \rceil\)
因此max进行了\(d\)次比较,\(W(n)=n-1+\lceil logn \rceil\)
对于找第二大的问题,算法Findmax是时间复杂度最低的算法。

上面,咱们只讨论了一些特例状况,基本上使用顺序比较或分组比较的方法,没有明确的分治算法的特征。下面考虑通常性的选第k小问题的算法,用到分治策略。

2选第k小

输入:数组S,S的长度n,正整数k,1<=k<=n
输出:第k小的数
选第k小,可使用排序算法,从小到大排好序后,选择第k个便可,最好的排序算法的时间复杂度是\(O(nlogn)\)
下面考虑性能更好的分治算法,就是\(O(n)\)时间的算法,方便起见,假设数组S中元素彼此不等。
以S中的某个元素\(m^*\)做为划分标准,将S划分为两个子数组S1和S2,把这个数组中比\(m^*\)小的都放入\(S_1\)的数组中,数组\(S_1\)的元素个数是\(|S_1|\)个;把这个数组中比\(m^*\)大的都放入\(S_2\)的数组中,数组\(S_2\)的元素个数是\(|S_2|\)个。

  • \(k<|S_1|\),则原问题概括为在数组\(S_1\)中找第\(k\)小的子问题。
  • \(k=|S_1|+1\),则\(m^*\)就是要找的第\(k\)小元素。
  • \(k>|S_1|+1\),则原问题概括为在数组\(S_2\)中找第\(n-|S_1|-1\)小的子问题。
    算法的关键是如何肯定这个划分\(S\)的标准\(m^*\),它要具备如下 特征
  1. 寻找\(m^*\)的时间代价不能高于\(O(nlogn)\),若是直接寻找\(m^*\),时间应是\(O(n)\)。设选择算法的时间复杂度为\(T(n)\),递归调用这个算法在\(S\)上的一个真子集M上寻找\(m^*\),应该使用\(T(cn)\)时间(\(c<1\),反映\(M\)的规模小于\(S\)
  2. 经过\(m^*\)划分的两个子问题的大小分别记做\(|S_1|\)\(|S_2|\),每次递归调用时,子问题规模与原问题规模\(n\)的比都不超过\(d\)\(d<1\)),调用时间\(T(dn)\),而且应保证\(c+d<1\),不然方程\(T(n)=T(cn)+T(dn)+O(n)\)的解不会达到\(O(n)\)
    下面的分治算法采用递归调用的方法寻找\(m^*\),先将\(S\)分组,5个元素一组,共分红\(\lceil n/5 \rceil\)个组。在每组中取中位数,把这\(\lceil n/5 \rceil\)个中位数放入集合\(M\)中,而后使用选择算法选出集合\(M\)中的中位数\(m^*\)。此次递归调用的子问题规模是原问题规模的\(1/5\),\(1/5\)即为上文的特征(1)的\(c\)
    算法Select(S,k)
    输入:数组S,S的长度n,正整数k,1<=k<=n
    输出:第k小的数
将S划分红5个一组,共n/5(上取整)个组
每组中找一个中位数,把这些中位数放到集合M中
m* <- Select(M,|M|/2)                  //选M中的中位数m*,将S中的数组划分红A,B,C,D四个集合
把A和D中的每一个元素与m*比较,小的构成S1,大的构成S2
S1 <- S1并C ; S2 <- S2并B
if k=|S1|+1 then 输出m*
else if k<=|S1|
    then Select(S1,k)
    else Select(S2,k-|S1|-1)

image
左下方的集合\(C\)中因此元素所有小于\(m^*\),右上角的集合B中的全部元素所有大于\(m^*\)。仅仅对于\(A\)\(D\)中的元素,咱们不能肯定它们是否大于或小于\(m^*\),因此在算法的行4加以比较,完成\(S1\)\(S2\)的划分。
下面分析时间复杂度
假设n是5的倍数,且\(n/5是奇数\),即\(n/5=2r+1\)
因此\(|A|=|D|=2r\)\(|B|=|C|=3r+2\)\(n=10r+5\)
若A,D的元素都小于\(m^*\),那么它们都加入至S1中,且下一步算法又在这个子问题上递归调用,这对应了归约后子问题的规模的上界,也正好是时间复杂度最坏的状况。相似的,若是A,D的元素都大于\(m^*\),也会出现相似的状况。
之前者为例时,子问题的大小是
\(|A|+|C|+|D|=7r+2=7\frac{n-5}{10}+2=7\frac{n}{10}-1.5<\frac{7n}{10}\)
上式代表子问题规模最大不超过原问题的\(7/10\),这个参数\(7/10\)就是前文特征(2)中的\(d\)
因此最坏状况下的时间复杂度的递推式:
\(W(n)<=W(\frac{n}{5})+W(\frac{7n}{10})+cn\)
image
image

\[\begin{aligned} W(n) & <= tn+0.9tn+0.9^2 tn+... \\ & = tn(1+0.9+0.9^2+...) \\ & = O(n) \end{aligned} \]

因此从上面的分析能够看出,这样找的\(m^*\)彻底知足算法要求,两次递归调用的参数分别是\(c=0.2\)\(d=0.7\)\(c+d=0.9<1\)
因此Select算法能够把时间复杂度降至线性时间。
分组时也能够选\(3\)个或\(7\)个元素,元素数的改变可能会改变\(m^*\)的特征,从而使得针对某些分组方法获得的\(c+d\)的值再也不小于\(1\),这就会增长运算时间。

求解选择问题的时间复杂度最低的算法就是O(n)时间的算法!

相关文章
相关标签/搜索