【数据结构】了解,ins下 时间复杂度

发布时间:2024年01月16日

?一、算法时间复杂度的定义

定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作T(n) = O(f(n)).它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。

?即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度


// 请计算一下Func1中++count语句总共执行了多少次?
void Func1(int N)
{
    int count = 0;
    for (int i = 0; i < N ; ++ i)
    {
        for (int j = 0; j < N ; ++ j)
        {
            ++count;
        }
    }
    for (int k = 0; k < 2 * N ; ++ k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
        ++count;
    }
    printf("%d\n", count);
}

这里Func1的基本操作的执行次数:

F(N)=N^2+2*N+10


二、什么是大O(大O表示法)

这里的大O指的是什么呢,一提到时间复杂度,大家都懂O(1)、O(n)、O(n^2)等等,却不明白括号前面的O是什么意思。

在实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概的执行次数那这里就需要用到大O的渐进表示法。

在这边我们如果想要求出大概的执行次数,那就需要找到函数表达式中最具有决定性的一项(抓大头)

例如:F(N)=N^2+2*N+10这个函数表达式中,哪一项是能决定整个式子大小的?那必然是项数最大的那一项。

从图中看来,我们可以用极限的思想来看这个式子。当N无限增大的时候,就会发现后面这两项对整个式子的影响微乎其微。此时这个表达式的时间复杂度:O(N^2)。


前面我们简单的介绍了大O表示法,想必大家也对这个方法有了初步的印象。接下来,让我们进行更深入的了解吧。

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。

那么如何分析一个算法的时间复杂度呢?即如何推导大O阶呢?

推导大O阶:

  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且其系数不是1,则去除与这个项相乘的系数。

得到的结果计算大O阶。

现在,仿佛是得到游戏攻略一样,我们好像已经得到了一个推导算法时间按复杂度的万能公式。

通过上面我们会发现大O表示法去掉了那些对结果影响不大的项,简洁明了地表示出了执行次数。


最好情况,最坏情况,平均情况

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

我们查找一个有n个随机数字数组中地某个数字,最好的情况是第一个数字就是,那算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置上待着,那么算法的时间复杂度为O(n)。

通常,除非特殊指定,我们提到的运行时间都是最坏情况的运行时间,也就是最坏时间复杂度。


三、一些实际的案例

例一、

// 计算Func2的时间复杂度?
void Func2(int N)
{
    int count = 0;
    for (int k = 0; k < 2 * N ; ++ k)
    {
        ++count;
    }
    int M = 10;
    while (M--)
    {
        ++count;
    }
    printf("%d\n", count);
}

时间复杂度:O(N)

理解:整体的函数表达式为:F(N)=2*N+10。但根据大O阶表示法,我们只需要保留最高阶项,并把最高阶项的系数给化为1。

例二、

// 计算Func3的时间复杂度?
void Func3(int N, int M)
{
    int count = 0;
    for (int k = 0; k < M; ++ k)
    {
        ++count;
    }
    for (int k = 0; k < N ; ++ k)
    {
        ++count;
    }
    printf("%d\n", count);
}

时间复杂度:O(M+N)

理解:函数内部有两个循环,一个执行N次,另一个执行M次。但是函数中并未给我们说明M和N的区别,所以在无法辨别的情况下,时间复杂度为O(M+N)。如果此时说明M远大于N,时间复杂度为O(M);如果说明N远大于M,时间复杂度为O(N);如果说明M和N一样大,时间复杂度为O(N)或O(M)。

例三、

// 计算Func4的时间复杂度?
void Func4(int N)
{
    int count = 0;
    for (int k = 0; k < 100; ++ k)
    {
        ++count;
    }
    printf("%d\n", count);
}

时间复杂度:O(1)

理解:有些人会认为这个函数的时间复杂度为O(100),但是根据大O阶表示法的第一条:用常数1取代运行时间中的所有加法常数。所以时间复杂度为O(1)。

例四、

// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

时间复杂度:O(N)

理解:strchr这个库函数的作用是在str这个字符数组中查找一个字符。。如果这个字符在第一位,那时间复杂度为O(1)。如果这个字符在最后一位,那时间复杂度为O(N)。此时这就涉及到了最坏时间复杂度的知识点,一般就是无特殊说明,指的是最坏的时间复杂度。

例五、

??有关冒泡排序的知识我觉得这位作者的讲解很棒https://wzill.blog.csdn.net/

相关知识点:????https://blog.csdn.net/weixin_45490198/article/details/130959009

// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n)
{
    assert(a);
    for (size_t end = n; end > 0; --end)
    {
        int exchange = 0;
        for (size_t i = 1; i < end; ++i)
        {
            if (a[i-1] > a[i])
            {
                Swap(&a[i-1], &a[i]);
                exchange = 1;
            }
        }
    if (exchange == 0)
    break;
    }
}

时间复杂度:O(N^2)

理解:这里是个冒泡排序,最终的结果是一个等差数列的和。当然,这个排序的时间复杂度也分最好与最坏。

首先,在最理想的情况下,时间复杂度为O(1)。但是,这明显是不可能的。毕竟我们不知道冒泡排序是有序还是乱序,而且如果数组的长度过长,那我们看也是看不过来的。

其次,另一种情况是这个数组已经是有序或者是只有一两个数是乱序的。那时间复杂度就为O(N-1)。

最后就是最坏的情况,整个数组都是乱序的。这样子就是如图所示

所以时间复杂度就为O(N^2)。?

例六、

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{
    assert(a);
    int begin = 0;
    int end = n-1;
// [begin, end]:begin和end是左闭右闭区间,因此有=号
    while (begin <= end)
    {
        int mid = begin + ((end-begin)>>1);
        if (a[mid] < x)
            begin = mid+1;
        else if (a[mid] > x)
            end = mid-1;
        else
            return mid;
    }
    return -1;
}

时间复杂度:O(logN)

理解:

?所以就是2^x=N,利用数学的知识换个底就能求出答案。

补充:对数在文本中并不好写,支持一些展示公式编辑器才方便时间复杂度简写成logN。但是只有log2N可以简写成logN,其他的都要写出来。

例七、

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
    if(0 == N)
        return 1;
    return Fac(N-1)*N;
}

?时间复杂度:O(N)

理解:

?总结:递归算法时间复杂度是多次调用的次数累加。

例八、

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{
    if(0 == N)
        return 1;
 
    for(int i=0; i<N; i++)
    {
        ~
    )
 
    return Fac(N-1)*N;
}

时间复杂度:O(N^2)

l理解:这个递归里面有循环,那N随着递归会逐渐变小,所以这层循环的本质就是等差数列求和,那时间复杂度就是O(N^2)。

//计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
	if (N < 3)
		return 1;

	return Fib(N - 1) + Fib(N - 2);
}

时间复杂度:O(N^2)

理解:

所以结果可以用等比数列求和公式计算就可以得出时间复杂度?

?

补充:常见的时间复杂度?

O(1)常数阶
O(N)线性阶
O(N^2)平方阶
O(logN)对数阶
O(N*logN)N*logN阶
O(N^3)立方阶
O(2^N)指数阶

常用的时间复杂度所耗费的时间从小到大依次是:

O(1)<O(logN)<O(N)<O(N*logN)<O(N^2)<O(N^3)<O(2^N)<O(N!)<O(N^N)?


?以上就是我对时间复杂度的一个理解。如果有出错的地方希望各位伙伴能及时指出,我也会及时改正的。感谢各位的观看,谢谢!!!!!

文章来源:https://blog.csdn.net/weixin_73359101/article/details/135585648
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。