算法相关概念讲解和时间复杂度,空间复杂度的简单分析

2024-08-27 22:04

本文主要是介绍算法相关概念讲解和时间复杂度,空间复杂度的简单分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

算法相关概念讲解和时间复杂度,空间复杂度的简单分析

  • 1.算法的概念
  • 2.算法的描述
    • 2.1.自然语言描述
    • 2.2.流程图描述
    • 2.3.伪代码描述
  • 3.时间复杂度概念、简单分析及相关例题
    • 3.1.例题.最大子段和
      • 题目描述
      • 输入格式
      • 输出格式
      • 输入输出样例
        • 输入 #1
        • 输出 #1
      • 提示
          • 样例 1 解释
          • 数据规模与约定
      • 思路1.直接枚举
      • 思路2.动态规划
    • 3.2.不同量级的时间复杂度总结和归纳
  • 4.空间复杂度概念、简单分析及相关例题
    • 4.1.例题
    • 4.2.注意事项
  • 5.练习题目和答案
    • 5.1.题目
    • 5.2.答案

1.算法的概念

    在数学和计算机中,算法 ( a l g o r i t h m ) (algorithm) (algorithm)是指一个被定义好的、计算机可实施的有限步骤或次序,常用于计算、数据处理和自动推理。算法是有效方法,包含一系列定义清晰的指令,并可于有限的时间及空间内清楚地表述出来。

    我们通常用时间复杂度空间复杂度来衡量一个算法的优劣,而同一个问题可以用具有不同时间复杂度和空间复杂度的算法来求解。

    算法具有以下五个重要特征。

  1. 输入项:一个算法有0个或多个输入。
  2. 输出项:一个算法有1个或多个输出。
  3. 确定性:算法的每一个步骤必须有确定的定义。即语句的描述不能存在二义性,对于每一种情况,需要执行的动作都应该是严格的、清晰的。
  4. 有穷性:算法必须能在执行有限步之后终止,即在执行有限步后自动结束,而不会出现有限循环,并且每一步都在规定的时间内完成。
  5. 可行性:算法执行的任何步骤都是可以被分解为基本的、可执行的操作步骤,即算法的每一条运算原则上都能精确地执行。

    其中,C++入门需要学习的算法有模拟(高精度),排序,枚举,递推递归,贪心,二分,搜索,简单动态规划等。

2.算法的描述

    :以下内容可能会触及到没有学过的知识,但是没太大关系,只需要大概了解一下主体内容就行。

    算法可采用多种语言来描述,各种描述语言在对问题的描述能力方面存在一定的差异。常用的方式有自然语言流程图伪代码等。不管用哪种描述方式表达算法,其描述的结果必须满足算法的五个特征。

2.1.自然语言描述

    算法的自然语言描述,指的是用日常使用的语言来描述解决问题的具体步骤。

    用自然语言描述算法的优点是通俗易懂,当算法中的操作步骤都是顺序执行时,比较直观,容易理解。缺点是如果算法中包含了分支结构和循环结构,并且操作步骤较多时,容易引起歧义,且描述得不够清晰

2.2.流程图描述

    流程图是以特定的图形符号加上说明来表示算法的图。流程图用一些图框来表示各种类型的操作,在框内写出各个步骤,然后用带箭头的线将它们连接起来,以表示算法执行的先后顺序

    使用流程图来描述算法,其优点是可以使读者更加清楚地了解整个算法的完整操作过程,有助于在工作过程中及时对算法进行修改。但流程图有其约定的符号,绘制时需要根据其符号进行搭建,绘制过程比较繁琐

    具体可以参考这一篇文章。

2.3.伪代码描述

    伪代码是一种非正式的用于描述模块结构图的语言。使用伪代码的目的是使被描述的算法可以容易地以任何一种编程语言实现。因此,伪代码必须结构清晰代码简单可读性好。伪代码介于自然语言与编程语言之间,以编程语言的书写形式说明算法功能。使用伪代码,你用拘泥于具体实现,相比程序语言它更类似自然语言,可以将整个算法运行过程的结构用接近自然语言的形式描述出来。

    使用伪代码描述算法没有严格的语法限制,书写格式比较自由,只要把意思表达清楚就可以了,它更侧重于对算法本身的描述。在伪代码描述中,表述关键词的语句一般用英文单词,其他语句可以用英文语句,也可以用中文语句。

    以下是一个冒泡排序的伪代码示例。

flag<-0
for i<-1 to n-1 doif flag为真 then dobreakfor j<-1 to n-i doif a(j)>a(j+1) then doswap a(j),a(j+1)flag<-1

