key 发表于 20-5-2009 20:11:39

两个有序数组中找第n个数(其中n小于任何一个数组的长度)

有数组A和B,都是按从小到大的排序。现在需要从这两个数组中找到第n大的数。

最简单的做法:
逐次比较A和B两个数组,并移动比较指针,直到能找到第n个数。这样的算法复杂度为O(n)

更有效的方法在二楼说(如果能占上的话)

key 发表于 20-5-2009 20:23:27

O(lgn)的算法

1. 在A、B中分别取长度为n/2的部分,比较A和B,如相等,则A或B为所求。
2. 不失一般性,假设A<B,在A之后取多n/4个数,而B则取前n/4个数,比较A[(3n)/4]和B的值,如果相等,则找到所求,否则转3
3. 假设这时B < A[(3n)/4],则在B之取多B个数,而A后取多A个数,比较之。。。。

最多经过lg(n)次的比较后,我们可以得到排第n位的那个数了。算法描述:

假设第k次比较时,A数组取的位置为x,而B数组取的位置为y,不失一般性,假设A < B,则下次比较的位置为:
A
B,注意,这里需要使 x + n/(2^k) + y - n/(2^k) == n,而因为整除问题,需要做一定的调节。
直到 n/2^k==0
而k=1时, x = n/2, y = n/2

显然,这样的算法时间复杂度为 O(lg(n))

key 发表于 20-5-2009 20:25:54

对于3个数组,则每次递增或递减的pace可以设为n/3^k,对于s个数组,可以设定为n/s^k。。。。这个没有验算过。

klux 发表于 20-5-2009 20:29:39

同样找第k大的数,要是只有一个数组,但数组是无序的呢?

coredump 发表于 20-5-2009 20:55:18

原帖由 klux 于 20-5-2009 20:29 发表 http://www.freeoz.org/forum/images/common/back.gif
同样找第k大的数,要是只有一个数组,但数组是无序的呢? 先排序:lol

key 发表于 20-5-2009 23:00:59

原帖由 klux 于 20-5-2009 20:29 发表 http://www.freeoz.org/forum/images/common/back.gif
同样找第k大的数,要是只有一个数组,但数组是无序的呢?

这样的题目如果不是要考你的应变能力,就是考你的记忆力了。hoho~

首先,最简单、最容易想到的算法就是先排序,然后再给出目标元素。
随便找一种O(nlogn)的排队算法,比如插入排队,于是我们可以得到一个
时间复杂性为O(nlogn)的算法

然后,我们可以通过学习/记忆,知道有一种随机选取算法。在队列中随便找
一个数,如x,用x来分割队列成两部分,比x小的放在左,比x大的放在右,
如果最后发现x所在的位置就是我们需要的位置,那么就得到了我们所要的。
否则,如果x比我们想要的r值小,则在右边再选一个x值进行同样的分割,
否则,在x的左边进行相同的操作。

这种方法的worst-case为O(n^2),但由于我们通过随机选择,所以,可以用
均值/期望值来作为算法的复杂度,可以证明是O(n)。有人要求证明吗?如果我
还没有疯掉,我可能在下一楼给出证明。

如果考官还不满意这个“期望值”为O(n)的算法,要求你给出一个worst-case
的算法,而你又没有记得的话,我建议你回家先吧。因为worst-case为O(n)
的算法是计算机算法的一个突破,在1973年由5个变态搞出来,其中两个是
Standford教授,一个是普林斯顿,一个是MIT的,还有一个是CMU的。

这个算法的大致做法是把所有n个元素分成5组,并行列出:
o o o o o o o o o o o o o
o o o o o o o o o o o o o
o o o o o o o o o o o o o
o o o o o o o o o o o o
o o o o o o o o o o o o
对每一列的元素进行中间元素的查找,把找到的中间元素放在中间。
然后对中间一行的中间元素进行中值查找(即在n/5个元素中做中值查找),
把找到的中值置中(连同其对应的列)。于是可以得到左上角的 3 * n / 10个元素
小于中值,而右下角的 3 * n / 10 个元素大于中值。如果你要找的元素在1 ~ 7n/10
之间,就不要在右下角找,如果你要找的元素在 3n/10 ~ n 之间,就不要在左上角找。
由这样的方法,递归得到你要的第r个元素。

