时间复杂度和空间复杂度

news2024/11/19 19:37:57

文章目录

  • 一、算法的复杂度
  • 二、时间复杂度
      • 1.时间复杂度概念
      • 2.大O的渐进表示法
      • 3.常见时间复杂度计算举例
          • 3.1 实例1 【 O(N)】
          • 3.2 实例2 【O(N+M)】
          • 3.3 实例3 【O(1)】
          • 3.4 实例4 【O(N)】
          • 3.5 实例5 【O(N^2)】
          • 3.6 实例6 【O(logN)】
          • 3.7 实例7 【O(N)】
          • 3.8 实例8【O(2^N)】
  • 三、空间复杂度
          • 实例1 【O(1)】
          • 实例2 【O(N)】
          • 实例3 【O(N)】
  • 四、复杂度的oj练习
      • 1.消失的数字
      • 2.旋转数组
          • 2.1 方法一 遍历(行不通,在本题干)
          • 2.2 方法二 反转法
          • 2.3方法三 开辟数组法
          • 2.4 方法四 环状替换法
  • 最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出

一、算法的复杂度

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

二、时间复杂度

1.时间复杂度概念

时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一
个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知
道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个
分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法
的时间复杂度。

下面我们看一下求程序所用时间的一个函数clock()
由图可以看出Debug和Release运行的结果还是不一样的,Release是经过优化的
在这里插入图片描述
下面我们看一段代码,来初步了解一下

// 请计算一下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);
}

下面是我们真实算出的结果,而在实际中只需要估读就够了遵循大O的渐进表示法
在这里插入图片描述

2.大O的渐进表示法

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

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

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N^2)
N = 10 F(N) = 100
N = 100 F(N) = 10000
N = 1000 F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
最坏情况:N次找到
平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

3.常见时间复杂度计算举例

3.1 实例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);
}

此题应用的是只保留最高阶项,最高阶项存在且不是1,则去除与这个项目相乘的常数。
实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)

3.2 实例2 【O(N+M)】

// 计算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);
}

实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)

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

用常数1取代运行时间中的所有加法常数
实例3基本操作执行了100次,通过推导大O阶方法,时间复杂度为 O(1)

3.4 实例4 【O(N)】
// 计算strchr的时间复杂度?
const char * strchr ( const char * str, int character );

实例4基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)

3.5 实例5 【O(N^2)】

// 计算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;
	}
}

实例5基本操作执行最好N次,最坏执行了(N*(N+1)/2次,通过推导大O阶方法+时间复杂度一般看最
坏,时间复杂度为 O(N^2)

3.6 实例6 【O(logN)】
// 计算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;
}

实例6基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(logN) ps:logN在算法分析中表示是底
数为2,对数为N。有些地方会写成lgN。(建议通过折纸查找的方式讲解logN是怎么计算出来的)
在这里插入图片描述

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

实例7通过计算分析发现基本操作递归了N次,时间复杂度为O(N)。
这题可以进行一下扩展

long long Fac(size_t N)
{
	if (0 == N)
		return 1;

	for (int i = 0; i < N; i++)
	{
		//……
	}
	return Fac(N - 1) * N;
}
3.8 实例8【O(2^N)】
// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}

因为下面有两个分支所以是以二倍的速度增长,所以有时候递归并不是很好
在这里插入图片描述

结果为【O(N^2)】

三、空间复杂度

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

实例1 【O(1)】
// 计算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渐进表示法,所以结果为【O(1)】

实例2 【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;
}

因为这里创建了一个动态数组,申请了N个变量所以空间复杂度【O(N)】

实例3 【O(N)】
// 计算阶乘递归Fac的空间复杂度?
long long Fac(size_t N)
{
	if (N == 0)
		return 1;
	return Fac(N - 1) * N;
}

实例3递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)

四、复杂度的oj练习

1.消失的数字

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
方法一代码:

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

方法二代码:

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

下面还有一种方法就是不符合题意,超出了时间复杂度
在这里插入图片描述

2.旋转数组

在这里插入图片描述

2.1 方法一 遍历(行不通,在本题干)

超出时间限制
在这里插入图片描述

void rotate(int* nums, int numsSize, int k) {
    for (int i = 0; i < k; i++)
    {
        int n = nums[numsSize - 1];
        for (int j = numsSize - 1; j > 0; j--)
        {
            //n=arr[j];
            nums[j] = nums[j - 1];
        }
        nums[0] = n;
    }
}
2.2 方法二 反转法

先将整体反转,之后在分为两部分反转

在这里插入图片描述

void reverse(int *nums,int left,int right)
{
    //int temp=0;
    while(left<right)
    {
        int temp=nums[left];
        nums[left]=nums[right];
        nums[right]=temp;
        left++;
        right--;
    }
}
void rotate(int* nums, int numsSize, int k){
    k %= numsSize;
    reverse(nums,0,numsSize-1);
    reverse(nums,0,k-1);
    reverse(nums,k,numsSize-1);
}
2.3方法三 开辟数组法

