《Mathematical Analysis of Algorithms》中有关“选择第t大的数”的算法分析

开头废话

这个问题是Donald.E.Knuth在他发表的论文Mathematical Analysis of Algorithms中提到的,这里对他的算法分析过程给出了更详细的解释。

问题描述:

给定一个数组a[1,2,...,n],用尽量少的比较次数找出数组中第t大的数。(假定这n个数两两不同)。

算法描述:

对于这个问题,可以很容易想到对应的算法。一个 \(O(n\log n)\) 的排序算法总能解决问题(然鹅今天我们并不对数组进行完全的排序)。
参照快速排序中的Partition操作,将元素a[i]放到某个位置\(k\),使得排在它前面的元素都比它大(但不一定按照从大到小的次序排列),后面的元素都比它小。再根据a[i]的位置\(k\)与\(t\)的大小关系,缩小查找范围再对子问题求解。
对于每一次Partition操作,会有这样的3种情况:
(1).若\(k=t\),算法结束。
(2).若\(k>t\),则对a[i]~a[k-1]递归地求解
(3).若\(k<t\),则对a[k+1]~a[j]递归地求解

时间复杂度分析

在这个问题的求解过程中,产生子问题的规模不断缩小。其中影响子问题的变量有\(n\)(数组的长度)和\(t\)(待查找的t)。Knuth记\(C_{n,t}\)为在\(n\)个元素的数组中选择第\(t\)大的数所需的平均比较次数,这里有一个前提,我们假设数组的排列是随机的,每一次Partition找到第1,第2,...,第n大的数概率均为\(\frac 1 n\)。

于是我们可以得到这样的式子:
\[
\begin {aligned}
C_{1,1}&=0\C_{n,t}&=n-1+\frac 1n (A_{n,t}+B_{n,t}+0)
\end {aligned}
\]其中\(A_{n,t}\)和\(B_{n,t}\)的定义如下:\[
\begin {aligned}
A_{n,t}&=C_{n-1,t-1}+C_{n-2,t-2}+\cdots+C_{n-t+1,1}\B_{n,t}&=C_{t,t}+C_{t+1,t}+\cdots+C_{n-1,t}
\end {aligned}
\]这里\(A_{n,t}\)对应的是递归过程中所有\(k<t\)的情况。对于这些情况,我们从数组的第\(k+1\)项开始向后的部分进行求解,如果把这部分看作一个新的数组,那么原始数组中第\(t\)大的数,在新的数组中是第\(t-k\)大的,也就是说这部分子问题是查找长度为\(n-k\)的数组中第\(t-k\)大的元素,其中\(1\leq k \leq n.\)

类似的,\(B_{n,t}\)对应所有\(k>t\)的情况,将数组第一项到第\(k-1\)项取出,看作一个新的数组,原始数组中第\(t\)大的数,在这新的数组中仍然是第\(t\)大,所以这部分的子问题是在长度为\(k-1\)的数组中选择第\(t\)大的数,其中\(t+1\leq k \leq n.\)

括号内剩下的一项\(0\),对应的是\(k=t\)的情况,因为此时算法结束,不需要再求解子问题,所以比较次数为\(0.\)括号外的\(n-1\)是一次Partition要进行的比较次数。

这样,括号内就等于所有可能规模子问题的比较次数的总和,将它乘以\(\frac 1n\),就得到子问题比较次数的数学期望,即我们所求的平均情况下的预期比较次数。