可证明,这样的操作能在线性时间内获得第 r 个元素。希望我没有表述错误。

[ 本帖最后由 key 于 20-5-2009 23:04 编辑 ]

klux 发表于 20-5-2009 23:10:06

赞,都讲到了

key 发表于 20-5-2009 23:12:13

原帖由 klux 于 20-5-2009 23:10 发表 http://www.freeoz.org/forum/images/common/back.gif
赞,都讲到了

谢谢你提了这个问题,我刚才翻书去了:L :L :D ;P

coredump 发表于 20-5-2009 23:52:20

原帖由 key 于 20-5-2009 23:12 发表 http://www.freeoz.org/forum/images/common/back.gif


谢谢你提了这个问题,我刚才翻书去了:L :L :D ;P
:zan
你就是我们的key啊:good

key兄现在研究什么领域呢?你功底真扎实啊!

key 发表于 21-5-2009 01:22:28

然后,我们可以通过学习/记忆,知道有一种随机选取算法。在队列中随便找
一个数,如x,用x来分割队列成两部分,比x小的放在左,比x大的放在右,
如果最后发现x所在的位置就是我们需要的位置,那么就得到了我们所要的。
否则,如果x比我们想要的r值小,则在右边再选一个x值进行同样的分割,
否则,在x的左边进行相同的操作。

这种方法的worst-case为O(n^2),但由于我们通过随机选择,所以,可以用
均值/期望值来作为算法的复杂度,可以证明是O(n)。有人要求证明吗?如果我
还没有疯掉,我可能在下一楼给出证明。

我尝试证明一下这个东西。很容易证明,随机取得的x可以把队列分割后,前半部分刚好有k个元素的概率为
E = 1/n,这里 Xk表示为随机取得的x可以把队列分割成前半部分恰好有k个元素的随机变量。

一次操作,可能得到的分割情况有:(0, n-1), (1, n-2), ..., (k-1, n-k), ..., (n-1, 0),共n中情况,
分割后,最坏的情况是要找到的r在大的那边,这样下一次分割的 n 值就为 max(k-1, n-k)。

