【数据结构初阶】一. 复杂度讲解

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

学C的第三十四天【程序环境和预处理】_高高的胖子的博客-CSDN博客

 =========================================================================

                     

1 . 算法效率

(1). 什么是数据结构:

               

数据结构(Data Structure)是计算机存储组织数据的方式

相互之间存在一种或多种特定关系的数据元素的集合

                     


                    

(2). 什么是算法:

                

算法(Algorithm)就是定义良好的计算过程

取一个或一组的值为输入,并产生出一个或一组值作为输出

简单来说算法就是一系列的计算步骤用来将输入数据转化成输出结果

                     


                    

(3). 算法的复杂度:

                     

算法编写成可执行程序后运行时需要耗费时间资源空间(内存)资源

因此衡量一个算法的好坏,一般是时间空间两个维度来衡量的,

时间复杂度空间复杂度

                      

时间复杂度主要衡量一个算法的运行快慢

空间复杂度主要衡量一个算法运行所需要的额外空间

计算机发展的早期计算机的存储容量很小。所以对空间复杂度很是在乎

但是经过计算机行业的迅速发展计算机的存储容量已经达到了很高的程度

所以我们如今已经不需要再特别关注一个算法的空间复杂度

               

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                   

2 . 时间复杂度

(1). 时间复杂度的概念:

               

计算机科学中算法的时间复杂度是一个函数,它定量描述了该算法的运行时间

一个算法执行所耗费的时间,从理论上说,是不能算出来的,

只有把你的程序放在机器上跑起来才能知道

但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦

所以才有了时间复杂度这个分析方式

            

一个算法所花费的时间其中语句的执行次数成正比例

算法中的基本操作的执行次数,为算法的时间复杂度

                       

即:

找到某条基本语句问题规模N之间数学表达式,就是算出该算法的时间复杂度

             

图例:Func1执行的基本操作次数

                 

上图得到的Func1执行的基本次数为:

F(N) = N^2 + 2*N +10

但实际我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数

只需要大概执行次数,那么这里我们使用大O的渐进表示法

                     


                    

(2). 大O的渐进表示法:

          

大O符号Big O notation):是用于描述函数渐进行为的数学符号

            

推导大O阶方法

1、常数1取代运行时间中的所有加法常数

2、在修改后的运行次数函数中,只保留最高阶项

3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数得到的结果就是大O阶

            

使用大O的渐进表示法以后,

F(N) = N^2 + 2*N +10

第一步:+10 变为 +1

第二步:保留最高阶项 N^2

第三步:最高项相乘常数为1不用去除

            

所以Func1的时间复杂度O(N^2)

N = 10             F(N) = 100        

N = 100           F(N) = 10000    

N = 1000         F(N) = 1000000

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项

简洁明了的表示出了执行次数

大O的渐进表示法本质计算的是算法属于哪个量级

           

另外有些算法的时间复杂度存在最好平均最坏情况

(可查看下方案例四)

最坏情况:任意输入规模的最大运行次数(上界)

平均情况:任意输入规模的期望运行次数

最好情况:任意输入规模的最小运行次数(下界)

                

例如:在一个长度为N数组中搜索一个数据x

最坏情况N次找到

平均情况N/2次找到

最好情况1次找到

实际操作下一般情况关注的是算法的最坏运行情况

所以数组中搜索数据时间复杂度为O(N)

                     


                    

(3). 常见时间复杂度计算案例:

          

案例一:

//示例一:
//计算Func2的时间复杂度:
void Func2(int N)
{int count = 0;for (int k = 0; k < 2*N; ++k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

图示:

               

               

案例二:

//示例二:
//计算Func3的时间复杂度:
void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++k){++count;}for (int k = 0; k < N; k++){++count;}printf("%d\n", count);
}

图示:

               

               

案例三:

//示例三:
//计算Func4的时间复杂度:
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++k){++count;}printf("%d\n", count + N);
}

图示:“cpu技术太强了”

               

               

案例四:

//示例四:
//计算strchr的时间复杂度:
const char* strchr(const char* str, int character);
//strchr库函数:在str字符数组中查找一个字符

图示:

               

               

案例五:

//示例五:
#include <stdio.h>
//计算BubbleSort的时间复杂度:
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);exchange = 1;}}if (exchange == 0){break;}}
}

图示:

               

               

案例六:

//示例六:
//计算BinarySearch的时间复杂度:
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n - 1;// [begin, end]:begin和end是左闭右闭区间,因此有=号while (begin <= end){int mid = begin + ((end - begin) >> 1);if (a[mid] < x){begin = mid + 1;}else if (a[mid] > x){end = mid - 1;}else{return mid;}}return -1;
}

图示:

               

               

案例七:

//示例七:
//计算阶乘递归Fac的时间复杂度:
long long Fac(size_t N)
{if (0 == N){return 1;}return Fac(N-1)*N;
}

