[算法导论 Ch9 中位数和顺序统计量] Selection in O(n)

1. 寻找第k大(小)的数

假设数据存储在数组a[1..n]中

首先,寻找一个数组中最大或者最小的数,因为最大(小)的数一定要比其他所有的数大(小),因此至少要比较完所有的pair才能确定,所以时间复杂度在O(n)。那么寻找第k大(小)呢?

比较直观的,就是对数组中国所有的数据先进行排序,在我们这种渣渣的计算机入门选手而言,可选的有QuickSort,MergeSort和HeapSort,甚至是ShellSort等一些比较高级的方法啊...一般的代价都在O(n*logn)上,然后直接取出即可。

在算法导论的第九章,提出了一个效率更高的Selection算法,用到的主要原理就是分治,是根据QuickSort改编的,但是主要的不同的是,QuickSort会递归处理划分的两边,而这里的Selection则是只处理一半。因为要寻找第k小的数,那么如果k比之前的partition处理后,返回的位置下标要小,那么第k小的数一定在partition的pivot的左边一半,右边一半可以不用处理;若是k比这个pivot的下标要大,那么显然,要找的值在pivot的右边,但是这里有一点特殊,下一次递归查找的时候,是要寻找第(k-pivot下标)的数啦~

嗯,感觉好厉害的样子。然后让我来练练手。

 1     int partition(int[] a, int start, int end, int pivot) {//pivot为可以自己定义选定的参照值
 2         int i = start;
 3         int j = start;
 4         for(j = start; j < end; j++) {
 5             if(a[j]<=pivot) {
 6                 int temp=a[i];
 7                 a[i]=a[j];
 8                 a[j]=temp;
 9                 i++;
10             }
11         }
12         int temp2 = a[i];
13         a[i]=a[end];
14         a[end]=temp2;
15         return i;
16     }
17
18     int selectkth(int[] a, int start, int end, int k) {
19         if(start == end)
20             return a[start];
21         int q = partition(a,start,end,a[end]);
22         int p = q-start+1;
23         if(k == p)
24             return a[q];
25         else if(k < p) {
26             return selectkth(a, start, q-1, k);
27         }
28         else return selectkth(a, q+1 , end, k-p);
29     }

2.前k大的数(算法导论思考题1)

其实,在我们老师上课的时候,比较强调selection和partition的“化学作用”啊,如果问题变为找出前k大的k个数呢?当然,万能的排序还是OK的啊,直接排序完输出就好了,简直66666...

后来,作业题中出现了一些比较厉害的东西啊,它可以达到O(n + k*logn)啊,居然要用到建立一个堆啊,显然,算法导论上第六章说道,建立一个堆只需要线性时间啊,各种fix的操作也只需要logn啊,那不就是建个最大堆,然后提取k次root顺带着调整一下么...其实我喜欢说修理啊哈哈哈哈....

再后来,还有一个问题,说是可以到O(n+klogk)啊,你TM真是够了啊,然后,就是selection选出第k大的数啊,然后用这个数进行partition啊,把比他大的数用一次排序就好了啊,喂,你真是够了啊!

 3. Selection + Partition共同解决一些问题

为了进一步blablablabla,老师真的是很强调这个Selection+Partition啊

Question 1:算法导论的第九章的思考题就有一个,叫做weighted-median。

显然啊,我觉得排序算法解决一切啊,有木有!!但你他喵的非得线性时间干嘛啊~

嘿嘿,然后来了啊,选出中位数啊,嘿嘿,然后进行partition,从头开始计算权重的和啊,看看跟1/2怎么样?小?OK,权重不够哦,再在右边一半进行Median的Selection+Partition啊,再进行计算吧,带上前面算出来的权重和啊,再去跟1/2比较...要是第一趟处理就比1/2大,那就在左边做一次中位数Selection+Partition吧...嘿嘿嘿嘿,大概估算一下,它的cost应该实在n*(1+1/2+1/4+1/8+...+1/(2n))吧...其实也就是O(2n)的样子,不错了吧,嘿嘿

Question 2:寻找最近接Median的k个数...(算法导论9.3-7题)