创建一个数组,将数字分为两个部分,放入数组
在这里插入图片描述

void rotate(int* nums, int numsSize, int k)
{
	k %= numsSize;
	int *arr = (int *)malloc(sizeof(int)*numsSize);
	int count = 0;
	for (int i=numsSize-k; i < numsSize; i++)
	{
		arr[count] = nums[i];//将需要颠倒的先拷贝过去
		count++;
	}
	for (int j = 0; j< numsSize - k; j++)
	{
		arr[count] = nums[j];//再将后面的拷贝过来
		count++;
	}
	for (int j = 0; j < numsSize; j++)

	{
		nums[j] = arr[j];//拷贝回原数组
	}
}
2.4 方法四 环状替换法

环状替换法,即从起点开始,每次以K为单位进行跳跃式的替换,每个元素都会进行一次替换,进行了N次替换,即完成了数组的旋转;
注意点:
1.当下标+K大于数组长度的时候,就需要减去长度回到”起点“处;
2.回到起点后,判断回来的位置是否和出发的位置重复,如果不重复,那么一直进行下去,直至完成N此跳跃,形成一个环圈;
如果重合了,每次回到起点后就将起点完后挪动一个位置,直至完成N次跳跃;
原文链接
在这里插入图片描述

在这里插入图片描述

void rotate(int* nums, int numsSize, int k){
    k %= numsSize;
    int begin=0;
    int temp=0;
    int count=0;
    int num=k;
    int previous = nums[0];
    while(count<numsSize)
    {
        if(num<numsSize)
        {
            temp=nums[num];
            nums[num]=previous;
            num+=k;
            previous=temp;
            count++;
        }
        else
        {
            temp=nums[num-numsSize];
            nums[num-numsSize]=previous;
            previous=temp;
            count++;
            if (num - numsSize == begin)//回到起点的位置和出发位置相同
			{
				num = num - numsSize + 1;
				begin=num;//继续标记出发位置
				previous = nums[num];//重新储存起点
				num += k;
			}
			else
			{
				num = num - numsSize + k;
			}
        }
    } 
}

最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新的3D地图制图技术改变了全球定位的游戏规则

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 在人类技术发展的历史长河…

市场裁猿~ 行业内卷~ Android 开发突破重围?

往日光鲜亮丽的互联网企业在今年彻底进入了寒冬&#xff0c;往年的高不可攀&#xff0c;低成本运营&#xff0c;在今年都不再是护身符。更是有不少互联网大厂开启了裁员模式&#xff0c;其中就有百度、阿里巴巴、快手、汽车之家等知名企业。 从今年三月起直到现在互联网各厂裁员…

【微信小程序系列:二】小程序常用功能:文字可复制、跳转地图、扫一扫、拨打电话、调整屏幕亮度、监听截屏...

一.先言&#xff1a; (&#xff5e;&#xffe3;▽&#xffe3;)&#xff5e;&#xff0c;hello&#xff0c;微信小程序系列第二篇&#xff0c;介绍下小程序里的常用功能api&#xff0c;可以快速copy使用~ 二.文字可复制&#xff1a; 小程序页面里的文字默认是没有长按复制功…

自动泊车停车位检测算法

转自&#xff1a;https://zhuanlan.zhihu.com/p/522630354 图1&#xff1a;泊车示意图一、背景介绍 自动泊车大体可分为4个等级&#xff1a; 第1级&#xff0c;APA 自动泊车&#xff1a;驾驶员在车内&#xff0c;随时准备制动&#xff0c;分为雷达感知和雷达视觉感知两种方式。…

手机批发业务-商品备选区

样式 代码 <!doctype html> <html lang"en"><head><meta charset"UTF-8"><meta name"author" content"huyiwei"><meta name"generator" content"HBuilder X"><title>…

大数据项目之电商数仓、用户行为日志、服务器和JDK准备、模拟数据

文章目录3. 用户行为日志3.4 服务器和JDK准备3.4.1 服务器准备3.4.2 编写集群分发脚本xsync3.4.3 SSH无密登录配置3.4.4 JDK准备3.4.5 环境变量配置说明3.5 模拟数据3.5.1 使用说明3.5.1.1 将application.yml、gmall2020-mock-log-2021-10-10.jar、path.json、logback.xml上传到…

【C语言】经典编程题

文章目录1. Fibonacci数列 ⭐️2. 替换空格 &#x1f31f;3. 找单身狗 &#x1f4ab;4. 模拟实现 atoi ✨1. Fibonacci数列 ⭐️ 做题链接&#xff1a;Fibonacci数列 Fibonacci数列是这样定义的&#xff1a; F[0] 0 F[1] 1 for each i ≥ 2: F[i] F[i-1] F[i-2] 因此&…

