目录

一、概念

1.1、算法效率

1.2、时间复杂度

1.3、空间复杂度

二、计算

2.1、大O的渐进表示法

2.2、时间复杂度计算

例题:

2.3、空间复杂度计算

例题

三、有复杂度要求的习题


一、概念

1.1、算法效率

如何衡量一个算法的好坏?比如对于以下斐波那契数列:

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

斐波那契数列用递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢?在学完时间复杂度会为您揭晓。

算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度

1.2、时间复杂度

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

1.3、空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。

二、计算

2.1、大O的渐进表示法

先看一串代码:

// 请计算一下Func1基本操作执行了多少次?
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*N+2*N+10

例如F(10)=130、F(100)=10210、F(1000)=1002010

按理来说此题的时间复杂度就是上述的公式,其实不然。时间复杂度是一个估算,是去看表达式中影响最大的那一项。此题随着N的增大,这个表达式中N^2对结果的影响是最大的

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次
数,那么这里我们使用大O的渐进表示法。,因而上题的时间复杂度为O(N^2)

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

  • 推导大O阶方法:
  1. 用常数1取代运行时间中的所有加法常数。
  2. 在修改后的运行次数函数中,只保留最高阶项。
  3. 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。另外有些算法的时间复杂度存在最好、平均和最坏情况:

  1. 最坏情况:任意输入规模的最大运行次数(上界)
  2. 平均情况:任意输入规模的期望运行次数
  3. 最好情况:任意输入规模的最小运行次数(下界)
  • 例如:在一个长度为N数组中搜索一个数据x
  1. 最好情况:1次找到
  2. 最坏情况:N次找到
  3. 平均情况:N/2次找到

在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

注意:递归算法时间复杂度计算

  1. 每次函数调用是O(1),那么就看他的递归次数
  2. 每次函数调用不是O(1),那么就看他的递归调用中次数的累加

2.2、时间复杂度计算

例题:

  • 例一:
// 计算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)

解析:此题中最准确的次数为2*N+10,而其中影响最大的是N,可能有人觉着是2*N,但随着N的不断增大,2对结果的影响不是很大,况且要符合上述第三条规则:如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。所以把2去除掉,因而时间复杂度为O(N)

  • 例二:
// 计算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)

解析:因为M和N都是未知数,所以N和M都要带着,但是如果题目明确M远大于N,那么时间复杂度就是O(M),如果M和N差不多大,那么时间复杂度就是O(M)或O(N)

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

解析:这里最准确的次数是100,但是要符合大O的渐进表示法的规则,用常数1取代运行时间中的所有加法常数。所以时间复杂度就是O(1)

  • 例四:
// 计算strchr的时间复杂度?
const char* strchr(const char* str, char character)
{while (*str != '\0'){if (*str == character)return str;++str;}return NULL;
}
  • 答案:O(N)

解析:此题就要分情况了,这里假设字符串为abcdefghijklmn,如果目标字符找的是g,则需要执行N/2次,如果找到是a,则需要执行1次,如果找n,则N次,所以要分情况,这里就出现了有些算法的时间复杂度存在最好O(1)、平均O(N/2)和最坏O(N)情况,但是在实际中一般情况关注的是算法的最坏运行情况,所以此题时间复杂度为O(N)

  • 例五:
// 计算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)

解析:此段代码考到的是冒泡排序。第一趟的冒泡排序走了N次,第二趟走了N-1次,第三趟N-2,……最后就是1,次规律正合等差数列,求和即为(N+1)*N/2,当然这个是最准确的,这里还要找对结果影响最大的那一项,即N^2,所以时间复杂度是O(N^2)

  • 例六:
// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n;while (begin < end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x)begin = mid + 1;else if (a[mid] > x)end = mid;elsereturn mid;}return -1;
}
  • 答案:O(logN)

解析:此题很明显考到的是二分查找。假设数组长度为N,且找了X次,则1*2*2*2*2*……*2=N,即为2^X=N,则X等于log以2为底N的对数,而算法的复杂度计算,喜欢省略简写成logN,因为很多地方不好写底数,所以此题时间复杂度为O(logN)

  • 例七:
// 计算阶乘递归Factorial的时间复杂度?
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N-1)*N;
}
  • 答案:O(N)

解析:如果N为10

  • 例八:
long long Fib(int N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}

这串代码是上文最开始呈现的代码,代码风格十分简单,短短几行便可完成斐波那契数列的计算,可看似这么简洁的代码真的“好”吗?先来计算一下时间复杂度:

  • 答案:O(2^N)

解析:

有上图可以得知,第一行执行1次,第二行执行2^1次,第三行执行2^2次,以此类推,是个等比数列,累计算下来再根据大O阶表示法的规则得知,此斐波那契数列的时间复杂度为O(2^N)。

但是,根据2^N这个时间复杂度是个非常大的数字,当n=10时,在VS环境下很快容易得到答案,但是当n稍微再大一点比如说是50,就要等上很长一段时间才能将结果算出来,由此可见,简洁的代码不一定是最优的代码。

常见时间复杂度:O(N^2)、O(N)、O(logN)、O(1)

  • 复杂度对比:

2.3、空间复杂度计算

  • 空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。
  • 空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
  • 注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

例题

  • 例一:
// 计算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(1)

解析:这里其实总共开辟了三个空间,分别为end、exchange、i,既然是常数个变量,那么空间复杂度就是O(1),空间复杂度算的是申请的额外空间,所以跟上面的int*a和int n没有关系。可能有人觉着这是个for循环,exchange应该开辟n次,其实每次循环进来,exchange都会重新开辟,结束一次循环exchange销毁,以此类推,exchange始终是同一个空间。

而什么时候会出现O(n)呢?

  • 1、malloc一个数组
int *a = (int*)malloc(sizeof(int)*numsSize); //O(N)

此情况的前提是numsSize必须是个未知的数字,如果是具体数字,那么空间复杂度依旧是O(1)

  • 2、变长数组
int a[numsSize]; //numsSize未知,O(N)
  • 例二:
// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{if (n == 0)return NULL;long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n; ++i){fibArray[i] = fibArray[i - 1] + fibArray[i - 2];}return fibArray;
}
  • 答案:O(N+1)

解析:这里看到了malloc开辟了n+1个大小为long long类型的数组,看到这就不需要再过多计较后续创建了几个变量,因为空间复杂度是估算,所以直接就是O(N)

  • 例三:
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{if (N == 0)return 1;return Fac(N - 1) * N;
}
  • 答案:O(1)

解析: 这里递归函数是要建立栈帧的,而建立栈帧的个数为N个,每个栈帧的变量都是常数个,N个即空间复杂度为O(N)

  • 例四:
// 计算斐波那契递归Fib的空间复杂度?
long long Fib(size_t N)
{if (N < 3)return 1;return Fib(N - 1) + Fib(N - 2);
}
  • 答案:O(N)

解析:时间一去不复返,是累积的,空间回收以后是可以重复利用的。当递归到Fib(3)的时候,此时调用Fib(2)和Fib(1),调到Fib(2)就可以返回了,此时Fib(2)的栈帧就销毁了,此时再调用的Fib(1)和Fib(2)用的就是同一块空间,同理Fib(N-1)总共建立了N-1个栈帧,同理再调用Fib(N-2)和刚才Fib(N-1)使用的是同一块空间,充分说明了时间一去不复返,是累积的,空间回收以后是可以重复利用的。

三、有复杂度要求的习题

  • 题一:(消失的数字)

链接:https://leetcode-cn.com/problems/missing-number-lcci/

此题就明确了一个要求:想办法在O(n)的时间内完成,本题将提供两种有效且可行的方法,正文开始:

法一:相加 - 相加

  • 思想:

此题是在一串连续的整数中缺了一个数,那我们就把理应有的整数个数依次相加再减去原数组中缺一个数字的所有元素和即为我们想要的数字。

代码如下:

int missingNumber(int* nums, int numsSize){int sum1=0;int sum2=0;for(int i=0;i<numsSize+1;i++){sum1+=i;}for(int i=0;i<numsSize;i++){sum2+=nums[i];}return sum1-sum2;
}

法二:异或

  • 思想:

正如示例2,这里假设一共有10个数字,那么这里nums数组就是 [ 0 - 9 ],不过其中缺了一个数字,我们已经深知异或的运算规则(相同为0,相异为1)以及两个重要结论:1、两个相同的数字异或等于0。2、0与任何数字异或等于该任意数字。因此,我们完全可以先把原数组的所有元素异或起来,再把理论上0-n依次递增的所有元素都异或起来,然后两块再次异或得到的就是缺少的数字。

  • 画图展示:

代码如下:

int missingNumber(int* nums, int numsSize){int n=0;for(int i=0;i<numsSize;i++){n^=nums[i];}for(int i=0;i<numsSize+1;i++){n^=i;}return n;
}

注意:第二个for循环中循环的次数要建立在numsSize的基础上再加1,因为是缺少了一个数字,所以理论上数组的长度是在原基础上加1的。

  • 题二:(旋转数组)

链接:https://leetcode-cn.com/problems/rotate-array/

此题的进阶思想中就明确了使用空间复杂度为O(1)的算法来解决此问题,正文开始

法一:右旋K次,一次移动一个

  • 思想:

首先,定义一个变量tmp把数组的最后一个元素保存起来。其次,把前N-1个值往后挪。最后,把tmp的值放到第一个位置。如图所示:

此法时间复杂度为:O(N*K),空间复杂度O(1),此法的空间复杂度满足题意了,但有个风险,就是当K%N=N-1时时间复杂度过大,为O(N^2),所以再看看有无更好方法:

法二: 额外开数组

  • 思想:

额外开辟一个新数组,把后K个元素放到新数组前面,再把原数组N-K个元素拷贝到新数组后面。但是此法的时间复杂度是O(N),空间复杂度也是O(N),不符合题意,再换:

法三:三趟逆置

  • 思想:

第一趟对它的前N-K个元素逆置,第二趟对它的后K个元素逆置,最后整体逆置。如图所示:

此法非常巧妙,时间复杂度O(N),空间复杂度O(N),符合题意

代码如下:

void reverse(int*nums,int left,int right)
{while(left<right){int tmp=nums[left];nums[left]=nums[right];nums[right]=tmp;left++;right--;}
}
void rotate(int* nums, int numsSize, int k){k%=numsSize;reverse(nums,0,numsSize-k-1);reverse(nums,numsSize-k,numsSize-1);reverse(nums,0,numsSize-1);
}

 注意:这里当k=7时,相当于全部逆置完了一遍,也就是又回到了原来的样子,是有规律可循的,所以真正逆置的次数为k%=numsSize;