老规矩,排序啊,有木有!!!为什么又是线性时间呢?但是,老师这么说肯定是有用意的。好啦,我能想到的方法呢,就是先Select出中位数,cost=n,然后呢,就是对每个数与这个中位数做差取绝对值啊,然后这里的每个差的绝对值中,选出前k小的数,根据2中的原理,也就是O(n+klogk)的cost,然后映射回去原来的数,总体而言,的确是O(n)的cost吧(如果k不是特别大的话)。老师上课在讲Tutorial的时候,还提出了另一种差不多的方法,大致思路也就是selection+partition这个主题啦~~第一次选出中位数selection,记为M,加一次partition,共计O(2n),两边各自寻找中位数,只保留接近中位数M的一半,直到差不多在第一次的中位数M左右两边各有k个数左右,然后选出差值最小的k个数...总之就是缩小范围,然后再集中处理的样子吧,时间大概是一个等比级数的求和n*(2+2/2+2/4+2/8+...+2/2k),还是在O(n)的级别的,但我总觉得绕了这么一大圈...哎,网上搜了一下,大多数都是直接一次上来就做差的啊...(莫非是我记错了??

4. PK法

PK让我想起了年少无知的我看超女快男的经历啊。这里,我们老师主要用这种方法解决一些Selection相关的问题。

PK法的前提是我想要的超过总数的一半,主要思路是,每次都对等的淘汰两个或一个,若淘汰两个,至少有1个是我不想要的,若只淘汰了一个,那么我必定是我不想要的那个!这样淘汰直到最后,剩下的一定是我想要的。当然,若事先不知道前提是否成立,那么在得到结果以后,还要进行一次check!

Question 3:寻找出现频率最高的数,直接贴代码吧...

 1     int pk(int[] a) {
 2         int x = a[0];
 3         int count = 0;
 4         for(int i=0; i<a.length; i++) {
 5             if(a[i] == x)
 6                 count++;
 7             else if(count >= 1)
 8                 count--;
 9             else {
10                 //start from begin
11                 count = 0;
12                 //why i+1? for next item is a[i+1], then count will ++
13                 //delete this pair of two
14                 x = a[i+1];
15             }
16         }
17         //check
18         int y = x;
19         count = 0;
20         for(int i = 0; i<a.length; i++) {
21             if(a[i]==y) {
22                 count++;
23             }
24         }
25         if(count>a.length/2)
26             return x;
27         else
28             return -1;
29     }

还有一个扩展问题:现在数组中没有出现频率一半的数字了,但有三个都超过了四分之一,找到他们。

有兴趣的可以去看看这儿:http://www.cnblogs.com/jy02414216/archive/2011/03/04/1970497.html

Question 4:VLSI芯片测试

    Diogenes教授有n个被认为是完全相同的VLSI芯片,原则上它们是可以互相测试的。教授的测试装置一次可测二片,当该装置中放有两片芯片时,每一片就对另一片作测试并报告其好坏。一个好的芯片总是能够报告另一片的好坏,但一个坏的芯片的结果是不可靠的。这样,每次测试的四种可能结果如下:

    A芯片报告         B芯片报告     结论



    B是好的          A是好的      都是好的,或都是坏的

    B是好的          A是坏的      至少一片是坏的

    B是坏的          A是好的      至少一片是坏的

    B是坏的          A是坏的      至少一片是坏的

a)证明若多于n/2的芯片是坏的,在这种成对测试方法下,使用任何策略都不能确定哪个芯片是好的。假设坏的芯片可以联合起来欺骗教授。

b)假设有多于n/2的芯片是好的,考虑从n片中找出一片好芯片的问题。证明n/2对测试就足以使问题的规模降至近原来的一半。

c)假设多于n/2片芯片是好的,证明好的芯片可用Θ(n)对测试找出。给出并解答表达式测试次数的递归式。

注:VLSI——Very Large Scale Integrated.

算法分析:

a)    在所有的策略中,时间复杂度最高但最有效的方法是:对每个芯片,让其它所有芯片对它进行报告,由于好芯片数目小于n/2,对于任意芯片,坏芯片都可以让判断结果一模一样(比如判断结果好坏各占一半),此时,就无法判断出好坏。得证。
b)    问题可以这么理解,证明:当多于n/2的芯片是好的时,可以通过【n/2的下界】次操作,得到一个包含至多n/2个芯片的集合,且该集合内好的芯片大于一半。这样,以后只需要在这个集合上执行类似的判断动作就好了。
对于n个芯片的集合,假设good代表好芯片的数目,则坏芯片有(n – good)个,将n个芯片两两组合,接下来分类讨论。
        1.    当n为偶数时,假设好芯片和坏芯片组成的对数为r,则(n - good) >= r。对每个对,如果结果是情况2、3、4,则不做任何操作,如果结果是情况1,则从中挑出一个放到一个集合中。所以,我们可以在好芯片对中取到(good – r)/2个芯片,从坏芯片对中取到m个芯片,m <= (n – good - r)/2。因为(good – r)/2 > (n – good - r)/2,所以新集合中好芯片的数目大于一半,另外总芯片数小于等于(n/2 - r)。
        2.    当n为奇数时,提取一个芯片,对剩下的芯片采取偶数的方法,只不过最后的集合情况是:(good - r)/2 >= (n – 1 – good - r)/2,芯片总数小于等于((n – 1)/2 - r)。
                1)  当总数是偶数时,要么好的芯片数和坏的芯片数一样,原先被提取的芯片是好的芯片,把好的芯片加入集合;要么好的芯片比坏芯片多偶数个,此时不论被提取的芯片是好是坏,把它加入集合也能保证好的芯片数大于坏的芯片数。
                2)  当总数是奇数时,好的芯片数必然大于坏的芯片数。
       得证。
