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

选择问题最常见的问题有:

  • 1.1选最大

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

    1.3找第二大

  • 2选第k小(分治策略)

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,min

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中的最小元素。

算法FindMaxMin

将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\)

锦标赛算法

把数组中的元素两两一组,划分为\(\lfloor n/2 \rfloor\)组(n为奇数时1个元素轮空),每组组内两个元素比大小,大的进入下一轮(n为奇数时,轮空的元素也进入下一轮)。

所以下一轮有\(\lceil n/2 \rceil\)个元素。继续每组组内比大小,然后大的进入下一轮,直至找出max。筛掉\(n-1\)个元素,比较\(n-1\)次。

找第二大,不可能再用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)

左下方的集合\(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\)

\[\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)时间的算法!

原文地址:https://www.cnblogs.com/HIIM/p/12592459.html

时间: 2024-10-16 00:18:02

分治策略 - 典型实例 - 选择问题的相关文章

【从零学习经典算法系列】分治策略实例——快速排序(QuickSort)

在前面的博文(http://blog.csdn.net/jasonding1354/article/details/37736555)中介绍了作为分治策略的经典实例,即归并排序,并给出了递归形式和循环形式的c代码实例.但是归并排序有两个特点,一是在归并(即分治策略中的合并步骤)上花费的功夫较多,二是排序过程中需要使用额外的存储空间(异地排序算法<out of place sort>). 为了节省存储空间,出现了快速排序算法(原地排序in-place sort).快速排序是由东尼·霍尔所发展的一

【从零学习经典算法系列】分治策略实例——二分查找

1.二分查找算法简介 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法.搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束:如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较.如果在某一步骤数组 为空,则代表找不到.这种搜索算法每一次比较都使搜索范围缩小一半.折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn). 二分查找的优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且

南邮算法分析与设计实验1 分治策略

分治策略 实验目的: 理解分治法的算法思想,阅读实现书上已有的部分程序代码并完善程序,加深对分治法的算法原理及实现过程的理解. 实验内容: 用分治法实现一组无序序列的两路合并排序和快速排序.要求清楚合并排序及快速排序的基本原理,编程实现分别用这两种方法将输入的一组无序序列排序为有序序列后输出. 代码: #include <iostream> #include <cstdlib> #include <ctime> using namespace std; void Swa

最大子数组问题(分治策略实现)

在算法导论4.1最大子数组问题中首先提出的是暴力求解方法即计算所有子数组的组合,然后求其和,寻找最大值.这种方法运行时间为Ω(n^2).然后提出有没有更好的方法. 使用分治策略的求解方法: 寻找子数组A[low..high]的最大子数组,使用分治技术意味着要将子数组划分为两个规模尽量相等的子数组.也就是说,找到子数组的中央位置(比如mid),然后考虑求解两个子数组A[low..mid]和A[mid..high].A[low..high]的任何连续子数组A[i..j]所处的位置必然以一下三种情况之

分治策略结合递归思想求最大子序列和

我的主力博客:半亩方塘 对于 <数据结构与算法分析--C语言描述> 一书第 20 页所描述的算法 3,相信会有很多人表示不怎么理解,下面我由具体问题的求解过程出发,谈谈我自己的理解: 首先,什么是分治法呢?所谓 分治法,就是 将一个问题的求解过程分解为两个大小相等的子问题进行求解,如果分解后的子问题本身也可以分解的话,则将这个分解的过程进行下去,直至最后得到的子问题不能再分解为止,最后将子问题的解逐步合并并可能做一些少量的附加工作,得到最后整个问题的解.在求解原来整个问题的算法思想,与求解每一

计算机算法设计与分析之递归与分治策略——二分搜索技术

递归与分治策略 二分搜索技术 我们所熟知的二分搜索算法是运用分治策略的典型例子,针对这个算法,先给出一个简单的案例. 目的:给定已排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定的元素x. 我们首先想到的最简单的是用顺序搜索方法,逐个比较a[0:n-1]中元素,直至找出元素x或搜索遍整个数组后确定x不在其中.这个方法没有很好地利用n个元素已排好序的这个条件,因此在最坏的情况下,顺序搜索方法需要O(n)次比较. 而二分搜索方法充分利用了元素间的次序关系,采用分治策略,可在最坏情况下用

五大常见算法策略——递归与分治策略

摘要:递归与分治策略是五大常见算法策略之一,分治策略的思想就是分而治之,即先将一个规模较大的大问题分解成若干个规模较小的小问题,再对这些小问题进行解决,得到的解,在将其组合起来得到最终的解.而分治与递归很多情况下都是一起结合使用的,能发挥出奇效(1+1>2),这篇文章我们将先从递归说起,再逐渐向分治过渡,主要讲解方式是通过9个例题来说明问题的,问题都是根据难度由简到难,由浅入深,对递归与分治能有个大概的了解雏形,当然最重要还是要做大量练习才能掌握. 1.递归 我们第一次接触递归一般都是在初学C语

分治策略 - 最大子序列问题

自开始学习算法起,我感觉就是跪着把<算法导论>的代码看一遍.理解一遍然后敲一遍...说实话自己来写并且要求时间复杂度达到要求,我肯定是不能做到的,但我想前辈们辛苦积累的研究成果贡献出来也是为了让后人少走一些弯路,所以我的作用就是把前辈们的成果学习之后加以理解,然后积累经验,领悟到他们解决问题时的思路和灵感.还有就是把个人理解后的知识存储在不会忘记的地方作为复习备用... 当然什么是写博客呢,我个人认为是把所学的知识加上自己的理解然后用较为通俗的语言来解释一遍,至少这样才有可能把学到的东西变为自

第四章 分治策略 4.1 最大子数组问题 (暴力求解算法)

/** * 最大子数组的暴力求解算法,复杂度为o(n2) * @param n * @return */ static MaxSubarray findMaxSubarraySlower(int[] n) { long tempSum = 0; int left = 0; int right = 0; long sum = Long.MIN_VALUE; for (int i = 0; i < n.length; i++) { for (int j = i; j < n.length; j++