当前位置:   article > 正文

复杂度——时间复杂度_时间复杂度题目

时间复杂度题目

目录

前言

        算法的复杂度

        时间复杂度

        三、时间复杂度习题

                  例1、2、3、4:普通循环的时间复杂度

例5:二分查找法的时间复杂度

例6:冒泡排序的时间复杂度

例7:普通递归的时间复杂度

例8:求斐波那契数的时间复杂度

附加题:逆置数组


前言

        C程序在实现某种特定的功能的时候,会有多种代码去实现,为了提高程序的效率,需要考虑代码执行的时间和代码存储需要的空间。而在当今存储设备的进步,人们不是很在意存储代码所花空间的大小,会更在意代码运行的时间。

算法的复杂度

        我们熟知的斐波那契数列,用递归的方式去实现的时候,代码十分简洁,但是这简洁的代码能说明它的效率高吗?

  1. long long Fib(int N)
  2. {
  3. if(N < 3)
  4. return 1;
  5. return Fib(N-1) + Fib(N-2);
  6. }

        算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般 是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度。时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算 机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计 算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。

时间复杂度

        定义:在计算机科学中,算法的时间复杂度是一个函数 ,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有当程序执行起来,从它的执行快慢的情况下主观的判断。如果我们将每个代码都上机测试,那样将会很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法 的时间复杂度。

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

O 的渐进表示法
1.若运行次数为常数,无论大小,时间复杂度均记为O(1)
2.若计算的运行次数为N的表达式,则只保留最高阶项,并将系数视为1
        上述方法可以去掉对结果影响不大的项,简单明了的表示出了执行次数,在某些情况下算法的时间复杂度存在最好、平均、最坏三种。
最坏情况
任意输入规模的最大运行次数 ( 上界 )
平均情况
任意输入规模的期望运行次数
最好情况
任意输入规模的最小运行次数 ( 下界 )

时间复杂度习题

例1

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

        经过计算count一共计算F(N)=  N^2+2*N+10次

        N=10            F(N)=130

        N=100          F(N)=10210

        N=1000        F(N)=1002010

         上述中随着N的值不断增大,除了最高阶项,其余项对计算次数了影响很小,所以可以忽略不计。那么实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法可知时间复杂度为O(N^2)。

例2

  1. // 计算Func2的时间复杂度?
  2. void Func2(int N)
  3. {
  4. int count = 0;
  5. for (int k = 0; k < 2 * N; ++k)
  6. {
  7. ++count;
  8. }
  9. int M = 10;
  10. while (M--)
  11. {
  12. ++count;
  13. }
  14. printf("%d\n", count);
  15. }

        经过计算一共计算F(N)=  2*N+10次,则时间复杂度为O(N)

例3

  1. // 计算Func3的时间复杂度?
  2. void Func3(int N, int M)
  3. {
  4. int count = 0;
  5. for (int k = 0; k < M; ++k)
  6. {
  7. ++count;
  8. }
  9. for (int k = 0; k < N; ++k)
  10. {
  11. ++count;
  12. }
  13. printf("%d\n", count);
  14. }

        经过计算一共计算F(N)=  M+N次,则时间复杂度为O(M+N),若M>>N,则时间复杂度为O(M),反之则为O(N)。

例4

  1. void Func4(int N)
  2. {
  3. int count = 0;
  4. for (int k = 0; k < 100; ++k)
  5. {
  6. ++count;
  7. }
  8. printf("%d\n", count);
  9. }

        程序总共计算了100次,次数为常数,所以时间复杂度为O(1)

例5

  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. }

        二分查找法,最好的情况是第一次就找到了,或者几次就找到了,这样时间复杂度为O(1)

最坏的情况是最终找不到需要找到的值。

例6

        冒泡排序

  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-1)+(N-2)+(N-3)+.....+2+1=(N^N-N)/2,故时间复杂度为O(N^N)若数据本来就是有序的,则时间复杂度为O(N)。

 例7

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

        该递归调用时,N的值变化情况有:N、N-1、N-2、N-3......3、2、1,总共有N次变化情况,所以该递归的时间复杂度为O(N)。

