NO.42十六届蓝桥杯备战|数据结构|算法|时间复杂度|空间复杂度|STL(C++)

数据结构

什么是数据结构

在计算机科学中,数据结构是⼀种数据组织、管理和存储的格式。它是相互之间存在⼀种或多种特定关系的数据元素的集合。

说点通俗易懂的话,数据结构就是数据的组织形式,研究的就是把数据按照何种形式存储在计算机中

为什么会有数据结构
  • 随着计算机的发展和应⽤范围的拓展,计算机需要处理的数据量越来越⼤,数据类型越来越多,数据之间的关系也越来越复杂。
  • 这就要求⼈们对计算机加⼯处理的数据进⾏系统的研究,即研究数据的特性、数据之间存在的关系,以及如何有效的组织、管理存储数据,从⽽提⾼计算机处理数据的效率。
  • 数据结构这⻔学科就是在此背景上逐渐形成和发展起来的。
    对数据结构更深层次的理解:数据结构研究的是数据本⾝的特性、数据与数据之间的关系,以及如何在计算机中存储数据,从⽽⾼效的处理这些数据。

数据结构的三要素

逻辑结构

逻辑结构:数据中各元素之间的逻辑关系。

它只关⼼数据中各个元素之间的关系,并不关⼼数据是怎么在内存中存储的

常⻅的逻辑结构

  1. 集合
    所有的数据只是在⼀个集合中,彼此之间再没有其他的联系
  2. 线性结构
    数据之间只存在⼀对⼀的关系
  3. 树形结构
    数据之间是⼀对多的关系
  4. 图结构
    数据之间存在多对多的关系
存储结构

存储结构⼜称物理结构,但是存储⼆字更能体现本意,后续我们统称存储结构。

存储结构顾名思义,就是数据在计算机中是如何存储的。

⽤⼤⽩话就是,怎么把这个数据结构存到计算机中。

数据的存储结构主要有顺序存储以及链式存储。

  1. 顺序存储
    把逻辑上相邻的元素存储在物理上也相邻的存储单元中。-数组
    在程序中典型处理⽅式即数组。即数据不仅在逻辑上是连续的,物理上也是连续的
  2. 链式结构
    通过指针来存储前⼀个或下⼀个数据元素的地址,从⽽实现元素和元素之间的关系
数据的运算

数据的运算(针对数据的各种操作),包括数据结构的实现,以及基于数据结构上的各种操作

  • 创建数据结构;
  • 增加数据;
  • 删除数据;
  • 查找数据;
  • 修改数据;
  • 排序数据;
  • 输出数据;

算法

什么是算法

算法(Algorithm)是指解题⽅案的准确⽽完整的描述,是⼀系列解决问题的清晰指令,算法代表着⽤系统的⽅法描述解决问题的策略机制。也就是说,能够对⼀定规范的输⼊,在有限时间内获得所要求的输出。

  • 算法是可以没有输⼊的,⼀定要有输出的。没有输出的算法是没有意义的。
    简单来说算法就是⼀系列的步骤,⽤来将输⼊数据转化成输出结果
算法好坏的度量

【事前分析法】

算法设计好后,根据算法的设计原理,只要问题规模确定,算法中基本语句执⾏次数和需求资源个数基本也就确定了

算法执⾏所需资源的个数与问题规模n有关。因此可以根据算法执⾏过程中对空间的消耗来衡量算法的好坏,这就是空间复杂度

算法中基本语句总的执⾏次数与问题规模n有关。因此可以根据算法执⾏过程中,所有语句被执⾏的次数之和来衡量算法的好坏,这就是时间复杂度。

综上所述,时间和空间的消耗情况就是我们度量⼀个算法好坏的标准,也就是时间复杂度和空间复杂度

时间复杂度

在计算机科学中,算法的时间复杂度是⼀个函数式 T ( N ) T(N) T(N) ,它定量描述了该算法的运⾏时间。这个函数式 T ( N ) T(N) T(N)计算了程序中语句的执⾏次数

计算⼀下fun中++count语句总共执⾏了多少次

c++ 复制代码
void fun(int N)  
{  
	int count = 0;  
	for(int i = 0; i < N; i++)  
	{  
		for(int j = 0; j < N; j++)  
		{  
			++count; // 执⾏次数是 n*n,也就是 n^2  
		}  
	}  
	for(int k = 0; k < 2 * N; k++)  
	{  
		++count; // 执⾏次数是 2*n  
	}  
	int M = 10;  
	while(M--)  
	{  
		++count; // 执⾏次数 10  
	}  
}

fun 函数 ++count 语句的总执⾏次数:

T (N) = N^2 + 2 × N + 10

  • 当N = 10 时,T (N) = 100 + 20 + 10 = 130
  • 当N = 100 时,T (N) = 10000 + 200 + 10 = 10210
  • 当N = 1000 时,T (N) = 1000000 + 2000 + 10 = 1002010
  • 当N = 10000 时,T (N) = 100000000 + 20000 + 10 = 100020010
⼤O表⽰法

在上述案例中,对N取值进⾏分析,随着N的增⼤,对结果影响最⼤的⼀项是N^2 ,2xN和10可以忽略不计,算法执⾏时间的增⻓率与 T ( N ) T(N) T(N)中的N^2的增⻓率基本⼀致。

因此,在计算时间复杂度的时候,⼀般会把 T ( N ) T(N) T(N)中对结果影响不⼤的项忽略掉,该种表⽰时间复杂度的⽅式称为⼤O渐进时间复杂度-粗略的估计⽅式,只看影响时间开销最⼤的⼀项。

所以对于上述fun函数,其时间复杂度为 O ( T ( N ) ) = O ( N 2 + 2 N + 10 ) O(T(N))=O(N^2+2N+10) O(T(N))=O(N2+2N+10),取最⾼阶项最终为: O ( N 2 ) O(N^2) O(N2)

推导⼤O渐进时间复杂度的规则

  1. 时间复杂度函数式T (N)中,只保留最⾼阶项,去掉那些低阶项;
  2. 如果最⾼阶项存在且不是1 ,则去除这个项⽬的常数系数;
  3. T (N) 中如果没有N 相关的项⽬,只有常数项,⽤常数1 取代所有加法常数。
最优、平均和最差时间复杂度

在n 个整形元素数组中,检测x 是否存在,若存在返回其在数组中的下标,否则返回-1

c++ 复制代码
int find (int a[], int n, int x)  
{  
	for (int i = 0; i < n; i++)
	{  
		if (a[i] == x)  
		return i;  
	}  
	return -1;  
}
21 15 66 34 82 95 53 78 46 17
0 1 2 3 4 5 6 7 8 9

在查找过程中,需⽤x 与数组中元素依次⽐较,因此总的⽐较次数就是该算法的时间复杂度。则:

  • 若待查找元素x 为21 ,只需要⽐较1 次,为算法的最优(好)情况。
  • 若带查找元素x 为17 ,或者是不存在的元素,需要⽐较n 次,为算法的最坏(差)情况。
  • 所有情况的⽐较次数之和,除以总情况,为算法的平均情况。
    查找第⼀个元素需⽐较1次,查找第2个元素需⽐较2次,...,查找第n个元素需⽐较n次,假设每个元素查找的概率相同,则平均⽐较次数为
    f ( n ) = 1 + 2 + 3 + ⋯ + n n = ( 1 + n ) × n 2 n = 1 + n n f(n)=\frac{1+2+3+\dots+n}{n}=\frac{(1+n)\times n}{2n}=\frac{1+n}{n} f(n)=n1+2+3+⋯+n=2n(1+n)×n=n1+n
  • 最好时间复杂度为O(1) ;
  • 最坏时间复杂度为O(n) ;
  • 平均时间复杂度为 O ( 1 + n 2 ) O(\frac{1+n}{2}) O(21+n) ,也就是 O ( n ) O(n) O(n)
    但是,⽆论是在竞赛还是⼯程中,算法的时间复杂度⼀般为最差情况。因为最差情况是⼈对⼀件事情所能承受的底线,因此find 算法的时间复杂度为O(n) 。

时间复杂度计算案例

案例⼀
c++ 复制代码
void func1(int N)  
{  
	int count = 0;  
	for(int k = 0; k < 2 * N; k++)  
	{  
		++count;  
	}  
	int M = 10;  
	while (M--)  
	{  
		++count;  
	}  
	printf("%d\n", count);  
}

基本语句 ++count 关于问题规模n总执⾏次数的数学表达式为:f(n) = n × 2 + 10 ;

保留最⾼阶项,省略最⾼阶项的系数后的⼤O渐进表⽰法为:O(n)

案例⼆
c++ 复制代码
void func2(int N)  
{  
	int count = 0;  
	for (int k = 0; k < 1000; k++)  
	{  
		++count;  
	}  
	printf("%d\n", count);  
}

基本语句 ++count 关于问题规模n总执⾏次数的数学表达式为:f(n) = 1000 ;

不论n如何变化, ++count 总的执⾏次数都是1000 ,故时间复杂度为:O(1)

