复杂度分析

1. 时间复杂度

1.1 分析思路

运行时间可以直观准确地反映一个算法的效率。

时间复杂度分析统计的不是算法运行的时间,运行时间和平台环境也有着很大的关系,这个不是我们能控制的,所以,我们统计算法运行时间随着数据量变大时的增长趋势,我们举个例子看一下,假设输入数据大小为 n n n

// 常数阶
void AlgorithmA(int n)
{
	cout << "haha" << endl;
}
void AlgorithmB(int n)
{
	for (size_t i = 0; i < 1024*1024; i++)
	{
		cout << "haha" << endl;
	}
}
// 线性阶
void AlgorithmC(int n)
{
	for (size_t i = 0; i < n; i++)
	{
		cout << "haha" << endl;
	}
}
// 平方阶
void AlgorithmD(int n)
{
	for (size_t i = 0; i < n; i++)
	{
		for (size_t j = 0; j < n; j++)
		{
			cout << "haha" << endl;
		}
	}
}
  • A只有一个打印操作,运行时间不随 n n n增大而增大,B有1024*1024次打印操作,虽然运行时间很长,但是运行时间也不随 n n n增大而增大,所以A和B算法都是常数阶
  • B中打印操作要循环 n n n次,运行时间随着 n n n增大呈线性增长,属于线性阶
  • C中打印操作要循环 n 2 n^2 n2次,运行时间随着 n n n增大呈平方趋势增长,属于平方阶

在这里插入图片描述

1.2 大O渐进表示法法

根据分析思路,我们估算算法的时间复杂度只需要估算量级即可

常见的量级有以下几种:

𝑂(1) < 𝑂(log 𝑛) < 𝑂(𝑛) < 𝑂(𝑛 log 𝑛) < 𝑂( 𝑛 2 𝑛^2 n2) < 𝑂( 2 𝑛 2^𝑛 2n) < 𝑂(𝑛!)

常数阶 < 对数阶 < 线性阶 < 线性对数阶 < 平方阶 < 指数阶 < 阶乘阶

在这里插入图片描述

[声明]:此图来自hello‑algo.com

  1. 常数阶 O ( 1 ) O(1) O(1)

常数阶的操作数量与输入数据n的大小无关

// 常数阶
void AlgorithmB(int n)
{
	for (size_t i = 0; i < 1024*1024; i++)
	{
		cout << "haha" << endl;
	}
}
  1. 线性阶 O ( n ) O(n) O(n)

线性阶的操作数量与输入数据大小n呈线性级别增长,常出现在单层循环中

// 线性阶
void AlgorithmC(int n)
{
	for (size_t i = 0; i < n; i++)
	{
		cout << "haha" << endl;
	}
}	

遍历数组,遍历链表等操作都是线性阶

// 线性阶
#include <vector>
int arrTraversal(vector<int>& nums)
{
	int cnt = 0;
	// 范围for
	for (int e : nums)
	{
		cnt++;
	}
	return cnt;
}
  1. 平方阶 O ( n ) O(n) O(n)

平方阶的操作数量相对于输入数据大小 n n n呈平方级别增长。平方阶通常出现在嵌套循环中,内外层循环都为 O ( n ) O(n) O(n)

// 平方阶
void AlgorithmD(int n)
{
	for (size_t i = 0; i < n; i++)
	{
		for (size_t j = 0; j < n; j++)
		{
			cout << "haha" << endl;
		}
	}
}
  1. 指数阶 O ( 2 n ) O(2^n) O(2n)

“细胞分离”就是指数增长的典型案例

斐波那契数列也是一个指数级增长的典型案例

// 指数阶
long long Fib(size_t n)
{
 if(n < 3)
 return 1;
 
 return Fib(n-1) + Fib(n-2);
}

在这里插入图片描述

指数阶常常出现在递归中

  1. 对数阶 O ( l o g n ) O(logn) O(logn)

和指数阶相反,对数阶反映“每轮缩减到一半”。设输入数据大小为 n n n,每轮缩减到原来的一般,循环次数是 log ⁡ 2 n \log_2n log2n,为了书写方便,简记为 O ( l o g n ) O(logn) O(logn)

二分查找是一个典型的对数阶算法

// 对数阶
int BinarySearch(int* arr, int n, int x)
{
	assert(arr);
	int begin = 0;
	int end = n - 1;
	// [begin, end]:begin和end是左闭右闭区间,因此有=号
	while (begin <= end)
	{
		int mid = begin + ((end - begin) >> 1);
		if (arr[mid] < x)
			begin = mid + 1;
		else if (arr[mid] > x)
			end = mid - 1;
		else
			return mid;
	}
	return -1;
}

在这里插入图片描述

还有个有趣的知识,假设中国十四亿人口,每个人的省份证号按序排列,找一个人最多需要找多少次?

可以利用二分查找的思想,我们知道

