椋鸟数据结构笔记#2:复杂度

萌新的学习笔记,写错了恳请斧正。


目录

复杂度

时间复杂度

空间复杂度

通过复杂度衡量算法好坏


复杂度

复杂度是衡量算法好坏的一种方式。一般分为时间复杂度空间复杂度,分别用于衡量一个算法在运行时间长短和占据内存空间多少两方面的优劣。

一般我们考察复杂度就是找到相关的代数式,将常数全部忽略,将非最高次项全部忽略,在用括号括起来,前面写一个大O。这就是复杂度的大O表示法

比方说,对于代数式3N^2+2N+4,我们只取N^2;对于2N+4M+1,我们只取M+N。

(未知数的选用是随意的)

至于这个代数式如何找到,且看下方。

时间复杂度

时间复杂度的表达式就是算法执行基本语句的次数与未知量之间的代数关系(一般考虑最坏情况下的关系)

比方说下面这一个函数,不含未知数,其基本语句的运行次数是常数次,时间复杂度就是O(1)。

cpp 复制代码
void function()
{
    for (int i = 0; i < 114514; ++i)
    {    
        print("%d\n", i);
    }
}

而下面这个函数含有未知数,循环中的代码段要运行N次,那么时间复杂度就是O(n)。

cpp 复制代码
void function(int n)
{
    for (int i = 0; i < n; ++i)
    {    
        print("%d\n", i);
    }
}

而下面这个函数虽然有两个循环,但是相互独立,一个运行N次,一个运行2N次,一共是3N次,舍去常数后,时间复杂度还是O(N)。

cpp 复制代码
void function(int n)
{
    for (int i = 0; i < n; ++i)
    {    
        print("%d\n", i);
    }
    for (int i = 0; i < 2n; ++i)
    {    
        print("%d\n", i);
    }
}

但是要注意两个未知数一样时才能合并,不然就还是要写成两个未知数的代数式。比方说下面这一段的时间复杂度为O(M+N)。(如果说明M远大于N则可以省略N;如果MN存在代数关系则需要计算

cpp 复制代码
void function(int a, int b)
{
    for (int i = 0; i < 3a; ++i)
    {    
        print("%d\n", i);
    }
    for (int i = 0; i < 4b; ++i)
    {    
        print("%d\n", i);
    }
}

而下面这一段函数的时间复杂度为O(M^2+N),因为两个未知数是独立的,舍去非最高次项时不能舍弃含有其他未知数的项。

cpp 复制代码
void function(int a, int b)
{
    for (int i = 0; i < 3a; ++i)
    {
        for (int j = 0; j < 5a; ++j)    
        {
            print("%d\n", i);
        }
    }
    for (int i = 0; i < 4b; ++i)
    {    
        print("%d\n", i);
    }
}

再复杂一点,我们看看冒泡排序的时间复杂度,其结果为O(n^2):

cpp 复制代码
void bubbleSort(int p[], int n) //冒泡排序,并打印比较次数和移动次数
{
	_Bool flag = false;
	int cnt1 = 0;
	int cnt2 = 0;
	int swi = 0;

	do
	{
		flag = false;
		for (int i = 0; i < n - 1; i++)
		{
			if (p[i], p[i + 1])
			{
				swi = p[i + 1];
				p[i + 1] = p[i];
				p[i] = swi;
				flag = true;
				cnt2++;
			}
			cnt1++;
		}
	} while (flag);

	printf("%d %d ", cnt1, cnt2);
}

表达式也有可能含有其他数学运算,比方说二分搜素的时间复杂度就是O(logN)(注意对数的底数省略不写)

cpp 复制代码
int bin_search(int arr[], int left, int right, int key)
{
	int i;
	int j = 1;
	do
	{
		i = (right + left) / 2;
		if (key > arr[i])
			left = i;
		else
			right = i;
	} while (arr[i] != key);
	return i;
}

甚至更复杂的,有些表达式想要求出来需要较复杂的数列运算,包括但不限于使用等比数列求和公式、差比数列求和公式、裂项相消、配项......

空间复杂度

空间复杂度的表达式描述的是使用某算法所需要的**"额外"空间开销与未知数的关系(同样是一般考虑最坏情况**)。

首先,我们要注意这里的空间开销是算法额外产生的而不包括原本就开辟的空间

比方说,下面这个函数用于将一个数组的数据复制到另一个数组,那么本身就存在的数组的空间当然不能算在算法的空间开销中,其额外开辟的空间只有变量i,则空间复杂度为O(1)。

cpp 复制代码
void copyArr(int* arr1, int* arr2, int n)
{
    for (int i = 0; i < n; ++i)
    {
        arr2[i] = arr1[i];
    }
}

而如果我们写成这样,空间复杂度就变成O(N)了:

cpp 复制代码
void copyArr(int* arr1, int* arr2, int n)
{
    int* arrTmp = (int*)malloc(n * sizeof(int));
    for (int i = 0; i < n; ++i)
    {
        arrTmp[i] = arr1[i];
        arr2[i] = arrTmp[i];
    }
}

空间复杂度也可以变的较为复杂,比方说求斐波那契数列的空间复杂度:

cpp 复制代码
int Feb(int n)
{
	if (1 == n || 2 == n)
		return 1;
	else
		return Feb(n - 1) + Feb(n - 2);
}

想要算明白这个,需要对递归和函数栈帧(详见函数栈帧相关博客)有更深刻的理解。

由于递归不是一层一层进行的,所以其空间复杂度并不是总共开辟的栈帧空间的数量

递归是一条路走到底再返回走其他路的,所以空间复杂度等于其路径深度,也就是O(N)。

(这里学过深度优先搜素dfs就清楚了)

通过复杂度衡量算法好坏

一般我们认为,对于一个算法而言,其优劣可以通过未知数趋于无穷时的复杂度体现。而一般我们可以通过下面的不等式链比较复杂度:


相关推荐
Darling噜啦啦3 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠4 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾4 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
闪闪发亮的小星星4 天前
高斯光以及高斯光公式解释
笔记
cqbzcsq4 天前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
Qres8214 天前
算法复键——树状数组
数据结构·算法
阿米亚波4 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.4 天前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
.千余4 天前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他
自传.4 天前
尚硅谷 Vibe Coding|第二章 AI编程工具生态 学习笔记
笔记·学习·ai编程·尚硅谷·vibe coding