案例三
c++ 复制代码
void func3(int m, int n)  
{
	for (int i = 0; i < m; i++)  
	{  
		printf("hello\n");  
	}  
	for (int i = 0; i < n; i++)  
	{  
		printf("hello\n");  
	}  
}

基本语句 printf("") 总的执⾏次数为f(m, n) = m + n ;

m和n的变化都是影响基本语句执⾏次数,即m和n都是问题规模,故时间复杂度为 O ( m , n ) O(m,n) O(m,n) ,或者也可以表⽰为 O ( m a x ( m , n ) ) O(max(m, n)) O(max(m,n)) 。

案例四
c++ 复制代码
void func4(int m, int n)  
{  
	for (int i = 0; i < m; i++)  
	{  
		for(int j = 0; j < n; j++)  
		{  
			printf("%d, ", i);  
		}  
		printf("\n");  
	}  
}

基本语句 printf("%d, ", i) 总的执⾏次数为f(m, n) = m × n ;

m和n的变化都是影响基本语句执⾏次数,即m和n都是问题规模,故时间复杂度为 O ( m × n ) O(m \times n) O(m×n) 。

案例五
c++ 复制代码
void func5(int n)  
{  
	int cnt = 1;  
	while (cnt < n)  
	{
		cnt *= 2;  
	}  
}

假设执⾏次数为x ,则 2 x = n 2^x=n 2x=n ;因此执⾏次数: x = log ⁡ 2 n x=\log_{2}n x=log2n。

⼀般情况下不管底数是多少都可以省略不写,那么本案例的时间复杂度为 log ⁡ n \log n logn

案例六
c++ 复制代码
// ⽤递归计算 N 的阶乘  
long long fac(int N)  
{  
	if(N == 0) return 1;  
	return fac(N - 1) * N;  
}

递归算法时间复杂度求解⽅式为,单次递归时间x总的递归次数。

注意,这⾥只是简易的估算⽅式。递归算法的时间复杂度严谨的计算⽅法是利⽤主定理(Master

Theorem)来求得递归算法的时间复杂度。

对于递归算法,仅需掌握这样简易的计算⽅式

单次递归没有循环之类,所以时间复杂度为常数。总的递归次数就是递归过程中,fac递归调⽤了多少次。

Fac(5) 需要递归6次,则Fac(n) 就需要递归n + 1次,故递归求阶乘的时间复杂度为O(n) 。

空间复杂度

有了时间复杂度的铺垫,空间复杂度的计算就⽐较容易了。

在算法竞赛中,空间复杂度就是整个程序在解决这个问题时,⼀共使⽤了多少空间。相⽐较于时间复杂度,空间复杂度就没那么受关注,因为多数情况下题⽬所给的内存限制是⾮常宽裕的。

但是,这并不表明可以肆⽆忌惮的使⽤空间,⼀旦超出给定的限制,程序也是不会通过的

冒泡排序
c++ 复制代码
#include <iostream>  
using namespace std;  
const int N = 20;  
int arr[N];  
int main()  
{  
	int n = 0;  
	cin >> n;  
	int i = 0;  
	//输⼊  
	for(i=0; i < n; i++)  
	cin >> arr[i];  
	//排序  
	for(i = 0; i < n-1; i++)  
	{  
		int j = 0;  
		for(j = 0; j <= n-1-i; j++)  
		{  
			if(arr[j] < arr[j+1])  
			{  
				int tmp = arr[j];  
				arr[j] = arr[j+1];  
				arr[j+1] = tmp;  
			}  
		}  
	}  
	//输出  
	for(i=0; i < n; i++)  
	{  
		cout << arr[i] << endl;  
	}  
	return 0;  
}

空间复杂度:需要⼀个和问题规模⼀致的数组来存数据。因此空间复杂度为O(N)

递归求阶乘

递归算法空间复杂度求解⽅式,单次递归空间复杂度*递归次数。

c++ 复制代码
// 计算递归求阶乘Fac算法的空间复杂度?  
long long fac(int N)  
{  
	if (N == 0) return 1;  
	return fac(N - 1) * N;  
}

递归的调⽤会有空间消耗

常⻅复杂度的增⻓对⽐

各种常⻅的时间复杂度的⼤⼩对⽐:
O ( 1 ) < O ( l o g N ) < O ( N ) < O ( N l o g N ) < O ( N 2 ) < O ( N 3 ) < O ( 2 N ) < O ( n ! ) O(1) < O(logN) < O(N) < O(NlogN) < O(N^2) < O(N^3) < O(2^N ) < O(n!) O(1)<O(logN)<O(N)<O(NlogN)<O(N2)<O(N3)<O(2N)<O(n!)