通过观察我们可以得到以下的递推公式:
\[
\begin {aligned}
A_{n+1,t+1}&=C_{n-1,t-1}+C_{n-2,t-2}+\cdots+C_{n-t+1,1}+C_{n,t}=A_{n,t}+C_{n,t}\B_{n+1,t}&=C_{t,t}+C_{t+1,t}+\cdots+C_{n-1,t}+C_{n+1-1,t}=B_{n,t}+C_{n,t}
\end{aligned}
\]由上述等式作差消法,可以得到:
\[
(n+1)C_{n+1,t+1}-nC_{n,t+1}-nC_{n,t}+(n-1)C_{n-1,t}\=(n+1)n-n(n-1)-n(n-1)+(n-1)(n-2)\\+(A_{n+1,t+1}-A_{n,t})-(A_{n,t+1}-A_{n-1,t})+(B_{n+1,t+1}-B_{n,t+1})-(B_{n,t}-B_{n-1,t})
\=2+C_{n,t}-C_{n-1,t}+C_{n,t+1}-C_{n-1,t}
\]合并同类项即可得到:
\[
(n+1)C_{n+1,t+1}-(n+1)C_{n,t+1}-(n+1)C_{n,t}+(n+1)C_{n-1,t}=2\\\Downarrow\C_{n+1,t+1}-C_{n,t+1}-C_{n,t}+C_{n-1,t}=\frac{2}{n+1}
\]
接下来我们考察边界条件,当\(t=1\)时,由以上的式子我们可以得到下述方程组:
\[
\left\{
\begin{array}{l}
C_{n,1}= n-1+\frac{1}{n}(C_{1,1}+C_{2,1}+\cdots +C_{n-1,1})\ B_{n,1}=C_{1,1}+C_{2,1}+\cdots+C_{n-1,1}\ B_{n+1,1}=B_{n,1}+C_{n,1}\ C_{n,1}=n-1+\frac{1}{n}(B_{n,1})\ C_{n+1,1}=n+\frac{1}{n+1}(B_{n+1,1})
\end{array}
\right.
\]消去方程组中包含\(B\)的项,可以得到:
\[
\begin{aligned}
(n+1)C_{n+1,1}-nC_{n,1} &= (n+1)n-n(n-1)+C_{n,1}\C_{n+1,1}-C_{n,1}&=2-\frac{2}{n+1} \quad\quad(*)
\end{aligned}
\]接下来求解\(C_{n,1}\):
列出方程组:
\[
\left\{
\begin{array}{c}
\begin{aligned}
C_{1,1}&=0\C_{2,1}-C_{1,1}&=2-\frac22\C_{3,1}-C_{2,1}&=2-\frac23\\cdots\C_{n,1}-C_{n-1,1}&=2-\frac2n\\end{aligned}
\end{array}
\right.\\]将以上\(n\)个方程求和,最终左边只剩下\(C_{n,1}\),得到如下式子:
\[
\begin{aligned}
C_{n,1}&=2(n-1)-2\sum_{k=2}^n \frac1k\\quad\Downarrow
\C_{n,1}&=2n-2\sum_{k=1}^n\frac1k=2n-2H_n
\end{aligned}
\]这里的\(H_n\)表示调和级数的前\(n\)项部分和。
由于问题具有的对称性(这部分可自行证明),\(C_{n,n}=C_{n,1}=2n-2H_n\),将此式记作\((\Delta)\)
由\((*)\)式,可以列出以下方程组:
\[
\left\{
\begin{array}{l}
(C_{n+1,t+1}-C_{n,t})-(C_{n,t+1}-C_{n-1,t})=\frac2{n+1}\(C_{n,t+1}-C_{n-1,t})-(C_{n-1,t+1}-C_{n-2,t})=\frac2{n}\\quad\quad\quad\quad\quad\quad\quad\quad\cdots\(C_{t+2,t+1}-C_{t+1,t})-(C_{t+1,t+1}-C_{t,t})=\frac2{t+2}\\end{array}
\right.\\]再次对这\(n-t\)个方程累加,并联立\((\Delta)\)式,可以得到:
\[
\begin{aligned}
C_{n+1,t+1}-C_{n,t}&=\frac{2}{n+1}+\frac{2}{n}+\cdots+\frac{2}{t+2}+C_{t+1,t+1}-C_{t,t}\&=2(H_{n+1}-H_{t+1})+2-\frac{2}{t+1}
\end{aligned}
\]依次写出\(C_{n,t}-C_{n-1,t-1}\)到\(C_{2,2}-C_{1,1}\)的\(n-1\)个方程并再次累加(过程略去),可以推出:
\[
C_{n,t}=2\sum_{2\leq k\leq t}(H_{n-t+k}-H_{k}+1-\frac1k)+C_{n+1-t,1}
\]
化简后:
\[
C_{n,t}=2((n+1)H_n-(n+3-t)H_{n+1-t}-(t+2)H_t+n+3),\quad(1\leq t\leq n)
\]
由于\(t\)与\(n\)同阶,且平均情况下\(t\)的数学期望\(E(t)=\frac 2n\),又 \(H_n=\Theta(\log n)\) ,所以:
\[
C_{n,t}=O(n\log n)
\]至此,时间复杂度的证明结束。

原文地址:https://www.cnblogs.com/allegro-vivace/p/12520377.html

时间: 2024-08-29 03:49:18

《Mathematical Analysis of Algorithms》中有关“选择第t大的数”的算法分析的相关文章

笔试题--在一个整数数组中找出第5大的数

/** * 在一个整数数组中找出第5大的数 * 思路是首先在数组中找到最小数,然后依次找到第五大的数 * * @param array * @return */ public static int[] selectionSort(int[] array) { if (array.length == 0) return array; for (int i = 0; i < array.length; i++) { int minIndex = i; for (int j = i; j < arr

[二分法]线性时间内在数组中找出第k大的数

#include "stdafx.h" #include "iostream" using namespace std; //参数为 数组指针,开始下标, 结束下标, 第K大数(k从0开始) int rand_par(int array[], int start, int end, int th){ if( start < 0 || end < 0 || th < 0 || end < start ){ return -1; } int le

从数组中找出第K大的数

利用改进的快排方法 public class QucikFindMaxK { static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; } // 升序排列,比arr[pivotIndex]值小的数都在arr[pivotIndex]左边,大的数都在右边 static int partition(int arr[], int left, int right, int p

寻找数列中第K大的数

版权所有 未经允许 请勿擅自商用 转载请指明出处 最早看到这个问题是在那本Mark写的数据结构与算法分析的书中引论部分,当时就是瞅瞅,到了最近,在实际应用中,我需要查找一些列的数中第k大的数时,我才重新回顾品味这个问题.现在,实际问题中,我还暂时没有解决问题,但是这段思考过程很有意思,在这里给大家品味下. 具体的问题有点复杂,在这里就不赘述了,暂且将这个问题形式化的描述如下: 给定一个有限无序数列记做{an},从这个数列中找出第k大的数. 输入:数列{an},k 输出:第k大的数 首先,有个最简

《Principles of Mathematical Analysis》-chaper1-实数系与复数系

今天我们开始简单的介绍数学分析这门课程,参考教材是Walter Rudin著的<Principles of Mathematical Analysis> 对于一门新课你最开始可能会问的是:这门课讲述了一个什么故事?简单的翻阅了一下这本书的目录,数学分析这一块,里边有微积分里的东西:微分法.级数.多元函数这类东西,当然也有离散数学中一些集合.关系的东西,因此,我们不能妄下论断说,数学分析就是带证明的微积分,但是,数学分析到底是讲述了一个怎样的故事呢?让我们怀揣着这个问题走进这门课. 数学作为工具

多维建模 在 Analysis Services 项目中定义数据源视图(一)

我本机安装的mssql2012,在下图中我们看到SQL Server Data Tools(SSDT),制作SSRS报表.SSIS项目.及SSAS项目都可以在DataTools应用程序中操作.       新建项目后,已安装模板中有一个商业智能=>Analysis Services, 选择第一个Analysis Services多维和数据挖掘项目后,点击确定. 下面是一个新建的Analysis Services项目示例, 现在我们来创建数据源视图, 1.定义连接可以点击新建按钮 2.使用服务账户

php array_rand()函数从数组中随机选择一个或多个元素

php使用array_rand()函数从数组中随机选择一个或多个元素的方法. 使用array_rand() 函数从数组中随机选出一个或多个元素,并返回. array_rand(array,number) 参数 描述 array 必需.规定输入的数组参数. www.jbxue.com number 可选.默认是 1.规定返回多少个随机的元素. 例子: <?php $a=array("a"=>"Dog","b"=>"Cat

STL中慎重选择删除元素的方法

 一.要删除容器中有特定值的所有对象 1.如果容器是vector.string或deque,则使用erase-remove习惯用法.例如: vector<int> c; c.erase(remove(c.begin(),c.end(),1963),c.end());//删除值是1963的元素 下面讲一下算法remove: template<classForwardIterator,class T> ForwardIteratorremove(ForwardIterator fi

Eclipse插件开发中的选择监听机制(Selection Provider-Listener)

Eclipse插件开发中的选择监听机制(Selection Provider-Listener) 监听机制是eclipse插件开发或rcp应用开发中常用的技术,比如点击TableViewer或TreeViewer中的某个元素,需要针对当前selection做出某些处理. 实现方式一般有两种情况,即selection provider 和listener在一个视图中,或在不同的视图中: 1.selection provider自己增加监听进行处理 前者直接让selection provider实现