【数据结构】------时间复杂度和空间复杂度的计算

一、数据结构

1.什么是数据结构

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素集合。简单来说,数据结构就是对数据进行管理(增删查改)的一系列操作。
数据结构和数据库的作用很相似,二者的区别在于管理的位置不同;当数据量很大时,数据一般都会存放在磁盘中,此时我们用数据库进行管理;当数据量较小时,我们用数据结构来管理。
数据库

2.什么是算法

算法 (Algorithm) 就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

数据结构和算法是相辅相成的,二者是我中有你、你中有我的关系:在一个数据结构中可能会用到算法来优化,一个算法中也可能用到数据结构来组织数据。

二、算法效率

算法复杂度:
1.时间效率:时间复杂度;
衡量的是一个算法的运行速度
2.空间效率:空间复杂度;
衡量的是一个算法所需要的额外空间

三、时间复杂度

1.时间复杂度的概念

定义:算发的时间复杂度是一个函数。它定量的描述了该算法的运行时间。
一个算法执行所耗费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。

2.时间复杂度的表示方法

我们计算时间复杂度不是计算算法运行的次数,而是用大O的渐进表示法来计算,其具体的计算方法如下:
1.用常数1取代运行时间中的所有加法常数;
2.在修改后的运行次数函数中,只保留最高阶项;
3.如果最高阶存在且不是1.则只去除这个项目相乘的常数。

3.算法复杂度的三种情况

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

例如:在一个长度为N数组中搜索一个数据x
最好情况:1次找到
平均情况:N/2次找到
最坏情况:N次找到
平均情况:N/2次找到

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

例1:

void Func(int N)
{
int count = 0;
for (int k = 0; k < 100; ++ k)
{
 ++count;
}
printf("%d\n", count);
}

上面程序具体执行的次数:100
用大O的渐进表示法得出时间复杂度:O(1) (用常数1取代运行时间中的所有加法常数。)
例2:

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

上面程序具体执行的次数:N * N + 2*N + 10

用大O的渐进表示法得出时间复杂度:O(N^2) (只保留最高阶项)
例3:

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

上面程序具体执行的次数:2*N + 10

用大O的渐进表示法得出时间复杂度:O(N) (如果最高阶项存在且不是1,则去除与这个项目相乘的常数;

5.复杂时间复杂度计算

(1)冒泡排序的时间复杂度

void  BubbleSort(int arr[], int n)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < n - 1; i++)
	{
		for (j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

具体执行次数:时间复杂度计算时以最坏情况为准,则假设数组开始是逆序的,那么第一次排序执行 n-1 次,第二次排序执行 n-2 次,第三次执行 n-3 次 … …,直到最后达成有序,所以冒泡排序的具体执行次数是一个等差数列,具体次数 = (首项+尾项)/2*项数 = (N^2-N)/2

用大O的渐进表示法得出时间复杂度:O(N^2)

(2)二分查找的时间复杂度

int BinarySearch(int arr[], int n, int x) //n元素个数  x:要查找的数
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] > x)
		{
			right = mid - 1;     //中间元素比x大就挪动right下标
		}
		else if (arr[mid] < x)
		{
			left = mid + 1;    //中间元素比x小就挪动left下标
		}
		else
			return mid;     //找到就返回该元素所在的下标
	}
	return 0;               //找不到就返回0
}

具体执行次数:和上面一样,这里考虑最坏情况,即数组中没有想找的元素,数组会从中间开始查找,每次排除掉一半的元素,直到把所有元素排除完,第一次排除后剩下 1/2 的元素,第二次排除后剩下 1/4 元素,第三次排除后剩下 1/8 元素 … …,设元素个数为N,查找次数为X,则 X * (½)^N = 1 -> (½)^N = X -> 具体次数:X = log2N

用大O的渐进表示法得出时间复杂度:O(logN)

注:因为在键盘上无法表示出log的底数,所有在时间复杂度中把log的底数2省略掉了,直接用logN表示log2N的时间复杂度。
(3)阶乘递归的时间复杂度

long long Factorial(int N)
{
	return N < 2 ? N : Factorial(N - 1) * N;
}

具体次数:这里 n 调用 n-1 , n-1 调用 n-2 …,直到 n = 1,所以一共执行了 n-1 次。
用大O的渐进表示法得出时间复杂度:O(N)
以五的阶乘示例:
在这里插入图片描述(4)斐波那契递归的时间复杂度

long long Fibonacci(size_t N)
{
	return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2);
}