3.时间复杂度概念、简单分析及相关例题

    一个程序在写出来之前是无法准确估计实际运行时间的。但是几乎所有算法竞赛的任务都会告知输入数据的规模和运行时间限制,这就允许选手通过分析算法的时间复杂度(Time complexity),从而实现估计能否在限定的时间能运行完程序。

    真的有这么神奇吗?别急,让我们不妨先来看一道例题。

3.1.例题.最大子段和

传送门 - 洛谷

题目描述

给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n n n

第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai

输出格式

输出一行一个整数表示答案。

输入输出样例

输入 #1
7
2 -4 3 -1 2 -4 3
输出 #1
4

提示

样例 1 解释

选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,1,2},其和为 4 4 4

数据规模与约定
  • 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 1 0 3 n \leq 2 \times 10^3 n2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 104ai104

思路1.直接枚举

    最直接的思路就是双重循环,枚举某一个区间的所有数并进行累加。代码如下:

#include<bits/stdc++.h>
using namespace std;
int main()
{int n,a[200010],sum,ans=INT_MIN;//用ans记录当前答案,sum记录当前区间的和cin>>n;for(int i=1;i<=n;i++)cin>>a[i];for(int i=1;i<=n;i++)//枚举起点for(int j=0;i+j<=n;j++)//枚举长度{sum=0;//归0for(int k=0;k<=j;k++)//累加sum+=a[i+k];ans=max(ans,sum);//更新答案}cout<<ans;return 0;
}

    那么,这个程序运行需要花多久时间呢?这主要还跟输入规模 n n n有关。我们可以根据这份代码估计这个程序运行的次数

    这段代码中, n n n比较大时,sum+=a[i+k];会运行很多次。对于给定的 n n n,我们可以定义一个计数器cnt,每运行一次这个语句就cnt++,最后再输出。

    当然,我们也可以使用数学的方法来估算。假设给定的 n n n 3 3 3。设时间复杂度为 T ( n ) T(n) T(n),最外层的循环变量i x x x时的运行总次数为为 S x S_x Sx,可以得出:
T ( 3 ) = S 1 + S 2 + S 3 = ( 1 + 2 + 3 ) + ( 1 + 2 ) + 1 = ( 1 + 3 ) ∗ 3 2 + ( 1 + 2 ) ∗ 2 2 + ( 1 + 1 ) ∗ 2 2 ( 等差数列求和公式 ) = 1 2 ( 4 ∗ 3 + 3 ∗ 2 + 2 ∗ 1 ) = 1 2 [ ( 3 2 + 3 ) + ( 2 2 + 2 ) + ( 1 2 + 1 ) ] = 1 2 [ ( 1 2 + 2 2 + 3 2 ) + ( 1 + 2 + 3 ) ] = 1 2 [ 1 6 ∗ 3 ( 3 + 1 ) ( 2 ∗ 3 + 1 ) + ( 1 + 2 + 3 ) ] ( 平方和公式,其中 n = 3 ) = 1 2 [ 1 6 ∗ 3 ( 3 + 1 ) ( 2 ∗ 3 + 1 ) + ( 1 + 3 ) ∗ 3 2 ] ( 等差数列求和公式 ) \begin{align*} T(3)&=S_1+S_2+S_3\\ &=(1+2+3)+(1+2)+1\\ &=\frac{(1+3)*3}2+\frac{(1+2)*2}2+\frac{(1+1)*2}2(等差数列求和公式)\\ &=\frac1 2(4*3+3*2+2*1)\\ &=\frac1 2[(3^2+3)+(2^2+2)+(1^2+1)]\\ &=\frac1 2[(1^2+2^2+3^2)+(1+2+3)]\\ &=\frac1 2[\frac1 6*3(3+1)(2*3+1)+(1+2+3)](平方和公式,其中n=3)\\ &=\frac1 2[\frac1 6*3(3+1)(2*3+1)+\frac{(1+3)*3}2](等差数列求和公式) \end{align*} T(3)=S1+S2+S3=(1+2+3)+(1+2)+1=2(1+3)3+2(1+2)2+2(1+1)2(等差数列求和公式)=21(43+32+21)=21[(32+3)+(22+2)+(12+1)]=21[(12+22+32)+(1+2+3)]=21[613(3+1)(23+1)+(1+2+3)](平方和公式,其中n=3)=21[613(3+1)(23+1)+2(1+3)3](等差数列求和公式)
    从一般到特殊,我们可以得出一般情况下输入规模为 n n n时此程序的运行次数的代数式。
T ( n ) = S 1 + S 2 + S 3 + . . . + S n = 1 2 [ ( n + 1 ) 2 + n 2 + ( n − 1 ) 2 + . . . 1 2 + 1 ] = 1 2 [ ( 1 2 + 2 2 + 3 2 + . . . + n 2 ) + ( 1 + 2 + 3 + . . . + n ) ] = 1 2 [ 1 6 n ( n + 1 ) ( 2 n + 1 ) + ( 1 + 2 + 3 ) ] ( 平方和公式 ) = 1 2 [ 1 6 n ( n + 1 ) ( 2 n + 1 ) + ( 1 + n ) ∗ n 2 ] ( 等差数列求和公式 ) = 1 6 ( n 3 + 3 n 2 + 2 n ) \begin{align*} T(n)&=S_1+S_2+S_3+...+S_n\\ &=\frac1 2[(n+1)^2+n^2+(n-1)^2+...1^2+1]\\ &=\frac1 2[(1^2+2^2+3^2+...+n^2)+(1+2+3+...+n)]\\ &=\frac1 2[\frac1 6n(n+1)(2n+1)+(1+2+3)](平方和公式)\\ &=\frac1 2[\frac1 6n(n+1)(2n+1)+\frac{(1+n)*n}2](等差数列求和公式)\\ &=\frac 1 6(n^3+3n^2+2n) \end{align*} T(n)=S1+S2+S3+...+Sn=21[(n+1)2+n2+(n1)2+...12+1]=21[(12+22+32+...+n2)+(1+2+3+...+n)]=21[61n(n+1)(2n+1)+(1+2+3)](平方和公式)=21[61n(n+1)(2n+1)+2(1+n)n](等差数列求和公式)=61(n3+3n2+2n)
    其中, n 3 n^3 n3对这个式子起主导作用,可以说这个程序的时间复杂度就是 O ( n 3 ) O(n^3) O(n3)

    一般的家用计算机一秒可以运行千万到数亿( 1 0 8 10^8 108)。代入这道题的数据范围,得到 n 3 = 4 ∗ 1 0 10 n^3=4*10^{10} n3=41010,不能通过所有的测试点。
在这里插入图片描述

思路2.动态规划

    这道题的正解是动态规划。这里暂时不需要知道动态规划是什么,直接给出代码。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 200010
int main()
{int dp[MAXN],a[MAXN],n,ans=INT_MIN;cin>>n;for(int i=1;i<=n;i++)cin>>a[i];dp[1]=a[1];//初始状态 for(int i=2;i<=n;i++){dp[i]=max(a[i],dp[i-1]+a[i]);//状态转移方程 ans=max(ans,dp[i]);}cout<<ans;return 0;
}

    这里我们只需要分析时间复杂度就行了。大多数情况下,我们都不需要使用复杂的数学公式进行推导,而是只需要得出一个大概的式子。例如上面的代码,主要部分就是这个代码段:

for(int i=2;i<=n;i++)
{dp[i]=max(a[i],dp[i-1]+a[i]);//状态转移方程 ans=max(ans,dp[i]);
}

    可以看出,这个循环一共执行了 n − 1 n-1 n1次。那么它的时间复杂度大概就是 O ( n ) O(n) O(n)

3.2.不同量级的时间复杂度总结和归纳

    在编写程序时,我们通常会使用估算的方法来计算某个算法的时间复杂度。有的时候,为了严谨,也会使用数学公式进行推导。

    在编写程序时,我们要根据题目给出的数据范围,合理编写不同时间复杂度的程序。下表提供了常见复杂度与数据范围。

复杂度常见范围冒险范围
O ( n ) O(n) O(n) 1 0 7 10^7 107 1 0 8 10^8 108
O ( n log ⁡ n ) O(n\log n) O(nlogn) 3 ∗ 1 0 5 3*10^5 3105 1 0 6 10^6 106
O ( n log ⁡ 2 n ) O(n\log ^2 n) O(nlog2n) 1 0 5 10^5 105 1 0 6 10^6 106
O ( n 2 ) O(n^2) O(n2) 5 1 0 3 5^10^3 5103 1 0 4 10^4 104
O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn) 1 0 3 10^3 103 3 ∗ 1 0 3 3*10^3 3103
O ( n 3 ) O(n^3) O(n3) 100 100 100 400 400 400
O ( 2 n ) O(2^n) O(2n) 20 20 20 25 25 25
O ( n ∗ 2 n ) O(n*2^n) O(n2n) 15 15 15 20 20 20
O ( n ! ) O(n!) O(n!) 10 10 10 10 10 10

    下图提供了不同时间复杂度的运算次数随数据规模增长趋势。
在这里插入图片描述
    有的时候,我们并不能直接编写出满分程序。这个时候,我们可以尝试编写非完美算法

  1. 完成较小数据范围的高复杂度算法;
  2. 解决部分特殊情况;
  3. 使用近似算法,比如随机算法,启发式搜索,模拟退火等。

4.空间复杂度概念、简单分析及相关例题

    空间复杂度是评估算法的另一个重要因素。不过在比赛时,给出的栈空间,即空间限制十分充裕。这就导致了空间复杂度相比来说就没有时间复杂度那么重要。

4.1.例题

    试分析下面算法的空间复杂度。

    注意:MAXNMAXM分别为nm的数据范围。

#include<iostream>
using namespace std;
#define MAXN 110
#define MAXM 1010
int a[MAXN][MAXM],n,mcnt;
int main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>a[i][j];for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(a[i][j]==10)cnt++;cout<<cnt;return 0;
}

    解析:本题中的空间开销主要来源于数组。所以主要关注数组占用的内存。其占用内存为n*m,故该算法的空间复杂度为 O ( n m ) O(nm) O(nm)

4.2.注意事项

    在代码运行的过程中,开辟数组,设立STL容器,运行递归函数,都会占用较大内存。特别是设立STL容器,需要小心题目的数据范围。

5.练习题目和答案

5.1.题目

    试分析下面每个算法的时间复杂度和空间复杂度(递归函数带来的开销不需要计算):
1.

int accumulation(int n) {if (n == 1)return n;elsereturn n + accumulation(n - 1);
}
bool is_prime(int num) {if (num<2)return false;for (int i = 1; i * i <= num; i++)if (num%i == 0)return false;return true;
}
#include <iostream>
using namespace std;
int n;
long long f[15] = {1, 1};
int main() {cin >> n;for (int i = 2; i <= n; i++)f[i] = f[i - 1] * i;cout << f[n] <<endl;return 0;
}
#include <iostream>
#include <cstring>using namespace std;#define MAXN 1010
int n, primes[MAXN], pcnt;
bool is_prime[MAXN];int main() {cin >> n;memset(is_prime, 1, sizeof(is_prime));is_prime[0] = is_prime[1] = 0;for (int i = 2; i <= n; i++) {if (is_prime[i])primes[pcnt++] = i;for (int j = 0;  j < pcnt && i * primes[j] <= n;  j++) {is_prime[i * primes[j]] = 0;if (i % primes[j] == 0)break;}}for (int i = 0; i < pcnt; i++)cout << primes[i] << endl; return 0;
}
#include<iostream>using namespace std;int n, k;int solve1() {int l = 0, r = n;while (l < r) {int mid = (l + r) / 2;if (mid * mid <= n) l = mid + 1;else r = mid;}return l - 1;
}double solve2(double x) {if(x == 0) return x;for (int i = 0; i < k; i++)x = (x + n / x) / 2;return x;
}int main() {cin >> n >> k;double ans = solve2(solve1());cout<< ans << ' ' << (ans * ans == n) << endl;return 0;
}

5.2.答案

题号/类别时间复杂度空间复杂度
1 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
2 O ( n ) O(\sqrt n) O(n ) O ( 1 ) O(1) O(1)
3 O ( n ) O(n) O(n) O ( n ) O(n) O(n)
4 O ( n ) O(n) O(n) O ( n ) O(n) O(n)
5 O ( log ⁡ n + k ) O(\log n+k) O(logn+k) O ( 1 ) O(1) O(1)

解析:

  1. 可以转换成以下等价的迭代形式;
for (int i = 1; i <= n; i++)sum += i;
return sum;

    故时间复杂度为 O ( n ) O(n) O(n)
2. 主要分析下列循环,i*i<=num可以化成i<=sqrt(num)

for (int i = 1; i * i <= num; i++)
  1. 主要分析下列循环。
for (int i = 2; i <= n; i++)
  1. 主要分析两个循环。

    第一个,循环次数为 n n n

for (int i = 2; i <= n; i++)

    第二个,由于质数越到后面越稀疏,所以时间复杂度接近常数。

for (int j = 0;  j < pcnt && i * primes[j] <= n;  j++)

    合并得: O ( n ∗ 1 ) = O ( n ) O(n*1)=O(n) O(n1)=O(n)
5. 第一个函数,每次查找序列长度减半,时间复杂度为 O ( log ⁡ 2 n ) O(\log_2 n) O(log2n),简称 O ( log ⁡ n ) O(\log n) O(logn)

int solve1() {int l = 0, r = n;while (l < r) {int mid = (l + r) / 2;if (mid * mid <= n) l = mid + 1;else r = mid;}return l - 1;
}

    第二个函数,时间复杂度为 O ( k ) O(k) O(k)

for (int i = 0; i < k; i++)

    合并得: O ( log ⁡ n + k ) O(\log n+k) O(logn+k)

喜欢就订阅此专辑吧!

【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。

欢迎扫码关注蓝胖子编程教育
在这里插入图片描述

这篇关于算法相关概念讲解和时间复杂度,空间复杂度的简单分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112899

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h