一次分割操作所需的时间复杂度为 O(n),所以整个查找时间T(n)为
T(n) <= sum(Xk * T(max(k-1, n-k)) + O(n)(1)
这里sum是从k=1 to n的。
对(1)式求期望值,得到
E <= E + O(n) (2)
= E * sum(E) + O(n) (3)
= (1/n) * sum(E) + O(n)(4)
= (2/n) * sum(E) + O(n) (5)这里的sum表示的是k = n/2 to n这部分的值

这个证明的目标是E = O(n),那这里我们就设定T(k) = ck,如果(5)代入后成立,则证明成功
代入后得到:
E <= (2/n) * sum(E) + O(n) = (2/n) * c * sum(k) + 0(n) (6)

显然有sum(k) == (3/8)n^2(等差数列求和)
E <= (2/n) * c * (3/8) n + O(n) = (3/4) * c * n + O(n) = c * n - ((1/4)*c*n - O(n)) (7)

由7式可知,当c为足够大的数时,不等式成立,由此可证明 E = O(n)

key 发表于 21-5-2009 01:26:37

原帖由 coredump 于 20-5-2009 23:52 发表 http://www.freeoz.org/forum/images/common/back.gif

:zan
你就是我们的key啊:good

key兄现在研究什么领域呢?你功底真扎实啊!

过奖了,都是google + 一大堆书的结果。哪可能记得这么多东西呢。

最近闲置在家,以看书为乐。市场不好,加之自己不太了解市场,只能先闲置一段时间了。

someonehappy 发表于 21-5-2009 12:04:07

找到资料都能理解的话,也是牛人了。

decisiontree 发表于 21-5-2009 13:44:16

原帖由 key 于 20-5-2009 20:25 发表 http://www.freeoz.org/forum/images/common/back.gif
对于3个数组,则每次递增或递减的pace可以设为n/3^k,对于s个数组,可以设定为n/s^k。。。。这个没有验算过。

我觉得这个方法不错,但对多个数组的比较会有困难。如三个数组的n/3 position的比较结果不是BINARY的值。那如何决定后续指针的移动方向。

decisiontree 发表于 21-5-2009 14:06:22

原帖由 key 于 20-5-2009 23:00 发表 http://www.freeoz.org/forum/images/common/back.gif

这个算法的大致做法是把所有n个元素分成5组,并行列出:
o o o o o o o o o o o o o
o o o o o o o o o o o o o
o o o o o o o o o o o o o
o o o o o o o o o o o o
o o o o o o o o o o o o
对每一列的元素进行中间元素的查找,把找到的中间元素放在中间。
然后对中间一行的中间元素进行中值查找(即在n/5个元素中做中值查找),
把找到的中值置中(连同其对应的列)。。。

》》》
我对每列(n/5个元素)中间元素的找法有怀疑。如何保证在线性时间找到非排序的列的中间元素,即在n/5个元素中找第n/10个元素。

lavahx 发表于 21-5-2009 14:21:20

找个O(N)排序算法不是很好吗

key 发表于 21-5-2009 17:03:50

原帖由 decisiontree 于 21-5-2009 13:44 发表 http://www.freeoz.org/forum/images/common/back.gif


我觉得这个方法不错,但对多个数组的比较会有困难。如三个数组的n/3 position的比较结果不是BINARY的值。那如何决定后续指针的移动方向。

这个问题不难解决,如果是两个数组,那可以一个天一个地(即ceiling/floor)即可,如果是三个数字,那就可以按顺序0,1,2这样来排余数。
总之,想办法让总长度为n即可。显然,要做到这一点并不难,只是做个决定怎样摆就行了。

key 发表于 21-5-2009 17:05:28

我对每列(n/5个元素)中间元素的找法有怀疑。如何保证在线性时间找到非排序的列的中间元素,即在n/5个元素中找第n/10个元素。

这个问题是一个递归问题。先假设T(n)为线性,那当然我们可以用线性的时间处理T(n/5)了。

key 发表于 21-5-2009 17:39:54

原帖由 lavahx 于 21-5-2009 14:21 发表 http://www.freeoz.org/forum/images/common/back.gif
找个O(N)排序算法不是很好吗

你说得很对。O(n)排序真的很神奇,不过三种典型的O(n)排序,包括计数排序、Radix排序和Bucket排序都有一定的条件制约。
比如Radix排序,我需要找一个合适的radix来排。从某种意义上,通用性受制约。

当然,这应该是一种选择。

decisiontree 发表于 21-5-2009 18:17:25

原帖由 key 于 21-5-2009 17:03 发表 http://www.freeoz.org/forum/images/common/back.gif


这个问题不难解决,如果是两个数组,那可以一个天一个地(即ceiling/floor)即可,如果是三个数字,那就可以按顺序0,1,2这样来排余数。
总之,想办法让总长度为n即可。显然,要做到这一点并不难,只是做个决定怎 ...

呵呵,恕我愚笨。我知道总长度为n。请明确解释下三个数组的问题。特别是排在中间那个数组的指针走向,向左走还是向右走。然后看看这个办法能不能用到一百一千个数组里。

key 发表于 21-5-2009 21:06:27

原帖由 decisiontree 于 21-5-2009 18:17 发表 http://www.freeoz.org/forum/images/common/back.gif


呵呵,恕我愚笨。我知道总长度为n。请明确解释下三个数组的问题。特别是排在中间那个数组的指针走向,向左走还是向右走。然后看看这个办法能不能用到一百一千个数组里。

相信没有什么东西比一段代码更有说服力了。我用C语言实现了上面说的那个算法。1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <time.h>
6 #include <values.h>
7 #include <string.h>
8
9
10 /*
11* randomly choose the len of array A , Band C from 1024 to 2048
12* and randomly choose the value of <i>th from 100 to 500
13*
14* note that the start index of C array is 0 and the start number of ith is 1
15*
16*/
17
18 void generate(int x[], size_t len)
19 {
20   int k;
21
22   for(k=0; k<len; k++)
23   {
24         x = rand() % 9999;
25   }
26 }
27
28 int value_compare(const void * v1, const void * v2)
29 {
30   const int * vv1 = (const int *)v1;
31   const int * vv2 = (const int *)v2;
32
33   return (* vv1) - (* vv2);
34 }
35
36 int main(int argc, char ** argv)
37 {
38   int len, tlen;
39   int * seq;
40   int base;
41   int ith;
42   int count, lowest_pace;
43   int k;
44   int curpace, diff;
45
46   srand(time(NULL));
47
48   tlen = 0;
49   for(k=0; k<3; k++)
50   {
51         len = rand() % 1024 + 1024;
52         seq = (int *)malloc(len * sizeof(int));
53         generate(seq, len);
54         qsort(seq, len, sizeof(int), value_compare);
55         tlen += len;
56   }
57
58   ith = rand() % 400 + 100;
59
60   //debug
61   printf("N = %d, A = %d, B = %d, C = %d, ith = %d\n", tlen, len, len, len, ith);
62
63   count = 0;
64   lowest_pace = ith / 3;
65   base = base = base = 0;
66
67   int min, minSeq;
68
69   while(lowest_pace > 1)
70   {
71         printf("DEBUG lowest_pace = %d, A_base = %d, B_base = %d, C_base = %d\n",
72             lowest_pace, base, base, base);
73
74         diff = ith - (base + base + base + 3 * lowest_pace);
75         int pace;
76         int k;
77
78         minSeq = -1;
79
80         pace = pace = pace = lowest_pace;
81
82         for(k=0;diff>0; --diff, k++)
83         {
84             pace++;
85         }
86
87         int total_pace;
88
89         for(k=0; k<3; k++)
90         {
91             total_pace = base + pace;
92         }
93
94
95         min = MAXINT;
96
97         for(k=0; k<3; k++)
98         {
99             if(seq] < min)
100             {
101               min = seq];
102               minSeq = k;
103             }
104         }
105
106         base += pace;
107
108         curpace = 0;
109         for(k=0; k<3; k++)
110         {
111             curpace += base;
112         }
113
114         lowest_pace = (ith - curpace) / 3;
115   }
116
117   curpace = base + base + base;
118   diff = ith - curpace;
119
120
121   k = 0;
122
123   while(diff > 0)
124   {
125         min = MAXINT;
126         minSeq = -1;
127
128         for(k=0; k<3; k++)
129         {
130             if(min > seq])
131             {
132               min = seq];
133               //base ++;
134               minSeq = k;
135             }
136         }
137
138         base ++;
139         diff --;
140
141   }
142
143   printf("\n");
144   printf("<%d>th number is %d, seq is %d, base = %d, base = %d, base = %d\n",
145         ith, seq-1], minSeq,base, base, base);
146
147   int * nseq = (int *)malloc(sizeof(int) * tlen);
148   memcpy(nseq, seq, sizeof(int) * len);
149   memcpy((char *)nseq + len * sizeof(int), seq, sizeof(int) * len);
150   memcpy((char *)nseq + len * sizeof(int) + len * sizeof(int), seq, sizeof(int) * len);
151
152   qsort(nseq, tlen, sizeof(int), value_compare);
153
154   printf("<%d>th should be %d\n", ith, nseq);
155
156   return 0;
157 }测试结果一N = 4242, A = 1102, B = 1892, C = 1248, ith = 144
DEBUG lowest_pace = 48, A_base = 0, B_base = 0, C_base = 0
DEBUG lowest_pace = 32, A_base = 0, B_base = 48, C_base = 0
DEBUG lowest_pace = 21, A_base = 0, B_base = 48, C_base = 32
DEBUG lowest_pace = 14, A_base = 22, B_base = 48, C_base = 32
DEBUG lowest_pace = 9, A_base = 22, B_base = 62, C_base = 32
DEBUG lowest_pace = 6, A_base = 22, B_base = 62, C_base = 41
DEBUG lowest_pace = 4, A_base = 22, B_base = 62, C_base = 47
DEBUG lowest_pace = 2, A_base = 27, B_base = 62, C_base = 47

<144>th number is 323, seq is 1, base = 29, base = 68, base = 47
<144>th should be 323测试结果二N = 5364, A = 1788, B = 2025, C = 1551, ith = 220
DEBUG lowest_pace = 73, A_base = 0, B_base = 0, C_base = 0
DEBUG lowest_pace = 49, A_base = 0, B_base = 73, C_base = 0
DEBUG lowest_pace = 32, A_base = 49, B_base = 73, C_base = 0
DEBUG lowest_pace = 22, A_base = 49, B_base = 73, C_base = 32
DEBUG lowest_pace = 14, A_base = 71, B_base = 73, C_base = 32
DEBUG lowest_pace = 9, A_base = 71, B_base = 88, C_base = 32
DEBUG lowest_pace = 6, A_base = 71, B_base = 88, C_base = 41
DEBUG lowest_pace = 4, A_base = 71, B_base = 88, C_base = 47
DEBUG lowest_pace = 3, A_base = 76, B_base = 88, C_base = 47
DEBUG lowest_pace = 2, A_base = 76, B_base = 91, C_base = 47

<220>th number is 418, seq is 2, base = 77, base = 94, base = 49
<220>th should be 418测试结果三:N = 4915, A = 2045, B = 1040, C = 1830, ith = 345
DEBUG lowest_pace = 115, A_base = 0, B_base = 0, C_base = 0
DEBUG lowest_pace = 76, A_base = 115, B_base = 0, C_base = 0
DEBUG lowest_pace = 51, A_base = 115, B_base = 0, C_base = 76
DEBUG lowest_pace = 34, A_base = 115, B_base = 51, C_base = 76
DEBUG lowest_pace = 22, A_base = 150, B_base = 51, C_base = 76
DEBUG lowest_pace = 15, A_base = 150, B_base = 51, C_base = 98
DEBUG lowest_pace = 10, A_base = 150, B_base = 66, C_base = 98
DEBUG lowest_pace = 7, A_base = 150, B_base = 66, C_base = 108
DEBUG lowest_pace = 4, A_base = 157, B_base = 66, C_base = 108
DEBUG lowest_pace = 3, A_base = 157, B_base = 66, C_base = 112
DEBUG lowest_pace = 2, A_base = 161, B_base = 66, C_base = 112

<345>th number is 655, seq is 2, base = 164, base = 67, base = 114
<345>th should be 655

key 发表于 21-5-2009 21:51:34

原来的算法说可增可减,事实上是不应该减的,而是每次都从各自的base那里开始增一定量。
获取pace的方法也不是通过n/b^k这样的方式,而是通过剩余长度/b的方法
最后就是每次比较后,只有最小的那个队列的base增大,否则某个队列的增长就有可能过快,
导致结果错误(这个证明谁来做一下?)

基于第三点,如果有成百上千个队列,采用这样的方式来查找,可能效率不是太高,
也就是base case的值需要很大,lg(n)的效率才显示出来

klux 发表于 21-5-2009 22:25:11


相信没有什么东西比一段代码更有说服力了


这个不能认同。数学证明才有说服力。代码受到程序语言与运行环境的约束并不总是准确

key 发表于 22-5-2009 01:08:47

原帖由 klux 于 21-5-2009 22:25 发表 http://www.freeoz.org/forum/images/common/back.gif


这个不能认同。数学证明才有说服力。代码受到程序语言与运行环境的约束并不总是准确

哈哈。。。扛王

decisiontree 发表于 22-5-2009 01:57:14

原帖由 key 于 21-5-2009 21:51 发表 http://www.freeoz.org/forum/images/common/back.gif
原来的算法说可增可减,事实上是不应该减的,而是每次都从各自的base那里开始增一定量。
获取pace的方法也不是通过n/b^k这样的方式,而是通过剩余长度/b的方法
最后就是每次比较后,只有最小的那个队列的base增大, ...

