数据结构与算法分析-C++描述 第2章 复杂度

1.大O记号

如果存在c,n_0>0,s.t.N>=n_0时,T(N)<=cf(N),则记T(N)=O(f(N))
含义:T(N)的增长率不超过f(N)的增长率,f(N)叫做T(N)的一个上界,一般取f(N)是T(N)的上确界。
例:1000N=O(N^2) 可以取定义中的n_0=1000,c=1
1000N=O(N) 可以取定义中的n_0=1,c=1000
大O运算的性质:
(1)对加法和乘法保持运算:
T_1(N)=O(f(N))且T_2(N)=O(g(N)),则
T_1(N)+T_2(N)=O(f(N)+g(N))=max(O(f(N)),O(g(N)))
T_1(N)T_2(N)=O(f(N)g(N))
(2)T(N)k次多项式,则T(N)=O(N^k)(多项式函数的非最高次项和最高次项前的系数都会被O掉)
(3)\forall k,log^kN=O(N),对数增长无论叠加多少次幂,永远慢于一次增长。
(4)\lim_{x \to +\infty}\frac{f(N)}{g(N)}=const \Rightarrow f(N)=O(g(N))
若极限为0,则g(N)增长更快。
(5)增长速度比较:c<logN<log^2N<N<NlogN<N^2<2^N

2.计算时间复杂度

(1)常数次输入输出加减乘除赋值返回判断算O(1)
(2)一个for循环的时间复杂度为循环体内的时间复杂度乘N,同理,嵌套的k层循环就乘N^k
(3)顺序语句:直接加和;条件语句:判断语句所用时间+用时较长的情况所用的时间。
(4)递归:可以推出递推公式再放缩。

3.例:最大子序列和

给定a_1,a_2,...,a_n\forall i,j,求\sum_{k=i}^ja_k的最大值。
输入:
6
-2,11,-4,13,-5,-2
输出:20
(1)暴力枚举

#include<iostream>
using namespace std;
int in[100], n, s, res = -100000;
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> in[i];
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            s = 0;
            for (int k = i; k <= j; k++) {
                s += in[k];
            }
            if (s > res) res = s;
        }
    }
    cout << res << endl;
}

时间复杂度:O(N^3)
(2)注意到s(i,j)=s(i,j-1)+in[j],可以把最内层的循环撤掉。

#include<iostream>
using namespace std;
int in[100], n, s, res = -100000;
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> in[i];
    for (int i = 0; i < n; i++) {
        s = 0;
        for (int j = i; j < n; j++) {
            s += in[j];
            if (s > res) res = s;
        }
    }
    cout << res << endl;
}

时间复杂度:O(N^2)
(3)分治:把数组从中间等分,最大子序列和为
max(左半边的最大子序列和,右半边的最大子序列和,跨越左右的最大子序列和)
显然只要解决跨越左右的最大子序列和:含有左边最后一个元素的最大子序列和+含有右边第一个元素的最大子序列和。

#include<iostream>
#include<algorithm>
using namespace std;
int a[1000];

int getmax(int a[], int left, int right) {
    if (left == right) return a[left];
    int center = (left + right) / 2;
    int leftmax = getmax(a, left, center);
    int rightmax = getmax(a, center + 1, right);
    int leftlast = 0, leftlastmax = -1000000;
    for (int i = center; i >= 0; i--) {
        leftlast += a[i];
        if (leftlast > leftlastmax) leftlastmax = leftlast;
    }
    int rightfirst = 0, rightfirstmax = -1000000;
    for (int i = center + 1; i <= right; i++) {
        rightfirst += a[i];
        if (rightfirst > rightfirstmax) rightfirstmax = rightfirst;
    }
    return max(rightfirstmax + leftlastmax, max(leftmax, rightmax));
}
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    cout << getmax(a, 0, n - 1);
    return 0;
}

时间复杂度:
N=1时:直接return了,显然T(N)=O(1)
N>1时:走了两个递归,每个递归的规模约为原问题的一半,再加上一个一层循环,即
T(N)=2T(N/2)+O(N)
解这个方程(没说怎么解),得:T(N)=O(NlogN)

(4)注意到若a[i]<0,则它不能是最大子序列的起点,否则去掉a[i]的序列更大,同理,和为负的子序列不能是最大子序列的前缀。在算法(2)中如果第一次检测到s(i,j)<0,说明此时从i到j是一个和为负的序列,它的后缀一定是负的(因为如果存在一个正的后缀,就一定存在一个负的前缀,这样加起来才能是负的,这与第一次检测到s(i,j)<0矛盾!),所以从i到j的每一个元素都不能做最大子序列的起点,否则就存在一个最大子序列的负前缀了。综上所述,可以只遍历一遍,s(i,j)<0时让i跳到j。

#include<iostream>
#include<algorithm>
using namespace std;
int a[1000];
int n;
int s;
int res = -100000;
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = 0; i < n; i++) {
        s += a[i];
        if (s > res) res = s;
        if (s < 0) s = 0;
    }
    cout << res;
    return 0;
}

时间复杂度:O(N)

4.时间复杂度是对数的情况

如果一个算法将O(1)的问题(不计读入)分成若干部分(如两部分),则它的时间复杂度为O(logN)。
(1)二分搜索
输入:n,x,已经排好升序的a_0,a_1,...,a_n,求ia_i=x

#include<iostream>
using namespace std;
int n;
int x;
int a[1000];
int main() {
    cin >> n >> x;
    for (int i = 0; i < n; i++) cin >> a[i];
    int low = 0, high = n - 1;
    while (low <= high) {
        int mid = (low + high) / 2;
        if (x < a[mid]) high = mid - 1;
        else if (x > a[mid]) low = mid + 1;
        else {
            cout << mid;
            return 0;
        }
    }
    cout << -1;
    return 0;
}

进行一次循环,数据规模折半,设一共进行了T次循环后,数据规模变成1:
N(\frac{1}{2})^T=1
T=log_2N
故时间复杂度为O(log(N))
(2)欧几里得算法
gcd(m,n):

long gcd(long m, long n) {
    while (n != 0) {
        long t = m % n;
        m = n;
        n = t;
    }
    return m;
}

可以证明,时间复杂度为O(logN)
(3)幂运算

long power(long x, int n) {
    if (n == 0)
        return 1;
    if (n == 1)
        return x;
    if (n % 2 == 0) {
        return power(x*x, n / 2);
    }
    else return power(x*x, n / 2)*x;
}

可以证明,时间复杂度为O(logN)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容