数据结构与算法365天特训营-算法基础

算法复杂性

        算法是指对特定问题求解步骤的一种描述
        算法具有以下特性。
        (1)有穷性:算法是由若干条指令组成的有穷序列,总是在执行若干次后结束,不可能永不停止。
        (2)确定性:每条语句有确定的含义,无歧义。
        (3)可行性:算法在当前环境条件下可以通过有限次运算实现。
        (4)输入输出:有零个或多个输入,一个或多个输出。
        “好”算法的标准如下。
        (1)正确性:正确性是指算法能够满足具体问题的需求,程序运行正常,无语法错误,能够通过典型的软件测试,达到预期的需求。
        (2)易读性:算法遵循标识符命名规则,简洁易懂,注释语句恰当适量,方便自己和他人阅读,便于后期调试和修改。
        (3)健壮性:算法对非法数据及操作有较好的反应和处理。
        (4)高效性:高效性是指算法运行效率高,即算法运行所消耗的时间短。算法时间复杂度就是算法运行需要的时间。我们一般用算法基本运算的执行次数来衡量算法的效率。
        (5)低存储性:低存储性是指算法所需要的存储空间低。算法占用的空间大小称为空间复杂度

时间复杂度

        算法运行需要的时间,一般将算法的执行次数作为时间复杂度的度量标准。例子:

sum = 0;        //运行1次
total = 0;        //运行1次
for(i = 1; i <= n; i++) {        //运行n+1次
    sum = sum + i;        //运行n次
    for(j = 1; j <= n; j++)        //运行n*n+1次
        total = total + i * j;        //运行n*n次
}

        把算法的所有语句的运行次数加起来:1+1+n+1+n+n(n+1)+nn,可以用一个函数T(n)表示:
T(n)=2n^2+3n+3

        当n足够大时,例如n=10^5时,T(n)=2\times 10^{10}+3\times 10^5 +3,我们可以看到算法运行时间主要取决于第一项,后面的甚至可以忽略不计。
        用极限表示为:
\lim_{n\to\infty}\frac{T(n)}{f(n)}=C\neq 0, C为不等于0的常数
        如果用时间复杂度的渐进上界表示,如下图:

渐近时间复杂度上界

        从图中可以看出,当n\geq n_0时,T(n)\leq Cf(n),当n足够大时,T(n)f(n)近似相等。因此,我们用O(f(n))来表示时间复杂度渐近上界,通常用这种表示法衡量算法时间复杂度。
        上面算法的时间复杂度渐近上界为O(f(n))=O(n^2),用极限表示为:
\lim_{n\to\infty}\frac{T(n)}{f(n)}=\lim_{n\to\infty}\frac{2n^2+3n+3}{n^2}=2\neq 0

        还有渐近下界符号Ω(T(n)\geq Cf(n)),如下图所示:

渐近时间复杂度下界

        从上图可以看出,当n\geq n_0时,T(n)\geq Cf(n),当n足够大时,T(n)f(n)近似相等,因此,我们用Ω(f(n))来表示时间复杂度渐近下界。
        渐近精确界符号\Theta(C_1f(n)\leq T(n)\leq C_2f(n)),如下图所示:

渐近时间复杂度精确界

        从上图可以看出,当n\geq n_0时,C_1f(n)\leq T(n)\leq C_2f(n),当n足够大时,T(n)f(n)近似相等。这种两边逼近的方式,更加精确近似,因此,用\Theta(f(n))来表示时间复杂度渐近精确界。
        我们通常使用时间复杂度渐近上界O(f(n))来表示时间复杂度。
        观察以下算法,并分析算法的时间复杂度。

i = 1;        //运行1次
while(i <= n){        //可假设运行x次
    i = i * 2;        //可假设运行x次
}

        观察上面算法,无法立即确定while及i=i*2运行了多少次。这时可假设运行了x次,每次运算后i值为2,2^2,2^3,...,2^x,当i=n时结束,即2^x=n时结束,则x=\log_2n,那么算法的运算次数为1+2\log_2n,时间复杂度渐近上界为O(f(n))=O(\log_2n)
        在算法分析中,渐近复杂度是对算法运行次数的粗略估计,大致反映问题规模增长趋势,而不必精确计算算法的运行时间。在计算渐近时间复杂度时,可以只考虑对算法运行时间贡献大的语句,而忽略那些运算次数少的语句,循环语句中处在循环内存的语句往往运行次数最多,即为对运行时间贡献最大的语句。
        注意:不是每个算法都能直接计算运行次数。
        例如下面这个算法,在a[n]数组中顺序查找x,返回其下标i,如果没找到,则返回-1。