兄弟写代码辛苦了:good 。我看了一百二十多行,被看晕了。:L 汗自己(有COMMNENTS可能会好点)。大概知道你的意思了。但我还没法证明它是线性复杂度。

key 发表于 22-5-2009 08:42:22

原帖由 decisiontree 于 22-5-2009 01:57 发表 http://www.freeoz.org/forum/images/common/back.gif


兄弟写代码辛苦了:good 。我看了一百二十多行,被看晕了。:L 汗自己(有COMMNENTS可能会好点)。大概知道你的意思了。但我还没法证明它是线性复杂度。

写代码是最容易的阶段了,hoho~
其实上面说到的有两个不同的问题,这个算法是O(lgn)的复杂度,算法的核心是69行开始至115行的一个while循环,步进的处理在96至114,重点是两点:
1. 找到最小的行
2. 让最小的行的base步进
3. 设置下一次的最短步进距离
而82至85行是用来微调各个队列的步进,使之加起来总和为 n 。这个地方我写得不赖。

由上面的1,2点,确保了每次至少步进lowest_pace的距离。而第3点确保了lowest_pace大致上等于lg(ith), 整个算法的时间复杂度为lg(ith)

key 发表于 22-5-2009 08:47:00

原帖由 key 于 22-5-2009 08:42 发表 http://www.freeoz.org/forum/images/common/back.gif


写代码是最容易的阶段了,hoho~
其实上面说到的有两个不同的问题,这个算法是O(lgn)的复杂度,算法的核心是69行开始至115行的一个while循环,步进的处理在96至114,重点是两点:
1. 找到最小的行
2. 让最小的 ...

不是说两点吗?写出来是三点,哈哈。。。。真是:L :D

decisiontree 发表于 22-5-2009 12:57:05

原帖由 key 于 21-5-2009 21:51 发表 http://www.freeoz.org/forum/images/common/back.gif
原来的算法说可增可减,事实上是不应该减的,而是每次都从各自的base那里开始增一定量。
获取pace的方法也不是通过n/b^k这样的方式,而是通过剩余长度/b的方法
最后就是每次比较后,只有最小的那个队列的base增大, ...

我觉得减是需要的,最小队列成功步进后,最大队列因该相应步退(如果没有退成比当前最小队列还小的值),才能保证总值n不变。

绿衣 发表于 23-5-2009 21:37:43

这么多高人啊,简直要崇拜了。。。

算法复杂度是我的软肋了,一听就头大,都怪数据结构老师长得抱歉、教得也太抱歉了:P 。可是偏偏去年碰到几个面试/笔试就有,有什么比较容易的办法不?大家帮忙给个意见?

coredump 发表于 23-5-2009 22:24:13

回复 #28 绿衣 的帖子

学算法得沉得下心,耐着性子,慢慢培养兴趣,还得亲自动手,多写点C代码,这方面我也很差:L

绿衣 发表于 23-5-2009 23:43:35

同意啊,算法需要多练多看。还是想知道对O( x )括号里那个怎么确定有方法/技巧吗?

如果说对O(n)还有感觉的话,可是n^2、 lgN和更复杂的..具体是怎么算出来的有比较容易掌握的方法吗?很多书和资料只给出个复杂度是多少,实在让我为难啊:L
页: [1] 2
查看完整版本: 两个有序数组中找第n个数(其中n小于任何一个数组的长度)