c)    当每次的r为0时,所需要的递归次数最多,T(n) = T(n/2) + n/2。

(请原谅我直接贴了别人的博客:http://www.cnblogs.com/longdouhzt/archive/2011/07/15/2107751.html内容...)

时间: 2024-10-12 15:02:00

[算法导论 Ch9 中位数和顺序统计量] Selection in O(n)的相关文章

算法导论之七(中位数和顺序统计量之选择算法)

实际生活中,我们经常会遇到这类问题:在一个集合,谁是最大的元素?谁是最小的元素?或者谁是第二小的元素?....等等.那么如何在较短的时间内解决这类问题,就是本文要阐述的. 先来熟悉几个概念: 1.顺序统计量: 在一个由n个元素组成的集合中,第i个顺序统计量(order statistic)是该集合中第i小的元素.最小值是第1个顺序统计量(i=1),最大值是第n个顺序统计量(i=n).   2.中位数: 一个中位数是它所属集合的"中点元素",当n为奇数时,中位数是唯一的,位于i=(n+1

【算法导论学习-015】数组中选择第i小元素(Selection in expected linear time)

1.算法思想 问题描述:从数组array中找出第i小的元素(要求array中没有重复元素的情况),这是个经典的"线性时间选择(Selection in expected linear time)"问题. 思路:算法导论215页9.2 Selection in expect linear time 2.java实现 思路:算法导论216页伪代码 /*期望为线性时间的选择算法,输入要求,array中没有重复的元素*/ public static int randomizedSelect(i

算法导论第九章中位数和顺序统计量(选择问题)

本章如果要归结成一个问题的话,可以归结为选择问题,比如要从一堆数中选择最大的数,或最小的数,或第几小/大的数等, 这样的问题看似很简单,似乎没有什么可研究的必要,因为我们已经知道了排序算法,运用排序+索引的方式不就轻松搞定了?但细想,排序所带来的时间复杂度是不是让这个问题无形之中变得糟糕.那算法研究不就是要尽可能避免一个问题高复杂度地解决,让那些不敢肯定有无最优解的问题变得不再怀疑,这也是算法研究者所追求的一种极致哲学.既然排序让这个问题解决的性能无法确定,那我们就抛开排序,独立研究问题本身,看

第九章 中位数和顺序统计量 9.2 期望为线性时间的选择算法

package chap09_Medians_and_Order_Statistics; import static org.junit.Assert.*; import java.util.Random; import org.junit.Test; public class SearchAlorithms { /** * 分割(快速排序中对数组的分割) * * @param n * @param start * @param end * @return */ protected static

【算法导论学习-016】两个已排过序的等长数组的中位数(median of two sorted arrays)

问题来源 <算法导论>P223 9.3-8: Let X[1..n] and Y[1..n] be two arrays, each containing nnumbers already in sorted order. Give an O(lgn)-time algorithm to find themedian of all 2n elements in arrays X and Y. 翻译过来即:求两个等长(n个元素)的已排序数组A和B的中位数 方案1:对两个数组进行归并直到统计到第n

算法导论 第9章 中位数和顺序统计学

/* * 算法导论 第九章 中位数和顺序统计学 * 线性时间选择元素 */ #include <iostream> #include <ctime> using namespace std; int minimum(int *arr, int len); int randomizedSelect(int *arr, int p, int r, int i); int randomizedPartition(int *arr, int p, int r); void exchange

算法导论之所有排序算法的Python实现

最近一段时间学习了算法导论第二版书的第一部分和第二部分的内容,自己编写了其中排序相关的几乎全部算法,包括冒泡排序(bubble sort).选择排序( selection sort).插入排序(insertion sort).希尔排序(shell sort).归并排序(merge sort).快速排序(quick sort).计数排序(count sort).基数排序(radix sort).桶排序(bucket sort).期望线性时间的第k个顺序统计量选择.最坏情况线性时间的中位数选择,并给

《算法导论》读书笔记(三)

本章介绍了快速排序及其算法分析,快速排序采用的是分治算法思想,对包含n个数的输入数组,最坏情况下运行时间为θ(n^2),但是平均性能相当好,期望的运行时间为θ(nlgn).另外快速排序能够就地排序(我理解是不需要引入额外的辅助空间,每次划分能确定一个元素的具体位置),在虚拟环境中能很好的工作. 1.快速排序的描述 快速排序算法采用的分治算法,因此对一个子数组A[p-r]进行快速排序的三个步骤为: (1)分解:数组A[p...r]被划分为两个(可能为空)子数组A[p...q-1]和A[q+1...

算法导论CLRS答案

目前正在编写算法导论答案,欢迎大家follow me at mygithub 刚完成第9章,中位数和顺序统计学 正在编写第13章,红黑树 想要参与的朋友可以告诉我想要编写的章节,开个branch给你------