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

通过复杂度衡量算法好坏

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


相关推荐
澪吟5 分钟前
算法性能的核心度量:时间复杂度与空间复杂度全解析
数据结构·算法
louisdlee.28 分钟前
扫描线1:朴素扫描线
数据结构·c++·算法·扫描线
wan5555cn1 小时前
中国启用WPS格式进行国际交流:政策分析与影响评估
数据库·人工智能·笔记·深度学习·算法·wps
仰泳的熊猫1 小时前
LeetCode:1905. 统计子岛屿
数据结构·c++·算法·leetcode
THGML2 小时前
排序算法解析
数据结构·算法·排序算法
OKkankan2 小时前
模板的进阶
开发语言·数据结构·c++·算法
拾光Ծ2 小时前
【高阶数据结构】哈希表
数据结构·c++·哈希算法·散列表
我不会插花弄玉2 小时前
c语言实现栈【由浅入深-数据结构】
c语言·数据结构
半夏知半秋3 小时前
redis-哨兵模式配置整理
数据库·redis·笔记·后端·学习·lua·安全架构
草莓熊Lotso3 小时前
模板进阶:从非类型参数到分离编译,吃透 C++ 泛型编程的核心逻辑
linux·服务器·开发语言·c++·人工智能·笔记·后端