图示:

               

               

案例八:

//示例八:
//计算斐波那契递归Fib的时间复杂度:
long long Fib(size_t N)
{if (N < 3){return 1;}return Fib(N - 1) + Fib(N - 2);
}

图示:

                     


                    

(4). 常见时间复杂度对比

             

一般算法常见的复杂度如下表:

5201314O(1)常数阶
3n + 4O(n)线性阶
3n^2 + 4n + 5O(n^2)平方阶
3log(2)n + 4O(logn)对数阶
2n + 3nlog(2)n + 14O(nlogn)nlogn阶
n^3 + 2n^2 + 4n + 6O(n^3)立方阶
2^nO(2^n)指数阶

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

3 . 空间复杂度

(1). 空间复杂度的概念:

                     

空间复杂度是一个数学表达式

对一个算法在运行过程中额外临时占用存储空间大小的量度

                

空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,

所以空间复杂度算的是变量的个数

空间复杂度计算规则基本跟时间复杂度类似也使用大O渐进表示法

                   

注意:

函数运行时所需要的栈空间(存储参数局部变量、一些寄存器信息等)

编译期间已经确定好了

因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定

                     


                    

(2). 常见空间复杂度计算案例:

           

案例一:

//计算BubbleSort的空间复杂度:
void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i - 1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0){break;}}
}

图示:

           

           

案例二:

//计算Fibonacci的空间复杂度:
//返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{if (n==0){return NULL;}long long* fibArray = (long long*)malloc((n + 1) * sizeof(long long));fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n; ++i){fibArray[i] = fibArray[i - 1] + fibArray[i - 2];}return fibArray;
}

图示:

           

           

案例三:

//计算阶乘递归Fac的空间复杂度:
long long Fac(size_t N)
{if (N == 0){return 1;}return Fac(N-1)*N;
}

图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

4 . 复杂度的oj练习

(1). 时间复杂度练习:消失的数字

                   

对应链接:

面试题 17.04. 消失的数字 - 力扣(LeetCode)

              

题目:

           

解决思路一:使用等差数列公式

             

假设数组nums包含从0到n的所有整数

那么就可以使用 0+N等差公式 计算出一个结果

该结果等于 0~n的各数相加总和

用这个结果 减去 数组中的值

结果就是消失的数字的值

              

图示:

对应代码:
int missingNumber(int* nums, int numsSize){int N = numsSize;int ret = N*(N+1)/2;for(int i = 0; i < N; i++){ret -= nums[i];}return ret;
}

              

解决思路二:异或法

             

用 0 异或 完整的0~N各值

再用该异或的结果异或 nums数组少一个值

因为异或后相同为0相异为1

此时两对值中相同的值就会异或为0

nums少的一个值异或后就会得到该值

              

图示:

对应代码:
int missingNumber(int* nums, int numsSize){int N = numsSize;int x = 0; //用来保存异或后的结果for(int i = 0; i <= N; ++i){x ^= i;}for(int i = 0; i < N; ++i){x ^= nums[i];}return x;
}

                      


                    

(2). 空间复杂度练习:轮转数组

                   

对应链接:

189. 轮转数组 - 力扣(LeetCode)

               

题目:要求时间复杂度为O(N),空间复杂度为为O(1)

           

解决思路一:整体右旋

             

原数组分为两部分

假设需要右旋k个数字

以原数组末尾k个数字为一组剩下其他数字为一组

两组进行调换,即可实现

              

图示:

对应代码:
void rotate(int* nums, int numsSize, int k){//用空间换时间:int n = numsSize; //数组长度int* tmp = (int*)malloc(sizeof(int)*n);k %= n; //确保要右旋个数小于数组大小//直接使用memcpy函数进行调换:memcpy(tmp, nums+n-k, sizeof(int)*k); //把后k个值移到前面// tmp : 起始位置// nums+n-k : 数组nums后k个值的起始位置// sizeof(int)*k :拷贝k个int大小的数据memcpy(tmp+k, nums, sizeof(int)*(n-k)); //把后k个值移到前面// tmp+k : 拷贝到tmp+k的位置,因为上面把后k个值放在了前面// nums : 数组nums开始位置// sizeof(int)*(n-k) :拷贝(n-k)个int大小的数据//再赋给数组nums:memcpy(nums, tmp, sizeof(int)*n);//释放开辟的动态空间:free(tmp);
}

           

解决思路二:逆置

             

将原数组的前 n-k 个数逆置

后 k 个数也逆置

最后再整体逆置,即可实现

              

图示:

对应代码:
//逆置函数:
void reverse(int* a, int left, int right)
{while(left < right){int tmp = a[left];a[left] = a[right];a[right] = tmp;++left;--right;}
}void rotate(int* nums, int numsSize, int k){k %= numsSize;//逆置前 n-k 个数:reverse(nums, 0, numsSize-k-1);//逆置后 k 个数:reverse(nums, numsSize-k, numsSize-1);//最后整体逆置:reverse(nums, 0, numsSize-1);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/98133.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STL ---- vector 使用

单纯的使用vector不和algorithm连用. vector自带的方法: push_back(num) pop_back() push_back 添加元素, pop_back删除元素.添加和删除都是在末尾添加和删除的. void assign(const_iterator first,const_iterator last); // 相当于拷贝函数 void assign(size_type n,const…

centos7升级openssh版本

linux升级openssh版本&#xff0c;升级到8.6p1 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.下载rpm包 2.编写一键安装脚本&#xff08;然后执行&#xff09; #把所有的rpm包&#xff0c;我都放到了/ydy目录&#xff0c;下面安装时&#xff0c;也指定了这个目录 #编…

day-41 代码随想录算法训练营(19)动态规划 part 03

343.整数拆分 思路&#xff1a; 1.dp存储的是第i个数&#xff0c;拆分之后最大乘积2.dp[i]max(dp[i],max(j*(i-j),j*dp[i-j]));3.初始化&#xff1a;dp[0]dp[1]0,dp[2]1;4.遍历顺序&#xff1a;外层循环 3-n&#xff0c;内层循环 1-i 2.涉及两次取max&#xff1a; dp[i] 表…

LLMs之Code:SQLCoder的简介、安装、使用方法之详细攻略

LLMs之Code&#xff1a;SQLCoder的简介、安装、使用方法之详细攻略 目录 SQLCoder的简介 1、结果 2、按问题类别的结果 SQLCoder的安装 1、硬件要求 2、下载模型权重 3、使用SQLCoder 4、Colab中运行SQLCoder 第一步&#xff0c;配置环境 第二步&#xff0c;测试 第…

【RPC 协议】序列化与反序列化 | lua-cjson | lua-protobuf

文章目录 RPC 协议gRPCJSON-RPC 数据序列化与反序列化lua-cjsonlua-protobuf RPC 协议 在分布式计算&#xff0c;远程过程调用&#xff08;英语&#xff1a;Remote Procedure Call&#xff0c;缩写为 RPC&#xff09;是一个计算机通信协议。该协议允许运行于一台计算机的程序调…

横向对比 npm、pnpm、tnpm、yarn 优缺点

前端工程化是现代Web开发中不可或缺的一环&#xff0c;它的出现极大地提升了前端开发的效率和质量。 在过去&#xff0c;前端开发依赖于手动管理文件和依赖&#xff0c;这导致了许多问题&#xff0c;如版本冲突、依赖混乱和构建繁琐等。而今&#xff0c;随着众多前端工程化工具…

常见项目管理中npm包操作总结

前言 我们在日常工作中&#xff0c;可能需要下载包、创建包、发布包等等。本篇推文将记录日常项目中关于npm包的操作。 引用包 npm仓库公开的包我们都可以通过npm install的命令进行引用下载。 而我们开发的业务公共组件需要在公司内部项目公共引用&#xff0c;而不希望公开为外…

洞察商机,驱动创新:智能数据分析引领企业发展

“五度易链”产业大数据解决方案由产业经济、智慧招商、企业服务、数据服务四大应用解决方案组成&#xff0c;囊括了产业经济监测、产业诊断分析、企业监测预警、企业综合评估、大数据精准招商、招商智能管理、企业管理、企业培育、企业市场服务、企业金融服务、产业数据开放服…

docker 安装xxljob

1. 安装mysql镜像 2.初始化xxljob的数据库和表 一、初始化db:https://codechina.csdn.net/mirrors/xuxueli/xxl-job/-/blob/2.3.1/doc/db/tables_xxl_job.sql 对脚本进行修改&#xff0c;添加ROW_FORMATDYNAMIC 安装xxljob 镜像 docker pull xuxueli/xxl-job-admin:2.3.1 …

双亲委派机制的作用

文章目录 类加载过程一、java有哪些类加载器&#xff1f;二、双亲委派机制自定义String类 总结 类加载过程 先简单说一下java的类加载器 类加载器&#xff0c;顾名思义就是一个可以将Java字节码加载为java.lang.Class实例的工具。这个过程包括&#xff0c;读取字节数组、验证…

[Android AIDL] --- AIDL工程搭建

0 AIDL概念 AIDL&#xff08;Android Interface Definition Language&#xff09;是一种 IDL 语言&#xff0c;用于生成可以在 Android 设备上两个进程之间进行进程间通信&#xff08;IPC&#xff09;的代码。 通过 AIDL&#xff0c;可以在一个进程中获取另一个进程的数据和调…

尚硅谷大数据项目《在线教育之离线数仓》笔记006

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第11章 数仓开发之ADS层 P087 P088 P089 P090 P091 P092 P093 P094 P095 P096 P097 P098 P099 P100 P101 P102 P103 P104 P105 P106 P107 P108 P109 P110 P111 …