• 时间复杂度


    目录

    ​编辑

    复杂度的概念:

    时间复杂度的概念:

    大O的渐进表示法:

    推到大O渐进表示法:

    大O渐进表示法的关系和大小:

    其他的代码的时间复杂度: 

     在一个长度为N数组中搜索一个数据X:

    交换位置(冒泡排序): 

    O(log N):

     二分查找:

    递归: 

    计算斐波那契递归的时间复杂度:



     

    复杂度的概念:

    算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。

    时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。

    时间复杂度的概念:

    在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。

    一个算法所花费的时间与其中语句的执行次数成正比例算法中的基本操作的执行次数,为算法 的时间复杂度。

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

    而这种数学表达式通常是使用大O的渐进表示法。 

    大O的渐进表示法:

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

    推到大O渐进表示法:

    1. //请计算一下Func1++count语句总共执行了多少次?
    2. void Func1(int N)
    3. {
    4. int count = 0;
    5. for (int i = 0; i < N; ++i)
    6. {
    7. for (int j = 0; j < N; ++j)
    8. {
    9. ++count;
    10. }
    11. }
    12. for (int k = 0; k < 2 * N; ++k)
    13. {
    14. ++count;
    15. }
    16. int M = 10;
    17. while (M--)
    18. {
    19. ++count;
    20. }
    21. printf("%d\n", count);
    22. }

    Func1 执行的基本操作次数 :

    • N = 10 F(N) = 130
    • N = 100 F(N) = 10210
    • N = 1000 F(N) = 1002010 

    分析,由于此处的基本操作是三个循环:且因为时间复杂度的本质是算法中的基本操作的执行次数。

    所以,本代码的时间复杂度是  2*N+N*N+10

    • 但是按照大O渐进表示法的规则,取数学表达式中的最大一项,且若最大一项是常数,则用1表达,若最大一项有系数,则删除系数。

    所以以上代码用大O渐进表示法表示为  O(N^2) 

    1. //嵌套循环
    2. for (int i = 0; i < N; ++i)
    3. {
    4. for (int j = 0; j < N; ++j)
    5. {
    6. ++count;
    7. }
    8. }
    9. //普通循环1
    10. for (int k = 0; k < 2 * N; ++k)
    11. {
    12. ++count;
    13. }
    14. //普通循环2
    15. int M = 10;
    16. while (M--)
    17. {
    18. ++count;
    19. }

    大O渐进表示法的关系和大小:

    一般来说,大O渐进表示法的表达式是:O(N)、O(N^2)、O(1)

    • 其中的N表达的是某一算法中的基本操作的运行次数。
    • 1也表示一种次数,但不是表示运行一次,而是使用数学关系式计算出,时间复杂度最后是一个常数,又因为计算机的cpu功能,处理和运行一个时间复杂度是常数的代码,它的运行和处理速度是非常快的,哪怕这个常数是一亿。 
    • 需要注意,O(N)、O(N^2)、O(1)中,运行性能和速度的大小关系是:O(1) > O(N) > O(N^2)

    注意:这个常数必须要在类型(如int、long long、long)的范围内,不然会溢出。 

    其他的代码的时间复杂度: 

     在一个长度为N数组中搜索一个数据X:

    1. const char * strchr ( const char * str, int character )
    2. {
    3. while(*str)
    4. {
    5. if(*str == character)
    6. return str;
    7. }
    8. ++str;
    9. }

    根据时间复杂度的本质,"算法中的基本操作的执行次数,就是时间复杂度",而本代码中的多次运行的操作是移动,移动指针查找是数组中的元素是否是我们需要的

    所以从指针开始遍历数组,直到找到需要的元素,这一段查找确认的次数就是时间复杂度。

    而找到元素,这个是难以确认的,我们不知道元素是否在哪,所以这里有一种不确定性。

    而对于这种不确定性,我们选择了一种最坏的结果,那就是没有找到元素,而没有找到元素就是要先将数组用指针遍历一遍,所以,这个代码的时间复杂度就转化为了:要把指针移动几次,才能将数组遍历一遍。

    而数组的长度是N,那么指针则需要遍历N次,才能将数组遍历完一遍。 

    交换位置(冒泡排序): 

    1. void BubbleSort(int* a, int n)
    2. {
    3. assert(a);
    4. for (size_t end = n; end > 0; --end)//每次交换所需要的趟数
    5. {
    6. int exchange = 0;
    7. for (size_t i = 1; i < end; ++i)
    8. {
    9. if (a[i-1] > a[i])
    10. {
    11. Swap(&a[i-1], &a[i]);
    12. exchange = 1;
    13. }
    14. }
    15. if (exchange == 0)
    16. break;
    17. }
    18. }

     根据时间复杂度的本质,"算法中的基本操作的执行次数,就是时间复杂度",而本代码中的多次运行的操作是每次交换所需要的趟数!

    注意该代码中的交换是使用了调用函数,所以不需要算上交换的次数。

    • 从我们的初步运算,得知交换的趟数是N*(N-1+1-1)/2 ,最后算出(N^2)/2
    • 按照大O渐进法,得到该代码的时间复杂度是O(N^2) 

    O(log N):

    1. void fun(int n) {
    2. int i=l;
    3. while(i<=n)
    4. i=i*2;
    5. }
    • 此函数有一个循环,但是循环没有被执行n次,因为 i每次都是 2 倍进行递增,所以循环只会被执行log2(n)次。 

     二分查找:

    1. int BinarySearch(int* a, int n, int x)
    2. {
    3. assert(a);
    4. int begin = 0;
    5. int end = n - 1;
    6. // [begin, end]:begin和end是左闭右闭区间,因此有=
    7. while (begin <= end)
    8. {
    9. int mid = begin + ((end - begin) >> 1);
    10. if (a[mid] < x)
    11. begin = mid + 1;
    12. else if (a[mid] > x)
    13. end = mid - 1;
    14. else
    15. return mid;
    16. }
    17. return -1;
    18. }

     

    • 如图所示,我们再查找指定数字的时候并不是将整个数组(大小为N)遍历了一遍,而是直接再数组中间(N/2)的位置进行对比。
    • 而最坏的情况就是,N/2 /2 /2 /2 ……直到中间只有一个数字位置
    • 而将x作为查找到数字的次数,进行轮番的乘2,最后得出的结构就如上图所示 log 2 N 次
    • 所以大O渐进法就是O(log 2 N)2可以省略,所以便是 O(log N) 

    递归: 

    1. int f ( unsigned int n ) {
    2. if (n == 0 || n==1)
    3. return 1;
    4. else
    5. return n * f(n-1);
    6. }
    •  关于递归,因为我们时间复杂度的本质是某一个基本操作运行的次数累加,所以再递归中,我们是运行了N次的n*f(n-1)——n变成1位置
    • 所以,上诉代码的时间复杂度是O(N)

    计算斐波那契递归的时间复杂度:

    1. long long Fib(size_t N)
    2. {
    3. if(N < 3)
    4. return 1;
    5. return Fib(N-1) + Fib(N-2);
    6. }
    • 再斐波那契额数列中,一个数是由前两个数字组成的

     

    如上图所示,每一次的调用,都会产生新的分支,而新的分支又会再产生另外的新分支

    而时间复杂度取决于某一个操作的运行次数。

    再最开始的调用中,会分为两个部分,而每个部分又会产生全新的两个部分,直到结束。

    所以这里需要要运用的是等差数列的和公式,进行运算。

    以此得到的最后结果是O(N) 

     


  • 相关阅读:
    数字IC设计笔试题汇总(四):一些基础知识点
    狂神redis笔记10
    2023校招C++开发oppo笔试
    Linux是啥?我们来聊聊?
    天馈 频谱 场强 +定位,手持式信号综合分析仪---AMT950C
    数据在内存如何分布的?
    double或Double取整或保留n位小数(后续如果有需要再整理其他小数类型)
    罕见病 对称性脂肪瘤(MSL) 马德龙病
    Reactive UI -- 反应式编程UI框架入门学习(一)
    区块链(1):区块链简介
  • 原文地址:https://blog.csdn.net/2301_76445610/article/details/133961910