在很多情况下,描述一个算法的运行时间是有用的,为此,需要定义一套统一的算法渐进记号来合适的描述算法的运行效率。然后我们通过各种方法对不同的算法运行效率进行分析。
定义一:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集合的函数。如果 limn→∞f(n)g(n) lim n → ∞ f ( n ) g ( n ) 存在,并且等于某个常数 c(c>0) c ( c > 0 ) ,那么 f(n)=Θ(g(n)) f ( n ) = Θ ( g ( n ) ) 。通俗理解为 f(n) f ( n ) 和 g(n) g ( n ) 同阶, Θ Θ 用来表示算法的精确阶。
定义二:若存在正常量 c1 c 1 、 c2 c 2 ,使得对于足够大的 n n ,函数 f(n) f ( n ) 能“夹入” c1g(n) c 1 g ( n ) 与 c2g(n) c 2 g ( n ) 之间,则 f(n) f ( n ) 属于集合 Θ(g(n)) Θ ( g ( n ) ) ,记作 f(n)∈Θ(g(n)) f ( n ) ∈ Θ ( g ( n ) ) 。作为代替,我们通常记“ f(n)=Θ(g(n)) f ( n ) = Θ ( g ( n ) ) ”。
定义:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集 N N 上的函数。若存在正数 c c 和 n0 n 0 ,使得对一切 n≥n0 n ≥ n 0 都有 0≤f(n)≤cg(n) 0 ≤ f ( n ) ≤ c g ( n ) 成立,则称 f(n) f ( n ) 的渐进的上界是 g(n) g ( n ) ,记作 f(n)=O(g(n)) f ( n ) = O ( g ( n ) ) 。通俗的说n满足一定条件范围内,函数 f(n) f ( n ) 的阶不高于函数 g(n) g ( n ) 。
例如:设 f(n)=n2+n f ( n ) = n 2 + n ,则
f(n)=O(n2) f ( n ) = O ( n 2 ) ,取 c=2 c = 2 , n0=1 n 0 = 1 即可
f(n)=O(n3) f ( n ) = O ( n 3 ) ,取 c=1 c = 1 , n0=2 n 0 = 2 即可。显然,O(n^2)作为上界更为精确。
定义1:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集N上的函数。若对于任意正数 c c ,都存在 n0 n 0 ,使得对一切 n≥n0 n ≥ n 0 都有 0≤f(n)<cg(n) 0 ≤ f ( n ) < c g ( n ) 成立,则称 f(n) f ( n ) 的渐进的非紧确上界是 g(n) g ( n ) ,记作 f(n)=o(g(n)) f ( n ) = o ( g ( n ) ) 。通俗的说 n n 满足一定条件范围内,函数 f(n) f ( n ) 的阶低于函数 g(n) g ( n ) 。
定义2:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集合的函数。如果 limn→∞f(n)g(n)=0 l i m n → ∞ f ( n ) g ( n ) = 0 ,那么 f(n)=o(g(n)) f ( n ) = o ( g ( n ) ) 。通俗理解为 f(n) f ( n ) 低于 g(n) g ( n ) 的阶。
由O记号提供的渐近上界可能是渐近紧确的,也可能是非紧确的。
如: 2n2=O(n2) 2 n 2 = O ( n 2 ) 是渐近紧确的,而 2n=O(n2) 2 n = O ( n 2 ) 是非紧确上界。
例子: f(n)=n2+n f ( n ) = n 2 + n ,则 f(n)=o(n3) f ( n ) = o ( n 3 )
定义:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集 N N 上的函数。若存在正数 c c 和 n0 n 0 ,使得对一切 n≥n0 n ≥ n 0 都有 0≤cg(n)≤f(n) 0 ≤ c g ( n ) ≤ f ( n ) 成立,则称 f(n) f ( n ) 的渐进的下界是 g(n) g ( n ) ,记作 f(n)=Ω(g(n)) f ( n ) = Ω ( g ( n ) ) 。通俗的说n满足一定条件范围内,函数 f(n) f ( n ) 的阶不低于函数 g(n) g ( n ) 。
定义1:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集N上的函数。若对于任意正数 c c ,都存在 n0 n 0 ,使得对一切 n≥n0 n ≥ n 0 都有 0≤cg(n)<f(n) 0 ≤ c g ( n ) < f ( n ) 成立,则称 f(n) f ( n ) 的渐进的非紧确下界是 g(n) g ( n ) ,记作 f(n)=ω(g(n)) f ( n ) = ω ( g ( n ) ) 。通俗的说 n n 满足一定条件范围内,函数 f(n) f ( n ) 的阶高于函数 g(n) g ( n ) 。
定义2:设 f(n) f ( n ) 和 g(n) g ( n ) 是定义域为自然数集合的函数。如果 limn→∞f(n)g(n)=∞ lim n → ∞ f ( n ) g ( n ) = ∞ ,那么 f(n)=o(g(n)) f ( n ) = o ( g ( n ) ) 。通俗理解为 f(n) f ( n ) 高于 g(n) g ( n ) 的阶。
记号 | 含义 | 通俗理解 |
---|---|---|
Θ Θ | 渐进紧确界 | = |
O O | 渐进上界 | ≤ ≤ |
o o | 非紧的渐进上界 | < |
Ω Ω | 渐进下界 | ≥ ≥ |
ω ω | 非紧的渐进下界 | > |
+ 传递性: 所有五个标记
+ f(n)=Θ(g(n)) f ( n ) = Θ ( g ( n ) ) 且 g(n)=Θ(h(n))→f(n)=Θ(h(n)) g ( n ) = Θ ( h ( n ) ) → f ( n ) = Θ ( h ( n ) )
+ 自反性: O、 Θ Θ 、 Ω Ω
+ f(n)=Θ(f(n)) f ( n ) = Θ ( f ( n ) )
+ 对称性: Ω Ω
+ f(n)=Θ(g(n)) f ( n ) = Θ ( g ( n ) ) 当且仅当 g(n)=Θ(f(n)) g ( n ) = Θ ( f ( n ) )
+ 反对称性:
+ f(n)=O(g(n)) f ( n ) = O ( g ( n ) ) 当且仅当 g(n)=Ω(f(n)) g ( n ) = Ω ( f ( n ) )
+ f(n)=o(g(n)) f ( n ) = o ( g ( n ) ) 当且仅当 g(n)=ω(f(n)) g ( n ) = ω ( f ( n ) )
一些典型的增长阶:
递归方程:递归方程是使用小的输入值来描述一个函数的方程或者不等是。
求解递归方程的三个主要方法:
Master定理:设 a≥1 a ≥ 1 和 b>1 b > 1 是常数, f(n) f ( n ) 是一个函数, T(n) T ( n ) 是定义在非负整数级上的函数 T(n)=aT(nb)+f(n) T ( n ) = a T ( n b ) + f ( n ) , T(n) T ( n ) 可以如下求解:
换言之,我们比较 f(n) f ( n ) 和 nlogba n log b a :
求解递归方程 T(n)=T(5n6)+n T ( n ) = T ( 5 n 6 ) + n
证明或否证明 f(n)+o(f(n))=θ(f(n)) f ( n ) + o ( f ( n ) ) = θ ( f ( n ) )
证明:设k是任意常数正整数,则 logkn=o(n) log k n = o ( n )
证明 O(f(x))+O(g(x))=O(max(f(x),g(x))) O ( f ( x ) ) + O ( g ( x ) ) = O ( m a x ( f ( x ) , g ( x ) ) )
满足定义,所以 O(f(x)+g(x))=O(max(f(x),g(x))) O ( f ( x ) + g ( x ) ) = O ( max ( f ( x ) , g ( x ) ) ) 得证。
求解递归方程 T(n)=T(⌈n2⌉)+1 T ( n ) = T ( ⌈ n 2 ⌉ ) + 1 , ⌈x⌉ ⌈ x ⌉ 用以表示不小于x的整数中最小的一个。
对于平面上的两个点 p1=(x1,y1) p 1 = ( x 1 , y 1 ) 和 p2=(x2,y2) p 2 = ( x 2 , y 2 ) ,如果 x1≤x2 x 1 ≤ x 2 且 y1≤y2 y 1 ≤ y 2 ,则 p2 p 2 支配 p1 p 1 ,给定平面上的 n n 个点,请设计算法求其中没有被任何其他点支配的点。
设x坐标最大的点为 px(x1,y1) p x ( x 1 , y 1 ) ,y坐标最大的点为 py(x2,y2) p y ( x 2 , y 2 ) ,显然 px,py p x , p y 没有被支配,而 x<x2 x < x 2 或者 y<y1 y < y 1 的点都被二者中的一个支配,右上角的点没有被这两点支配,将这两点拿掉,我们可以对剩余的点递归的进行同样的操作。
感谢@TOM的建议,有一种更快的方案,可以这样做,按照x坐标优先的方式进行从大到小排序,而后维护一个当前的最大y值,大于当前最大y值的点则没有被支配,可以加入进去,然后更新最大y值,示意图就不画了。
bool cmp(const point &lhs, const point &rhs) {
if (rhs.x < lhs.x)
return true;
if (lhs.x < rhs.x)
return false;
return rhs.y < lhs.y;
}
std::vector find_no_dominated(const std::vector &points) {
int y_max = 0;
std::vector sorted = points;
sort(sorted.begin(), sorted.end(), cmp);
std::vector res;
for (point p:sorted) {
if (p.y >= y_max) {
res.push_back(p);
y_max = p.y;
}
}
return res;
}
如果一个数组 A[1…n] A [ 1 … n ] 中某个元素的数量超过其元素数量的一半,称其包含主元素,假设比较两个元素大小的时间不是常数但判定两个元素是否相等的时间是常数,要求对于给定数组 A A ,设计算法判定其是否有主元素,如果有,找到该元素。
(1)设计时间复杂性为 O(nlogn) O ( n log n ) 的算法完成该任务。
(2)设计时间复杂性为 O(n) O ( n ) 的算法完成该任务。
(1) 快速排序,而后遍历,时间复杂度为O(n*log*n)(比较不是常数时间)。参考@高靖龙的算法,把数组分成两个数组A,B,若A和B的主元素分别是a,b,那么主元素m必定是a或者b,然后使用遍历的方法就可以确定主元素是谁。
int find_master(const int A[], int start, int end) {
if (start + 1 == end)
return start;
int middle = (start + end) / 2;
int a = find_master(A, start, middle);
int b = find_master(A, middle, end);
int middle_size = (end - start) / 2;
int count_a = 0;
int count_b = 0;
for (int i = start; i != end; ++i) {
count_a += A[i] == A[a];
count_b += A[i] == A[b];
if (count_a > middle_size) return a;
if (count_b > middle_size) return b;
}
return -1;
}
int find_master(const int A[], int size) {
return find_master_log_n(A, 0, size);
}
(2) 若某个元素的个数超过总元素个数的一半,那么两两不同的元素抵消,那么最终剩下的元素一定是主元素。
int find_master(const int A[], int size) {
int master = 0, count = 1;
for (int i = 1; i != size; ++i) {
count += A[i] == A[master] ? 1 : -1;
if (count == 0) {
master = i;
count = 1;
}
}
count = 0;
for (int i = 0; i != size; ++i)
if (A[master] == A[i])count++;
return count > size / 2 ? master : -1;
}
这里给出的都是数组的下标。
证明:在有 n n 个数的序列中找出最大的数至少需要 n−1 n − 1 次比较
类似比赛晋级,两两配对比较,赢的再两两配对,最后得到冠军(最大的数),可以看成是一棵二叉树,以4个数为例:
graph TD;
line1_1((4)) --- line2_1((2))
line1_1 --- line2_2((4))
line2_1 --- line3_1((1))
line2_1 --- line3_2((2))
line2_2 --- line3_3((3))
line2_2 --- line3_4((4))
容易看出,找出最大的数比较次数是n-1。事实上只要二叉树的叶节点的个数为n,它的非叶节点数都是n-1,也就是说,无论如何比较,只要每个节点都要被比较到,最小比较次数总是为n-1,。
设计一个对7个元素进行排序的方法,保证其平均比较次数最少,要求证明这个结论
7个元素的排序决策树的节点一共有7!个。那么排序的决策树树高至少为 h≥log2(7!)≈12.2 h ≥ log 2 ( 7 ! ) ≈ 12.2 ,所以至少要比较13次。
考虑对3个元素排序,至少需要几次比较?答案是3次。但是如果有4个元素,要求对其中的3个元素建立全序关系,至少需要几次比较呢?答案仍然是3次。方法如下。假设有四个元素a、b、c、d。如下图所示,在a和b之间进行一次比较,在c和d之间进行一次比较,将这两次比较中较大的元素再进行一次比较,则至少有3个元素能够确定全序关系。
现在,我们仅通过3次比较,不仅得出了3个元素的全序关系,还知道另外一个元素与这3个元素中的一个元素的序关系。
假设有7个元素:a、b、c、d、e、f、g。
首先,我们使用3次比较,确定3个元素的全序关系,并且还额外的知道了另外一个元素与这3个元素中的一个元素的序关系。例如:a < b < c且a < d。
接着,我们使用折半查找将e插入到已序序列a、b、c中,至多需要2次比较。然后得到已序序列a、b、c、e。
由于最开始我们已经知道了剩下的未排序元素与已序序列中一个元素的序关系,因此至少可以排除掉一个元素。相当于将剩下的未排序元素插入到一个长度为3的已序序列,至多需要2次比较。例如将d插入到已序序列a、b、c、e中,已知a < d,则只需考虑将d插入到已序序列b、c、e中。
至多需要7次比较,即可完成5个元素的排序,而后我们将剩下的2个元素f、g插入其中,使用二分插入,至多比较6次。
综上,对7个元素进行排序,至多需要13次比较。
假设 a1,a2,…,an a 1 , a 2 , … , a n 是 {1,2,…,n} { 1 , 2 , … , n } 的一个随机排列,等可能的为 n! n ! 可能排列中的任意一种排列,当对列表 a1,a2,…,an a 1 , a 2 , … , a n 排序时,元素 ai a i 从它当前位置到达排序位置必须移动 |ai−i| | a i − i | 的距离,求元素必须移动的期望总距离 E∑ni=1[|ai−1|] E ∑ i = 1 n [ | a i − 1 | ]
解: