[数据结构与算法]-常见算法时间复杂度(程序运行时间)计算法则

本文欢迎转载,转载前请联系作者,经允许后方可转载。转载后请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSDN


一.引言

算法(Algorithm)是为求解一个问题需要遵循的、被清楚指定的简单指令的集合。估算分析算法所消耗的资源是一个理论问题,所以需要一套规范一套系统架构在帮助我们分析。

二.四个定义

定义1:如果存在正常数c和n0使得当N≥n0时,T(N)≤cf(N),则记为T(N)=O(f(N))。
定义2:如果存在正常数c和n0使得当N≥n0时,T(N)≥cf(N),则记为T(N)=Ω(g(N))。
定义3:T(N)=θ(h(N))当且仅当T(N)=O(h(N))和T(N)=Ω(h(N))。
定义4:如果对每一正常数c都存在常数n0使得当N>n0时T(N) <
cp(N),则T(N)=o(p(N))。有时也可以说,如果T(N)=O(p(N))且T(N)≠θ(p(N)),则T(N)=o(p(N))。

解释:
定义1:总会存在某个点n0从它以后cf(N)最小与T(N)一样大,如果忽略常数因子c则,则说明f(N)最小与T(N)一样大。也就是说T(N)的增长率小于等于f(N)的增长率。例如,T(N)=100N,f(N)=N³,则c为1,n0为10,所以100N=O(N³)(N的立方级)。我们称这种记法为大O标记法。
定义2:T(N)的增长率大于等于g(N)的增长率。
定义3:T(N)的增长率等于h(N)的增长率。
定义4:T(N)的增长率小于p(N)的增长率。与大O不同的是,没有增长率相等的可能性。

三.运行时间计算原则

对与程序运行时间的运算,一般我们有两种方式。一种是事后统计法,一种是事前分析估算法。

对于前者,当然不是一个好方法。第一,如果想使用这个方法,我们需要把程序执行至少一次以上,进而统计时间,而在某些情况下,我们甚至没有条件去执行某些程序。其次,这个方法的统计依赖硬件环境、配置等因素,对统计结果会产生一定的误差。

所以,我们这里采用后者,来对程序运行时间进行预估。为了简化分析,我们通常采用如下的约定:不存在特定的时间单位。所以我们要摒弃前导的常数以及摒弃低阶项。这么做就是要计算大O运行时间。因为大O是一个上界(可以理解为最坏的情况),所以我们要分析最坏的情况,即不能低估程序的运行时间。程序可能提前结束,但是绝不可能比大O时间还要长。

四.常见法则

1.for循环

一个for循环的运行时间至多是该循环内部语句的运行时间乘以循环次数。

public void do1(int n) {
    for (int i = 0; i < n; i++) {
        num++;
    }
}

num++占用2个单元时间,循环次数为n次,所以一共是2N个单元时间。摒弃前导常数,最后得到该方法的时间复杂度为O(N)。

2.嵌套for循环

从里向外分析这些循环。循环内部的语句运行时间乘以所有for循环大小的乘积。

public void do2(int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            num++;
        }
    }
}

该方法的运时间为O(N²)。

3.顺序语句

将各个语句运行时间求和。记得摒弃常数和低阶项。

public void do3(int n) {
    for (int i = 0; i < n; i++) {
        num++;
    }
    num++;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            num++;
        }
    }
}

这个方法,总时间单元为2N + 2 + 2N*N。最终得到O(N²)。

4.if-else语句

一个if-else语句的运行时间为判断时间加上条件分支中运行时间最长的那个分支的运行时间。虽然有些时候这么估计会可能导致时间过长,但是根据我们提到的规约,决不能估计过低。

public void do4(int n) {
    if(num == 0) {
        for (int i = 0; i < n; i++) {
            num++;
        }
    }else {
        num++;
    }
}

由于num == 0的情况下的运行时间长于num++,所以该方法的运行时间为O(N²)。

5.对数计算

我们用经典的折半查找例子来说明这种情况。

public boolean Search(int k) {
    int left = 0;// 左边界变量  
    int right = N - 1;// 右边界变量  
    int middle;// 中位数变量  
    while (left <= right) {
        middle = (left + right) / 2;
        if (k < data[middle]) {
            right = middle - 1;// 查找前半段  
        } else if (k > data[middle]) {
            left = middle + 1;// 查找后半段  
        } else if (k == data[middle]) {
            System.out.println("Data[" + middle + "] = " + data[middle]);
            return true;
        }
        count++;
    }
    return false;
}

循环从right - left = n - 1开始 ,并保持right - low ≥ -1。每次循环后right - left的值至少将该次循环前的值折半,所以循环次数最多为{log(N - 1)} + 2。其中{log(N - 1)}表示大于log(N - 1)的最小整数。所以,运行时间为O(logN)。

五.常见时间复杂度排序

常见的时间复杂度从小到大排序依次为:Ο(1)<Ο(logN)<Ο(N)<Ο(NlogN)<Ο(N²)<Ο(N³)<…<Ο(2^N)<Ο(N!)
这里写图片描述

六.结果准确性分析

通常来说,有时分析的值会过大。如果是这样,要么就进一步细化分析,要不可能是平均运行时间显著小于最坏的情形。对于许多复杂的算法,最坏的情况是可以成立的,虽然它在实践过程中通常过大。所以一般来说,最坏的情形就是最好的分析结果。


站在前人的肩膀上前行,感谢以下博客及文献的支持。
算法的时间复杂度和空间复杂度-总结
用java实现折半查找
《数据结果与算法分析 机械工业出版社》

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值