[数据结构初阶]算法的时空复杂度

目录 算法效率 如何衡量一个算法的好坏 算法的复杂度 时间复杂度 时间复杂度的概念 大O的渐进表示法 常见时间复杂度计算举例 例一&#xff1a; 例二&#xff1a; 例三&#xff1a; 例四&#xff1a…

C规范编辑笔记(三)

继上篇我们的C规范编辑笔记(二)后&#xff0c;我们今天开始分享第三篇笔记&#xff0c;话不多说&#xff0c;我们开始~ 1、一个 tab 键盘等于四个空格键。我们在编程的时候最好使用空格键而不是使用 tab 键&#xff0c;当两个编译器的 tab 键设置的大小不一样的时候&#xff0…

无人机基础知识:多旋翼无人机自动控制原理与算法

无人机基础知识&#xff1a;多旋翼无人机自动控制原理与算法前言无人机自动控制器PID控制算法前言 无人机&#xff08;Unmanned Aerial Vehicle&#xff09;&#xff0c;指的是一种由动力驱动的、无线遥控或自主飞行、机上无人驾驶并可重复使用的飞行器&#xff0c;飞机通过机…

Linux:进程描述、进程创建(fork)、进程状态(僵尸进程、孤儿进程)

目录操作系统进程的基本概念进程创建&#xff08;fork)进程状态僵尸进程孤儿进程操作系统 操作系统是一个管理计算机软硬件资源的软件&#xff0c;它包括内核&#xff08;进程管理&#xff0c;内存管理&#xff0c;文件管理&#xff0c;驱动管理&#xff09;和其他程序&#x…

神经网络输出表达式,神经网络数据格式

1、BP神经网络输入输出格式问题 在你的代码基础上说了。 clc;clear; close all; pload(originaldata.txt);%你问题最后说的数据文件名跟这个不同。 p1p; t[1];% 这个输出&#xff08;Targets&#xff09;应该和输入数据对应&#xff0c;输入数据有10个&#xff0c;输出应该也是…

如何从0到1搭建一个个人网站

前言 如何从0到1搭建一个可以外网访问的项目&#xff1f; 我就用自己的服务器给大家举例&#xff0c;怎么从0到1搭建一个学生和新手可以用来面试的项目&#xff0c;老手也可以回忆一下自己逝去的青春。 服务器在激活的时候会让你选系统&#xff0c;这个时候敖丙我个人建议&a…

软考 | 计算机系统

CPU 计算机系统硬件组成 计算机系统是由软硬件共同组成&#xff0c;协同运行程序。计算机的基本硬件由 运算器、控制器、存储器、输入设备、输出设备 5 大部件组成。其中&#xff0c;运算器和控制器等部件集成到一起的部分称为中央处理器&#xff08;CPU&#xff09;。CPU 是…

离线数仓 (八) --------- 数仓分层

目录一、为什么要分层二、数据集市和数据仓库区别三、数仓命名规范1. 表命名2. 脚本命名3. 表字段类型一、为什么要分层 分层说明&#xff1a; ODS 层&#xff1a;原始数据层&#xff0c;存放原始数据&#xff0c;直接加载原始日志、数据&#xff0c;数据保持原貌不做处理。 …

【C++】命名空间输入输出缺省参数函数重载

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;什么是C&…

链路状态路由协议 OSPF (一)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.OSPF的基本概念 1.什么是OSPF 2.OSPF路由协议概述 (1)内部…

网络安全知识之什么是网络安全?

网络安全的概念&#xff0c;有很多不同的定义。在一些场合中&#xff0c;我们最多的考虑的还是网络的技术安全&#xff0c;但是技术延伸出来的安全又会涉及到社会安全、文化安全、国家安全等等。 网络安全的概念&#xff0c;有很多不同的定义。在一些场合中&#xff0c;我们最多…

升级全新网络方案,给你低成本、高性能的裸金属体验

摘要&#xff1a;对政企、金融客户来说&#xff0c;裸金属服务器相比于云服务器&#xff0c;更能满足他们对性能和稳定性的要求。但裸金属服务器的网络实现&#xff0c;却存在成本高、灵活性差、成熟度不足等问题。为解决这些问题&#xff0c;华为云Stack有一套增强版的网络方案…

Unity Rigidbody实现爬坡功能

文章目录&#x1f388; 简介&#x1f388; 实现原理&#x1f388; 实现代码&#x1f388; 简介 人物移动功能的实现或许用到Character Controller组件或许用到Rigidbody刚体组件&#xff0c;前者可以直接实现在某些高地不平的地面上移动&#xff0c;而后者想要在具有一定角度的…