例8

  1. // 计算斐波那契递归Fib的时间复杂度?
  2. long long Fib(size_t N)
  3. {
  4. if(N < 3)
  5. return 1;
  6. return Fib(N-1) + Fib(N-2);
  7. }

         递归数为:2^0+2^1+2^2+......+2^(N-2)+2^(N-1)=-2^0+2^N=2^N+1

        则斐波那契数的递归的时间复杂度为O(2^N)

附加题:轮转数组——只考虑时间复杂度

方法一:

  1. int main()
  2. {
  3. int arr[] = { 1,2,3,4,5,6,7 };
  4. int sz = sizeof(arr) / sizeof(arr[0]);
  5. int k;
  6. scanf("%d", &k);
  7. while (k--)
  8. {
  9. int n = arr[sz-1];
  10. for (int i = sz-1; i > 0; --i)
  11. {
  12. arr[i] = arr[i - 1];
  13. }
  14. arr[0] = n;
  15. }
  16. for (int j = 0; j < sz; j++)
  17. printf("%d ", arr[j]);
  18. return 0;
  19. }

        打印只是为了观察交换的结果,我们忽略打印的步骤,可以计算出。

  1. k=1,时间复杂度为O(N)
  2. 未知k等于多少,时间复杂度为O(kN)
  3. 没说明k的值,时间复杂度为O(N^N)

方法二:牺牲空间,提高算法效率

  1. void rotate(int* nums, int numsSize, int k)
  2. {
  3. int i = 0;
  4. int j = 0;
  5. if (k > numsSize)
  6. k = k % numsSize;
  7. if (k == numsSize)
  8. {
  9. for (i = 0; i < numsSize; ++i)
  10. {
  11. printf("%d", nums[i]);
  12. }
  13. }
  14. else
  15. {
  16. int left = numsSize - k;
  17. int right = k;
  18. int* arr1 = (int*)malloc(sizeof(int) * left);
  19. int* arr2 = (int*)malloc(sizeof(int) * right);
  20. if(arr1==NULL || arr2==NULL)
  21. {
  22. printf("malloc fail");
  23. return;
  24. }
  25. for (i = 0; i < numsSize; ++i)
  26. {
  27. if (i < left)
  28. arr1[i] = nums[i];
  29. else
  30. {
  31. arr2[j] = nums[i];
  32. j++;
  33. }
  34. }
  35. for (i = 0, j = 0; i < numsSize; ++i)
  36. {
  37. if (i < right)
  38. nums[i] = arr2[i];
  39. else
  40. {
  41. nums[i] = arr1[j];
  42. j++;
  43. }
  44. }
  45. free(arr1);
  46. free(arr2);
  47. }
  48. }

        该方法为:将需要倒叙的数组元素分别放在新创建两个数组中,最终再将两个新创建的数组按照需要的顺序放入原数组中,放入新数组执行了N次,再放入元素组执行力N次,一共执行了2N次,所以时间复杂度为O(2N)=O(N),该方法需要浪费一定的空间。

  1. 例如:k=3
  2. 原数组:int nums[ ]={1,2,3,4,5,6,7}
  3. 新数组:int arr1[ ]={1,2,3,4}
  4. 新数组:int arr2[ ]={5,6,7}
  5. 调整后的原数组:int nums[ ]={5,6,7,1,2,3,4}

方法三:最优解

  1. void reverse(int* p, int left, int right)
  2. {
  3. while (right > left)
  4. {
  5. int tmp = p[left];
  6. p[left] = p[right];
  7. p[right] = tmp;
  8. right--;
  9. left++;
  10. }
  11. }
  12. void rotate(int* p ,int sz, int k)
  13. {
  14. if (k > sz)
  15. k %= sz;
  16. reverse(p, 0, sz - k - 1);
  17. reverse(p, sz - k , sz - 1);
  18. reverse(p, 0, sz - 1);
  19. }

        该方法为先将左边部分逆序,再将右边部分逆序,然后再整体逆序。

  1. 例如:k=3
  2. 原数组:int nums[ ]={1,2,3,4,5,6,7}
  3. 前n-k个逆置:int nums[ ]={4,3,2,1,5,6,7}
  4. 后k个逆置:int nums[ ]={4,3,2,1,7,6,5}
  5. 整体逆置:int nums[ ]={5,6,7,1,2,3,4}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/842165
推荐阅读
相关标签
  

闽ICP备14008679号