算法竞赛中的时间限制和空间限制

  1. 信息学竞赛中,C++通常设定1到2秒的时间限制,要控制运⾏次数在107⾄108 之间。
  2. 空间限制在128MB或256MB ,可以开⼀个 3 × 1 0 7 3 \times 10^7 3×107⼤⼩的int类型数组,或者5000x5000⼤⼩的⼆维数组,⼀般情况下都是够⽤的

各种数据量下,可选⽤的算法的时间复杂度

O ( log ⁡ n ) O(\log n) O(logn) O ( n ) O(\sqrt{n}) O(n ) O ( n ) O(n) O(n) O ( n ∗ log ⁡ n ) O(n*\log n) O(n∗logn) O ( n 2 ) O(n^2) O(n2) O ( n 3 ) O(n^3) O(n3) O ( 2 n ) O(2^n) O(2n) O ( n ! ) O(n!) O(n!)
n ≤ 10 n\le 10 n≤10
n ≤ 25 n \le 25 n≤25 ×
n ≤ 100 n\le 100 n≤100 × ×
n ≤ 1 0 3 n \le 10^3 n≤103 × × ×
n ≤ 2 ∗ 1 0 5 n \le 2*10^5 n≤2∗105 × × × ×
n ≤ 1 0 7 n \le 10^7 n≤107 × × × × ×
n ≤ 1 0 9 n \le 10^9 n≤109 × × × × × ×
n ≤ 1 0 18 n \le 10^{18} n≤1018 × × × × × × ×

STL

C++标准库

C++标准是C++编程语⾔的规范,由国际标准化组织(ISO)制定。C++标准的发展历程可以追溯到1998年,当时ISO/IEC14882:1998标准被发布,这是第⼀个C++标准,常被称为C++98。随后,C++标准经历了多次更新和修订,包括C++03(2003年)、C++11(2011年)、C++14(2014年)、C++17(2017年)以及C++20。

最新的C++标准是C++23,于2023年发布,引⼊了许多新特性,但⽬前⽀持完整的编译器较少。

每⼀个版本的C++标准不仅规定了C++的语法、语⾔特性,还规定了⼀套C++内置库的实现规范,这个库便是C++标准库。C++标准库中包含⼤量常⽤代码的实现,如输⼊输出、基本数据结构、内存管理、多线程⽀持等。

我们可以这样简单的理解,C++给我们提供了⼀⼤堆的代码,这些代码⾥⾯包含特别多已经实现好的数据结构和算法。因此,在做题的时候,如果涉及的数据结构和算法C++标准库已经帮我们实现好了,那么就可以直接使⽤,避免重复造轮⼦。

造轮⼦指的是重复发明已有的算法,或者重复编写现成优化过的代码。

造轮⼦通常耗时耗⼒,同时效果还没有别⼈好。但若是为了学习或者练习,造轮⼦则是必要的。 因此,学好C++标准库能极⼤的提⾼编程效率

什么是STL

STL即标准模板库(Standard Template Library),是C++标准库的⼀部分,⾥⾯包含了⼀些模板化的通⽤的数据结构和算法。由于其模板化的特点,它能够兼容⾃定义的数据类型,避免⼤量的造轮⼦⼯作。

NOI和ICPC赛事都⽀持STL库的使⽤,当然,蓝桥杯也是⽀持的。因此,⼀定要学习STL的使⽤,能够极⼤的提⾼编写代码的效率

相关推荐
AI技术控2 分钟前
扩散模型算法实战——视频生成(主页有源码)
算法·音视频
就很对28 分钟前
7种数据结构
数据结构·windows
f狐0狸x1 小时前
【蓝桥杯每日一题】3.17
c语言·c++·算法·蓝桥杯·二进制枚举
爱康代码1 小时前
【c语言数组精选代码题】
c语言·开发语言·数据结构
不忘不弃2 小时前
一般矩阵的转置
人工智能·算法·矩阵
用户49430538293802 小时前
基于GIS数据的即时建筑模型编辑软件
前端·算法·gis
奔跑的废柴2 小时前
LeetCode 452. 用最少数量的箭引爆气球 java题解
java·算法·leetcode·贪心算法·贪心
AndrewHZ3 小时前
【图像处理】ISP(Image Signal Processor) 图像处理器的用途和工作原理?
图像处理·人工智能·深度学习·算法·智能手机·影像系统·isp
共享家95273 小时前
顺序表的C语言实现与解析
数据结构·算法
uhakadotcom4 小时前
英伟达最新的GTC 2025提供了哪些新技术新能力?
算法·面试·github