<数据结构>时间复杂度及空间复杂度相关推荐

  1. 浅淡数据结构时间复杂度和空间复杂度

    文章目录 前言 1.时间复杂度和空间复杂度的相关介绍 1.为什么要引入时间复杂度和空间复杂度的概念 2.什么是时间复杂度和空间复杂度 2.具体示例分析 1.大O法只保留高阶项 2.一般情况关注的是算法 ...

  2. 数据结构——时间复杂度和空间复杂度

    目录 1.什么是时间复杂度和空间复杂度 1.1时间复杂度 1.2空间复杂度 2.如何计算时间复杂度和空间复杂度 2.1使用大O阶方法 2.2计算一些常用算法的时间复杂度 2.3计算一些常用算法的空间复 ...

  3. java数据结构-----时间复杂度、空间复杂度

    文章目录 1.前言 2.算法与时间复杂度.空间复杂度的关系 3.时间复杂度 4.常见时间复杂度举例 5.空间复杂度 6.常见的空间复杂度举例 1.前言 当我们设计一个算法的时候要考虑他的效率,例如,使 ...

  4. 数据结构 | 时间复杂度与空间复杂度

    -

  5. 数据结构 | 时间与空间复杂度就看这篇了【生活经历 + 实例讲解】

    大家好,本文我们将进入数据结构时间复杂度与空间复杂度,希望能带你一破复杂度求解的难关

  6. 数据结构(02)— 时间复杂度与空间复杂度转换

    1. 时间复杂度转化为空间复杂度 常用的降低时间复杂度的方法有递归.二分法.排序算法.动态规划等,降低空间复杂度的核心思路就是,能用低复杂度的数据结构能解决问题,就千万不要用高复杂度的数据结构. ​ ...

  7. Python数据结构与算法笔记(一):时间复杂度与空间复杂度

    学习内容来自清华计算机博士带你学习Python算法+数据结构.目前内容截止到树结构,后续的高级算法以后会补上. 算法概念简述 数据结构:数据存储 时间复杂度 每个电脑配置不一样.同一个程序执行的时间不 ...

  8. 【数据结构入门】算法的时间复杂度和空间复杂度详解

    文章目录 (1)算法效率 (2)时间复杂度的计算 1)什么是时间复杂度 2)大O渐进表示法(估算) 3)时间复杂度计算实例 4)总结 5)一些思考 (3)空间复杂度的计算 (4)常见复杂度对比 本篇前 ...

  9. Java数据结构(1.1):数据结构入门+线性表、算法时间复杂度与空间复杂度、线性表、顺序表、单双链表实现、Java线性表、栈、队列、Java栈与队列。

    数据结构与算法入门 问题1:为什么要学习数据结构          如果说学习语文的最终目的是写小说的话,那么能不能在识字.组词.造句后就直接写小说了,肯定是不行的, 中间还有一个必经的阶段:就是写作 ...

  10. 数据结构与算法之时间复杂度与空间复杂度

    ⭐️前面的话⭐️ 本篇文章带大家认识数据结构与算法基础,时间复杂度与空间复杂度.算法效率分析分为两种:第一种是时间效率,第二种是空间效率.时间效率被称为时间复杂度,而空间效率被称作空间复杂度. 时间复 ...

最新文章

  1. maven mybatis mysql_Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问...
  2. MySQL主主(双主)数据同步
  3. JS原型与原型链终极详解
  4. 使用Android内部的DownloadProvider下载文件,并获取cache权限 .
  5. 将您的基于 Accelerator 的 SAP Commerce Cloud Storefront 迁移到 Spartacus Storefront
  6. SAP Spartacus和product相关的标准normalizer
  7. 对于Eclipse的正确用法
  8. python存储和读取数据时出现错误_python读取json文件存sql及codecs读取大文件问题...
  9. redistemplate hash 过期时间_完美!谈谈Java中时间控制的几种解决方案
  10. 设置MYSQL数据库编码为UTF-8:
  11. HTML5 3D翻书效果(双面效应)
  12. linux I2C驱动实验
  13. 华为U8500刷了2.2后自定义铃声,短信通知音,闹铃音的方法
  14. Seating Arrangement CSU - 1997 (思维题,是否存在n个数的序列,使得相邻啷个数的差绝对值大于d)
  15. java web课程设计之图书管理系统
  16. 大数据处理实践探索 ---- 笔试面试题:Spark基本调优
  17. 【Python】均值回归策略回测(日内高频数据)
  18. C++ strIcmp 字符串大小比较
  19. 基于车道线的直线辅助横向定位
  20. 基于python的人脸识别检测开题报告_人脸识别系统的研究与实现(开题报告+论文+源程序)...

热门文章

  1. 积木盒子如何确保投资者安全
  2. 数据分析师做成了提数工程师,该如何破局?
  3. Exadata中Infiniband交换机升级
  4. k8s系列(四)——资源对象
  5. pyecharts 大小_pyecharts 常用API 图形初始化
  6. 2022年7月2日(星期六):骑行海囗
  7. GLFore声学成像仪对家电行业的应用
  8. WPF、C# iconfont图标字体只显示框框
  9. 硬件基础之STM32最小系统
  10. 泄漏用户信息,华为手机售后致歉;乐视网前三季度预亏16亿 ;小米将在印度超市卖手机丨价值早报