椋鸟数据结构笔记#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就清楚了)

通过复杂度衡量算法好坏

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


相关推荐
2603_969694852 小时前
【JavaWeb学习记录】MyBatis核心知识点与实战笔记:从入门到动态SQL
笔记·sql·学习·mysql·mybatis·javaweb·学习方法
Pniubi2 小时前
关于算法用现成数据结构和手撕
数据结构
可乐ea3 小时前
智能体记忆该拆成几层:笔记、记忆表示与注入上下文
服务器·数据库·笔记·ai智能体·提示词工程·上下文工程·记忆系统
Go Work6 小时前
两个等值条件摆着,MySQL 偏要全表扫
经验分享·笔记
Oll Correct6 小时前
Adobe illustrator 案例十四:花元素标志草图绘制
笔记·ui·adobe·illustrator
每月一号准时摆烂11 小时前
服务器运维笔记(一)——使用shell编写实时查看BMC功率的脚本
运维·服务器·笔记
老师我太想进步了202613 小时前
【数据结构-6】算法美学之单链表(1)
数据结构·算法
希望我万事顺遂14 小时前
力扣hot100-滑动窗口最大值
数据结构·算法·leetcode
꧁细听勿语情꧂14 小时前
二叉树余题,排序、直接插入排序、希尔排序
c语言·开发语言·数据结构·算法
是main不是漫15 小时前
【双指针|LeetCode1089 复写零】巧用逆向思路,破解原地覆盖陷阱
数据结构·算法·leetcode