在这里插入图片描述具体次数:以上图为例,我们可以看到在数值大于2的时候,每一层的调用次数是以2的指数形式增长的,是一个等比数列。

用大O的渐进表示法得出时间复杂度为:O(2^N)

5.不同时间复杂度效率的比较

在这里插入图片描述我们可以看到当测试数据很大时 O(logN) 和 O(1) 的效率几乎是一样的,所以二分查找是一种效率很高的算法,但是它也有一个缺陷,那就是它操作的数组元素必须是有序的。

四、空间复杂度的概念

空间复杂度也是一个数学表达式,是对一个算法在运行过程中额外占用存储空间大小的量度。

1.空间复杂度的概念

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

2.空间复杂度的计算方法

空间复杂度的计算方法和时间复杂度非常相似,且都是用大O的渐进法表示。具体的计算方法如下:
1.用常数1取代运行过程中定义的常数个变量;
2.在修改后的运行次数函数中,只保留最高阶项;
3.如果最高阶项存在且不是1,则去除与这个项目相乘的乘数;

3.常见的空间复杂度的计算

(1)冒泡排序的空间复杂度

void  BubbleSort(int arr[], int n)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < n - 1; i++)
	{
		for (j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

这里我们在循环外部定义了两个变量,然后在循环内部又定义了一个变量;可能有的同学会认为temp变量因为在循环内部,每次进入循环都会被重新定义,所以空间复杂度为N^2,其实不是的;

我们知道虽然时间是累积的,一去不复返,但是空间是不累积的,我们可以重复使用;对于我们的temp变量来说,每次进入if这个局部范围时开辟空间,离开这个局部范围时空间销毁,下一次在进入时又重新开辟空间,出去又再次销毁;所以其实从始至终temp都只占用了一个空间;

所以上面一共一共定义了三个变量,用大O的渐进表示法得到空间复杂度为O(1)。
(2)二分查找的空间复杂度

int BinarySearch(int arr[], int n, int x) //n元素个数  x:要查找的数
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] > x)
		{
			right = mid - 1;     //中间元素比x大就挪动right下标
		}
		else if (arr[mid] < x)
		{
			left = mid + 1;    //中间元素比x小就挪动left下标
		}
		else
			return mid;     //找到就返回该元素所在的下标
	}
	return 0;               //找不到就返回0
}

和冒泡排序的空间复杂度一样,这里只定义了三个(常数个)变量,所以空间复杂度是O(1)。
(3)阶乘递归的空间复杂度

long long Fac(int N)
{
	return N < 2 ? N : Factorial(N - 1) * N;
}

我们知道,每次函数调用开始时都会在栈区上形成自己的函数栈帧,调用结束时函数栈帧销毁;

对于上面的递归来说:只有当 N < 2 的时候函数才开始返回,而在这之前所形成的 Fac(N) Fac(N-1) Fac(N-2) …
这些函数的函数栈帧在返回之前都不会释放,而是一直存在,知道函数一步一步开始返回的时候开辟的空间才会被逐渐释放。所以在计算递归类空间复杂度度时,我们在意的是递归的深度;

这里调用的递归深度为 n - 1(递归 n - 1 次),所以空间复杂度为O(N)。
(4)斐波那契递归的空间复杂度

long long Fibonacci(size_t N)
{
	return N < 2 ? N : Fibonacci(N - 1) + Fibonacci(N - 2);
}

在这里插入图片描述首先就,斐波那契是逐个分支进行递归的,以上图为例,它会先递归6-5-4-3-2-1,再递归6-5-4-3-2,再递归6-5-4-2,以此类推,直到把最后一个分支递归完;

其次,空间是不会累积的,所以尽管我们同一个函数的函数栈帧会被开辟很多次,但是它仍然只计入一次开辟的空间复杂度。

所以递归调用开递归的深度,这里的空间复杂度为O(N)。

五、总结

1.时间复杂度和空间复杂度都是用大O的渐进表示法来表示;
2.时间复杂度看运算执行的次数、空间复杂度看变量定义的个数;
3.在递归中,时间复杂度看调用的次数,空间复杂度看调用的深度;
4.时间是累积的,一去不复返的;空间是不累计的,可以重复利用

冒泡排序的时间复杂度为O(N^2),空间复杂度为O(1)
二分查找的时间复杂度为O(logN),空间复杂度为O(1)
阶乘递归的时间复杂度为O(N),空间复杂度为O(N)
斐波那契递归的时间复杂度为O(2^N),空间复杂度为O(N)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度空间复杂度算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值