2 10 = 1024 , 2 20 = 1024 × 1024 , 2 30 = 1024 × 1024 × 1024 2^{10} = 1024,2^{20} = 1024\times1024, 2^{30} = 1024\times1024\times1024 210=1024,220=1024×1024,230=1024×1024×1024

而 2 30 的量级是 1 × 10 9 , 14 亿 = 1.4 × 10 9 ,所以我们最多需要找 31 次 而2^{30}的量级是1 \times 10^9,14亿 =1.4\times10^9,所以我们最多需要找31次 230的量级是1×10914亿=1.4×109,所以我们最多需要找31

  1. 线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)

线性对数阶常出现在嵌套循环中,一层 O ( n ) O(n) O(n),一层 O ( l o g n ) O(logn) O(logn)

// 线性对数阶
int linerLogRecur(int n)
{
    if (n <= 1)
        return 1;
    int cnt = linerLogRecur(n / 2) + linerLogRecur(n / 2);
    for (size_t i = 0; i < n; i++)
    {
        cnt++;
    }
    return cnt;
}
  1. 阶乘阶== O ( n ! ) O(n!) O(n)==

$n! = n \times (n - 1) \times (n - 2) \times … \times 2 \times 1 $

// 阶乘阶
int factorialRecur(int n)
{
    if (n == 0)
        return 1;
    int cnt = 0;
    // 从 1 个分裂出 n 个
    for (size_t i = 0; i < n; i++) 
    {
        cnt += factorialRecur(n - 1);
    }
    return cnt;
}

2. 空间复杂度

2.1 算法相关空间

  • 输入空间: 存储算法的输入数据
  • 暂存空间: 存储算法运行过程中的变量、对象、函数
  • 输出空间: 存储算法的输出数据

一般情况,空间复杂度统计暂存空间输出空间

暂存空间还可以分成三部分:

  • 暂存数据: 保存算法运行中的常量、变量、对象
  • 栈帧空间: 保存调用函数的上下文数据,每次调用函数都会在栈顶创建一个栈帧,函数返回后,栈帧空间会被释放
  • 指令空间: 保存编译后的程序指令

2.2 分析方法

空间复杂度是临时占用存储空间大小的度量,函数运行时所要的栈帧空间在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时显式额外申请的空来确定

然后请记住:空间可以复用,但是时间不行

句法复杂度分析是对句子或文本的句法结构复杂程度进行评估的过程。以下从方法、应用及相关研究方面进行阐述: ### 方法 - **传统方法**:在传统的句法复杂度分析中,可能会使用基于规则的方法。根据预先定义好的语法规则来判断句子的结构复杂程度,例如句子的长度、嵌套层次等。还会使用聚类和句法模式识别方法,通过对句子的句法模式进行聚类分析,来判断其复杂程度。未来,随着数据量增加和问题复杂,这些方法也会不断发展,比如提出更高效的聚类算法,开发更鲁棒的句法模式识别方法应对噪声和失真影响,还可能与深度学习等技术结合带来新突破 [^3]。 - **基于统计的方法**:在基于统计的句法分析方法中,涉及句法分析算法和歧义消解模型的设计。这些也可应用于句法复杂度分析,通过统计大量句子的句法特征,来衡量句法复杂度,决定着分析的效率和正确率 [^1]。 - **依存句法分析方法**:依存句法分析通过揭示句子中词语之间的依存关系,为句法复杂度分析提供结构化支持。随着深度学习技术的发展,依存句法分析在精度和效率上取得显著突破,可利用其对句子的依存结构进行分析,从而评估句法复杂度 [^2]。 ### 应用 - **自然语言处理系统**:句法复杂度分析对于机器翻译、自然语言理解、信息抽取和自动文摘等自然语言处理系统有重要意义。在机器翻译中,了解源语言句子的句法复杂度有助于更准确地进行翻译;在信息抽取和自动文摘中,可根据句法复杂度筛选和提炼关键信息 [^1]。 - **教育领域**:可以用于评估文本的难度,帮助教师选择适合学生水平的阅读材料,也可用于分析学生作文的句法复杂度,以评估其语言能力的发展。 ### 相关研究 - **句法分析器的实现**:相关研究可能会致力于实现高效的句法分析器,从句法分析算法和歧义消解模型设计等方面入手,实现对句子句法结构的准确分析,这也为句法复杂度分析提供基础 [^1]。 - **依存句法分析技术脉络研究**:有研究从基础理论、主流算法、技术工具到实际应用,全面解析依存句法分析的技术脉络,为句法复杂度分析提供了核心的分析手段 [^2]。 ```python # 示例代码:简单的基于句子长度的句法复杂度评估 def simple_syntax_complexity(sentence): return len(sentence.split()) sentence = "This is a simple example sentence." complexity = simple_syntax_complexity(sentence) print(f"该句子的简单句法复杂度评估值为: {complexity}") ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值