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

通过复杂度衡量算法好坏

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


相关推荐
铅华尽26 分钟前
Nginx学习笔记
笔记·学习·nginx
花鱼白羊42 分钟前
代码随想录刷题day14(2)|(链表篇)02.07. 链表相交(疑点)
数据结构·链表
安和昂1 小时前
effective-Objective-C 第四章阅读笔记
网络·笔记·objective-c
SharkWeek.2 小时前
【力扣Hot 100】普通数组2
数据结构·算法·leetcode
烟锁迷城2 小时前
软考中级 软件设计师 第一章 第十节 可靠性
笔记
胡楚昊2 小时前
B站pwn教程笔记-1
笔记
Bunny02127 小时前
SpringMVC笔记
java·redis·笔记
Amd7948 小时前
深入探讨索引的创建与删除:提升数据库查询效率的关键技术
数据结构·sql·数据库管理·索引·性能提升·查询优化·数据检索
OKkankan13 小时前
实现二叉树_堆
c语言·数据结构·c++·算法
贾贾202313 小时前
配电网的自动化和智能化水平介绍
运维·笔记·科技·自动化·能源·制造·智能硬件