顺序统计量将长度为n n n 的数组升序排序后,则第i i i 个位置的数字是该数组的第i i i 小的量,称之为第i i i 顺序统计量
数组最小值是第1个顺序统计量,最大值是第n n n 个顺序统计量,中位数(又称下中位数)是第⌊ n + 1 2 ⌋ \left⌊\frac{n+1}{2}\right⌋ ⌊ 2 n + 1 ⌋ 个顺序统计量
⌊ n ⌋ ⌊n⌋ ⌊ n ⌋ 表示对n n n 向下取整,⌈ n ⌉ \left⌈n\right⌉ ⌈ n ⌉ 表示对n n n 向上取整
最大值和最小值若想要寻找n n n 个数字里的最大值或最小值,只需要进行( n − 1 ) (n-1) ( n − 1 ) 次比较
查找最小值 1 2 3 4 5 int min = a[0 ];for (int i=1 ;i<n;i++){ if (a[i] > min) min = a[i]; }
显然这已经是最优的算法了,我们称他为“遍历查找”,因为该算法是简单地遍历了整个数组来寻找最大或最小值,总共需要( n − 1 ) (n-1) ( n − 1 ) 次比较,即S ( n ) = n − 1 S(n)=n-1 S ( n ) = n − 1
现在我们要研究如何以尽可能低的时间复杂度来同时求出数组的最大值和最小值
传统方法最容易想到的方法就是重复两次“遍历查找”,分别找出最大和最小值,那么就需要S ( n ) = 2 ( n − 1 ) S(n)=2(n-1) S ( n ) = 2 ( n − 1 ) 次比较,但是这显然不是最佳的方案.
设存在数组A = [ 9 , 0 , 1 , 2 , 100 ] A=[9,0,1,2,100] A = [ 9 , 0 , 1 , 2 , 1 0 0 ]
在寻找最小值时,当遍历到第2个元素时,由于0<9,所以最小值被替换成0,同时我们也可以得知0一定不是最大值,因为有个9比它更大.
在寻找最大值时,采用了相同的算法,导致0又被比较了一遍,而现在0不可能是最大值.
优化算法通过上面的传统方法,我们可以发现减少比较次数的关键是减少不必要的比较,这就给我们一个思路,将一个数组划分为k k k 段,找出这k k k 个数的最大最小值,然后分别和整个数组的最大最小值比较
设查找长度为n n n 的数组中的最大最小值,需要比较至多f ( n ) f(n) f ( n ) 次,数组被划分为n k \frac nk k n 段,每段k k k 个数字,每段分别需要比较f ( k ) f(k) f ( k ) 次就可以得到最大最小值,则共比较⌈ n k ⌉ f ( k ) \left⌈\frac nk\right⌉f(k) ⌈ k n ⌉ f ( k ) 次就可以得到n k \frac nk k n 个最大值数组和最小值数组,在查找整个数组的最小值时只需要从最小值数组里寻找就行了,而查找整个数组的最大最小值又需要比较⌈ n k ⌉ \left⌈\frac nk\right⌉ ⌈ k n ⌉ 次,于是得到f ( n ) f(n) f ( n ) 的函数
f ( n ) = ⌈ n k ⌉ f ( k ) + 2 ⌈ n k ⌉ f(n)=\left⌈\frac nk\right⌉f(k)+2\left⌈\frac nk\right⌉ f ( n ) = ⌈ k n ⌉ f ( k ) + 2 ⌈ k n ⌉
同理对于长度为k的数组,我们又可以将其分为更小的段,一直分下去,直到k = 2 k = 2 k = 2 时,f ( 2 ) = 1 f(2)=1 f ( 2 ) = 1 ,因为两个数只需要比较一次就能得到最大最小值,我们假设将长度为n n n 的数组划分为长度为k1的数段,将长度为k ( i − 1 ) k(i-1) k ( i − 1 ) 的数段划分为长度为k i ki k i 的更小的数段,带入f ( n ) f(n) f ( n ) ,得
f ( n ) = ⌈ n k 1 ⌉ f ( k 1 ) + 2 ⌈ n k 1 ⌉ = ⌈ n k 1 ⌉ ⌈ k 1 k 2 ⌉ f ( k 2 ) + 2 ( ⌈ n k 1 ⌉ + ⌈ k 1 k 2 ⌉ ) = ⌈ n k 1 ⌉ f ( k i ) ∏ x = 2 i ⌈ k x − 1 k x ⌉ + 2 ( ⌈ n k 1 ⌉ + ∑ x = 2 i ⌈ k x − 1 k x ⌉ ) ≥ ⌈ n k 1 ⌉ f ( k i ) ∏ x = 2 i k x − 1 k x + 2 ( ⌈ n k 1 ⌉ + ⌈ ∑ x = 2 i k x − 1 k x ⌉ ) ≥ ⌈ n k 1 ⌉ + 2 ( ⌈ n k 1 ⌉ + ⌈ ∑ x = 2 i k x − 1 k x ⌉ ) = 3 ⌈ n k 1 ⌉ + 2 ⌈ ∑ x = 2 i k x − 1 k x ⌉ \begin{array}{ll} f(n)&=\left⌈\frac n{k_1}\right⌉f(k_1)+2\left⌈\frac n{k_1}\right⌉ \\\\ &=\left⌈\frac n{k_1}\right⌉\left⌈\frac{k_1}{k_2}\right⌉f(k_2)+2\left(\left⌈\frac n{k_1}\right⌉+\left⌈\frac{k_1}{k_2}\right⌉\right) \\\\ &=\left⌈\frac n{k_1}\right⌉f(k_i)\prod\limits_{x=2}^{i}\left⌈\frac{k_{x-1}}{k_x}\right⌉+2\left(\left⌈\frac{n}{k_1}\right⌉+\sum\limits_{x=2}^{i}\left⌈\frac{k_{x-1}}{k_x}\right⌉\right) \\\\ &\geq \left⌈\frac{n}{k_1}\right⌉f(k_i)\prod\limits_{x=2}^{i}\frac{k_{x-1}}{k_x}+2\left(\left⌈\frac{n}{k_1}\right⌉+\left⌈\sum\limits_{x=2}^{i}\frac{k_{x-1}}{k_x}\right⌉\right) \\\\ &\geq \left⌈\frac{n}{k_1}\right⌉+2\left(\left⌈\frac{n}{k_1}\right⌉+\left⌈\sum\limits_{x=2}^{i}\frac{k_{x-1}}{k_x}\right⌉\right) \\\\ &= 3\left⌈\frac{n}{k_1}\right⌉+2\left⌈\sum\limits_{x=2}^{i}\frac{k_{x-1}}{k_x}\right⌉ \end{array} f ( n ) = ⌈ k 1 n ⌉ f ( k 1 ) + 2 ⌈ k 1 n ⌉ = ⌈ k 1 n ⌉ ⌈ k 2 k 1 ⌉ f ( k 2 ) + 2 ( ⌈ k 1 n ⌉ + ⌈ k 2 k 1 ⌉ ) = ⌈ k 1 n ⌉ f ( k i ) x = 2 ∏ i ⌈ k x k x − 1 ⌉ + 2 ( ⌈ k 1 n ⌉ + x = 2 ∑ i ⌈ k x k x − 1 ⌉ ) ≥ ⌈ k 1 n ⌉ f ( k i ) x = 2 ∏ i k x k x − 1 + 2 ( ⌈ k 1 n ⌉ + ⌈ x = 2 ∑ i k x k x − 1 ⌉ ) ≥ ⌈ k 1 n ⌉ + 2 ( ⌈ k 1 n ⌉ + ⌈ x = 2 ∑ i k x k x − 1 ⌉ ) = 3 ⌈ k 1 n ⌉ + 2 ⌈ x = 2 ∑ i k x k x − 1 ⌉
由于k x kx k x 一定是正数,因此i i i 为 1 时,右边的连加消失,f ( n ) f(n) f ( n ) 取到最小值,即k 1 = 2 k_1=2 k 1 = 2
f ( n ) m i n = f ( n ) ∣ i = 1 = 3 ⌈ n 2 ⌉ f(n)_{min}=f(n)|_{i=1}=3\left⌈\frac{n}{2}\right⌉ f ( n ) m i n = f ( n ) ∣ i = 1 = 3 ⌈ 2 n ⌉
得到上述结果的前提是事先定义好了m i n min m i n 和m a x max m a x 的初值,但是实际应用中我们可以根据数组动态调整初值,如果长度为偶数,则先比较前两项,大的作为m a x max m a x ,小的作为m i n min m i n ,共比较( 3 n 2 − 1 ) (\frac{3n}{2}-1) ( 2 3 n − 1 ) 次;如果长度为奇数,则min和max都取第一项,因此实际的比较次数应该是( 3 n 2 − 2 ) (\frac{3n}{2}-2) ( 2 3 n − 2 ) 次,即最终比较次数应该是
f ( n ) = 3 ⌊ n 2 ⌋ f(n)=3\left⌊\frac{n}{2}\right⌋ f ( n ) = 3 ⌊ 2 n ⌋
通过理论我们得知了只要把数组中的数两两比较,其中的较小者和最小值比较,而较大者和最大值比较,就可以实现用最少的比较次数同时求出最大最小值
查找最大最小值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void FindMinAndMax (int * a, int len) { int min, max, i; if (len % 2 == 0 ){ if (a[0 ] < a[1 ]){ min = a[0 ]; max = a[1 ]; }else { min = a[1 ]; max = a[0 ]; } i = 3 ; }else { min = max = a[0 ]; i = 2 ; } while (i < len){ if (a[i-1 ] < a[i]){ if (a[i-1 ]<min) min = a[i-1 ]; if (a[i]>max) max = a[i]; }else { if (a[i]<min) min = a[i]; if (a[i-1 ]>max) max = a[i-1 ]; } i += 2 ; } cout << "min:" << min << endl; cout << "max:" << max << endl; }
第i顺序统计量如果想要找到数组里的第 i 顺序统计量,也就是第 i 小的数字,通常的办法是把整个数组排序,然后直接取出对应位置的数字.快速排序是一个很好的办法,快速排序将数组划分为两个子数组来排序.实际上,我们只关心其中某一个位置的数字,因此对于快速排序划分出来的两个子数组,我们仅需要排序其中一个就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int find (int *a, int left, int right, int k) { if (left >= right) { return a[left]; } int i = left, j = right, key = a[left]; while (i < j) { while (a[i] < key) { ++i; } while (a[j] > key) { --j; } if (i < j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } } int s = i - left + 1 ; if (s == k) { return a[i]; } else if (s > k) { return find (a, left, j, k); } else { return find (a, j + 1 , right, k - s); } }
该方法的比较次数受到随机因素的影响,因此是一个随机算法.在平均情况下,它的时间复杂度能够达到O(n)