findx(int x) {        //在a[n]数组中顺序查找x
    for(i = 0; i < n; i++) {
        if(a[i] == x) {
            return i;
        }
        return -1;
    }
}

        我们很难计算以上程序到底执行了多少次,因为运行次数依赖于x在数组中的位置,如果第一个元素就是x,则执行了1次(最好情况);如果最后一个元素是x,则执行n次(最坏情况);如果分布概率均等,则平均执行次数为(n+1)/2
        有些算法,如排序、查找、插入等算法,可以分为最好、最坏平均情况分别求算法渐近复杂度,但我们考察一个算法通常考察最坏的情况,而不是考察最好的情况,最坏情况对衡量算法的好坏具有实际意义

空间复杂度

        算法占用的空间大小。一般将算法的辅助空间作为衡量空间复杂度的标准。
        空间复杂度的本意是指算法在运行过程中占用了多少存储空间。算法占用的存储空间包括:
        (1)输入/输出数据;
        (2)算法本身;
        (3)额外需要的辅助空间。
        输入/输出数据占用的空间是必须的,算法本身占用的空间可以通过精简算法来缩减,但这个压缩的量是很小的,可以忽略不计。而在运行时使用的辅助变量所占用的空间,即辅助空间是衡量空间复杂度的关键因素。
        例子:两数交换,分析其空间复杂度。

swap(int x, int y) {
    int temp;
    temp = x;        //temp为辅助空间
    x = y;
    y = temp;
}

        该算法使用了一个辅助空间temp,空间复杂度为O(1)
        注意:递归算法中,每一次递归需要一个栈空间来保存调用记录,因此,空间复杂度需要计算递归栈的辅助空间。
        例子:计算n的阶乘,并分析其空间复杂度。

fac(int n) {        //计算n的阶乘
    if(n < 0) {        //小于零的数无阶乘值
        printf("n<0, data error!");
        return -1;
    } else if(n == 0 || n == 1) {
        return 1;
    } else {
        return n * fac(n - 1);
    }
}

        阶乘是典型的递归调用问题,递归包括递推和回归。递推是将原问题不断分解成子问题,直到达到结束条件,返回最近子问题的解;然后逆向逐一回归,最终到达递推开始的原问题,返回原问题的解。
        试求5的阶乘,5的阶乘的递推和回归过程如下图:


阶乘的递推和回归

        上图的递推、回归过程是我们从逻辑思维上推理,用图的方式形象地表达出来的,计算机内部是怎样处理的呢?计算机使用一种称为“栈”的数据结构,如下图:


5阶乘的进栈过程
5阶乘的出栈过程

        从上图的进栈、出栈过程中,我们可以清晰地看到,首先把子问题一步步地压进栈,直到得到返回值,再一步步地出栈,最终得到递归结果。在运算过程中,使用了n个栈空间作为辅助空间,因此阶乘递归算法的空间复杂度为O(n)。上面的阶乘算法的时间复杂度也为O(n),因为n的阶乘仅比n-1的阶乘多了一次乘法运算,fac(n)=n*fac(n-1)。如果用T(n)表示fac(n)的时间复杂度,可表示为:
T(n)=T(n-1)+1

=T(n-2)+1+1

...

=T(1) +...+1+1=n

        常见的算法复杂度有以下几类。
        (1)常数阶
                常数阶算法运行的次数是一个常数,如5、20、100。常数阶算法时间复杂度通常用O(1)表示
        (2)多项式阶
                很多算法时间复杂度是多项式,通常用O(n)、O(n^2)、O(n^3)等表示。
        (3)指数阶
                指数阶时间复杂度运行效率极差。常见的有O(2^n)、O(n!)、O(n^n)等。使用这样的算法要慎重。
        (4)对数阶
                对数阶时间复杂度运行效率较高,常见的有O(\log n)、O(n\log n)等。
        常见的时间复杂度函数曲线如下:

常见函数增量曲线

        从图中可以看出,指数阶增量随着x的增加而急剧增加,而对数阶增加缓慢。它们之间的关系为:
O(1)<O(\log n)<O(n)<O(n\log n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)

        设计算法时要注意算法复杂度增量的问题,尽量避免爆炸级增量。

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

推荐阅读更多精彩内容