算法中的时间复杂度和空间复杂度计算方式

news/2024/4/26 20:32:31/文章来源:https://blog.csdn.net/u010194271/article/details/129256014

1 前言

作为一名“程序猿”,大家应该都听过这么一句话:程序=数据结构+算法。

这句话是由瑞士计算机科学家尼古拉斯·沃斯(Niklaus Wirth)在 1984 年获得图灵奖时说的一句话,这位大佬还以这句话为名出了一本书《Algorithms + Data Structures=Programs》,从此这句话就成为了大家耳熟能详的一句名言。

随着时间的推移,不管这句话是不是非常准确,但至少能说明数据结构与算法对程序来说是非常核心的基础,如果我们想要写出更多优秀优雅的代码,那么数据结构与算法是必须要掌握好的。

2 为什么要学习算法

很多人可能觉得,我不会算法,代码一样写得很"溜",算法这东西似乎用处不大。现在互联网的发达,我们想要什么几乎都可以在网上找到现成的,各种框架功能十分强大,似乎看起来确实不用算法也可以写出“好代码”。然而假如我们不懂算法,比如项目中用到了排序,我们如何评估代码的执行效率?再比如最常用的 ArrayList 和 LinkedList,我们该如何选择,又比如说我们需要去集合中找某一个数,又该如何写出性能优秀的代码呢?

同样的代码,如何判断谁的代码是优秀的代码?可读性,可扩展性,健壮性可能都可以用来判定,然而这些东西我觉得并不能直接体现出你代码的优秀,因为对用户而言,访问你的代码响应速度快那就是优秀的代码,相反,动辄响应几秒甚至更长时间的接口,恐怕就算你可读性再好,再健壮也称不上是好代码。

所以说一段代码是否优秀,最直接的判断标准就是性能,而如果要写出高性能的代码,那么就必须要了解算法,而且抛开这个因素,但凡不想一辈子都写 CRUD 代码的,也需要去了解算法,我们使用的很多框架和中间件底层都有数据结构和算法的身影,学好算法对我们源码阅读时理解其设计思想也是大有裨益的。

要说功利性的目的,那就是面试,目前很多大厂的面试,算法基本必面,所以想进大厂的话,咱们也得好好学学算法。

3 算法难学吗

提到算法,很多人的第一反应就是太难学了,学不会,或者说经常是看完就忘了,但是其实对于我们一个普通的开发者而言,因为并不需要我们去发明算法,我们需要的仅仅只是去灵活的运用算法,所以并不需要非常扎实的数据基础,当然基本的数学常识还是要有的。

如果说需要去发明设计一款算法,那就要去推导去证明算法的可行性,这种是需要具有非常扎实的数学基础的,一般人确实无法做到,然而我们普通程序员口中提到算法无非是二分查找法,哈希算法等,高级一点的就还有回溯,贪心,动态规划等等,这些所谓的算法都是已经有现成的公式了,我们要做的无非就是理解它,然后灵活的运用它。这就和我们以前学习数学公式一样,给你一个公式,然后你去做题,做题的过程其实就是去灵活的运用这个公式。

算法也是同理,都是有特定方法和特定思路的,我们也并不需要去推导证明这种方式为什么可行,所以学习算法没有其他诀窍,就是先理解思路,然后多练,等熟练了,自然就可以灵活运用了,也不会说学了立刻就忘了。学完就忘无非两个原因,一是没理解,而是没有练习巩固。

4 复杂度分析

数据结构与算法经常是放在一起讲,这两者是没办法独立的,因为算法是为了达到某种目的的一种实现方式,而数据结构是一种载体,也就是说算法必须依赖数据结构这种载体,否则就是空谈。换句话说:数据结构是为算法服务的,而算法又需要作用在特定的数据结构之上。

一个算法到底好不好,我们如何去评价?前面我们提到了,你的代码好不好,最直观的就是看响应速度,算法也一样,同样实现一个目的(比如说排序),谁的算法速度快,我们就可以认为谁的算法更优,如果说两种算法实现的速度差不多,那么我们还可以去评价算法所占用的空间,谁占用的空间少,那么就可以认为谁的算法更优,这就是算法的基础:时间复杂度和空间复杂度。

学习算法之前,我们必须要学会如何分析时间复杂度和空间复杂度(也就是“快”和“省”),否则自己写出来的算法自己都不知道算法的效率。

5 时间复杂度大 O表示法

接触过算法的都知道,算法的时间复杂度是用大写的“O”来表示的,比如:O(1),O(n),O(logn),O(nlogn),O(n²) 等等。

时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系,上面的这种时间复杂度表示法并不能真正反应一个算法的执行时间,反应的只是一个趋势,所以我们在分析复杂度的时候要关注“变”,忽略“不变”。

变指的是变量,也就是一段代码的执行时间是随着变量的变化而变化的,而不变指的是常量,也就是不论我的变量如何改变,执行时间都不会改变。

接下来我们就实际的来分析下常用时间复杂度的例子来练习一下。

5.1 O(1) 常数阶

0(1) 复杂度算法也称之为常数阶算法。这里的 1 是用来代指常量,也就是说这个算法的效率是固定的,无论你的数据量如何变化,效率都一样,这种复杂度也是最优的一种算法。

public static void print(int n){
int a = 1;
int b = 2;
int c = 3;
int sum = a + b + c;
System.out.println(sum);
}
上面的示例中不论有多少行代码,时间复杂度都是属于常数阶。换言之:只要代码不存在循环,递归等循环类调用,不论代码有多少行,其复杂度都是常数阶。

5.2 O(n) 线性阶

O(n) 复杂度算法也称之为线性阶。比如下面这个示例我们应该怎么分析复杂度呢?

public static void print1(int n){
int a = 0;
for (int i=0;i<n;i++){
System.out.println(i);
}
}
前面常量阶没分析是因为常量阶比较容易理解,接下来我们就以线性阶这个为例子来分析下具体是怎么得到的。

我们假设每一行代码的执行时间是 T,那么上面这段代码的执行复杂度是多少呢?

答案很明显,那就是 T+n*T,也就是 (n+1)T,而在算法中有一个原则,那就是常量可以被忽略,所以就得到了 nT,换成大 O 表示法就是 O(n)。

这只是一个简略的计算过程,大家也不用较真说每行代码执行时间可能不一样之类的,也不要较真说 for 循环占用了一行,下面的大括号也占用了一行,如果要较真这个,那我建议可以去想一下 1=1 为什么等于 2。

算法中的复杂度反应的只是一个趋势,这里 O(n) 反应的就是一个趋势,也就是随着 n 的变化,算法的执行时间是会降低的。

5.3 O(n²) 平方阶

知道了上面的线性阶,那么平方阶就很好理解了,双层循环就是平方阶,同理,三次循环就是立方阶,k 次循环就是 k 次方阶。

5.4 O(logn) 对数阶

O(logn) 也称之为对数阶,对数阶也很常见,像二分查找,二叉树之类的问题中会见到比较多的对数阶复杂度,但是对数阶也是比较难理解的一种算法复杂度。

下面我们还是来看一个例子:

public static void print2(int n){
int i=1;
while (i <= n) {
i = i * 2;
}
}
这段代码又该如何分析复杂度呢?这段代码最关键就是要分析出 while 循环中到底循环了多少次,我们观察这个循环,发现 i 并不是逐一递增,而是不断的翻倍:1->2->4->8->16->32->64 一直到等于 n 为止才会结束,所以我们得到了这样的一个公式:2^x=n。

也就是我们只要计算出 x 的值,就得到了循环次数,而根据高中的数学知识我们可以得到 x=log2n(2 在下面,是底数,试了几种方法都打不出来,放弃了),所以根据上面线性阶的分析方法,我们省略常量,就得到了示例中的算法复杂度为 O(log2n)。

同样的分析方式,下面的例子,我们可以很快的分析出复杂度就为 O(log3n):

int i=1;
while (i <= n) {
i = i * 3;
}
上面得到的 log3n 我们可以再做进一步的转换:log3n=log32 * log2n,而 log32(注意这几个地方的 3 是底数,在下面) 是一个常量,常量可以省略,所以也就得到了:O(log3n)=O(log2n)。同样的道理,不论底数是多少,其实最终都可以转化成和 O(log2n) 相等,正因为如此,为了方便,我们算法中通常就会省略底数,直接写作 O(logn)。

上面的数学公式大家如果忘了或者看不懂也没关系,只要记住不论对数的底数是多少,我们都算作 O(logn),而对于一个算法的复杂度是否是对数阶,还有一个简易的判断方法:当循环中下标以指定倍数形式衰减,那么这就是一个对数阶。

5.5 O(nlogn) 线性对数阶

如果理解了上面的对数阶,那么这种线性对数阶就非常好理解了,只需要在对数阶的算法中再嵌一层循环就是线性对数阶:

for (int j=1;j<=n;j++){
int i=1;
while (i <= n) {
i = i * 2;
}
}
分析了前面这些最常用的时间复杂度,其实我们可以得到以下规律:

只要是常量级别,不论多大,效率都是一样的(如:常量阶复杂度例子)。
分析一段代码的时间复杂度,只需要分析执行次数最多的一段代码(如:所以例子中我们只分析了循环体中代码执行次数)。
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积(如:分析线性对数阶复杂度例子)。

5.6 其他复杂度

除了上面常用的复杂度之外,另外还有指数阶,阶层阶,根号阶等,这些接触的相对会较少,我们就不特意做分析了,如果大家感兴趣的话,可以自己去了解下。

5.7 组合式复杂度分析

前面我们分析的都是只有一段代码比较复杂的情况下得到的复杂度结果,那么假如我一个算法中,有多段代码都比较复杂呢?这时候复杂度该如何分析?

取最大复杂度作为整个算法复杂度

我们先看下面这个例子:

public static void print1(int n){
for (int i=0;i<1000;i++){
System.out.println(i);
}
for (int j=0;j<n;j++){
System.out.println(j);
}

for (int p=0;p<n;p++){for (int q=0;q<n;q++){System.out.println(p+q);}
}

}
这个例子中有三个循环,首先第一个,是一个常量,那么根据前面的结论,不论这个常量是多大,都属于常量级,所以第一个循环中的复杂度为 O(1),第二个和第三个循环我们前面也分析过,复杂度分别为 O(n) 和 O(n²)。

也就是这一段代码中有三段代码产生了三种不同复杂度,而且这三个复杂度可以很明显得到的大小关系为:O(1)<O(n)<O(n²),像这种在同一个算法中有明确大小关系的,我们就可以直接取最大值作为这个算法的复杂度,所以这个例子中算法的复杂度就是 O(n²)。

取多个复杂度之和作为整个算法复杂度

接下来我们再来看一个例子:

public static void print2(int m,int n){
for (int i=0;i<1000;i++){
System.out.println(i);
}
for (int j=0;j<m;j++){
System.out.println(j);
}
for (int k=0;k<n;k++){
System.out.println(k);
}
}
这个例子我们同样对三段循环分别分析可以分别得到如下复杂度:O(1),O(m),O(n)。这时候我们只能知道 O(1) 最小可以忽略,但是后面两个无法却无法确定大小,所以这时候我们需要取两段循环复杂度之和来作为算法的复杂度,所以可以得到这个例子的算法复杂度为:O(m+n)。

6 时间复杂度类型

上面分析的时间复杂度都是比较简单的,实际算法中可能会比示例中复杂的多,而且我们示例中只要是循环都是无脑循环,也就是一定从头循环到尾,然而实际中我们有时候并不需要从头循环到尾,可能中途就会结束循环,所以我们根据实际情况,又可以将时间复杂度从以下四个方面来进一步分析:

最好时间复杂度
最坏时间复杂度
平均时间复杂度
均摊时间复杂度
这四种类型的时间复杂度在这里只会介绍前面三种,因为第四种比较复杂,而且使用场景也非常有限,而且对于这四种复杂度的分析,大家也作为了解就可以,不敢兴趣的朋友们可以跳过这一小部分,因为在绝大部分情况我们只需要分析最坏复杂度就行,也就是假设循环全部执行完毕场景下的时间复杂度。

6.1 最好时间复杂度

我们通过一个例子来理解下最好时间复杂度:

public static int findEle(int[] arr,int val){
if (null == arr || arr.length == 0){
return -1;
}
for (int i=0;i<arr.length;i++){
if (arr[i] == val){
return i;
}
}
return -1;
}
这个方法就是在一个指定数组中找到指定元素的下标,找不到就返回 -1,这个方法比较简单,应该比较好理解。

注意这个方法中的循环体,如果找到元素,那么就直接返回,这就会有一个现象,那就是我这个循环体到底会循环多少次是不确定的,可能是 1 次,也可能是 n(假设数组的长度) 次,所以假如我们要找的元素就在数组中的第一个位置,那么我循环一次就找到了,这个算法的复杂度就是 O(1),这就是最好情况时间复杂度。

6.2 最坏时间复杂度

理解了最好时间复杂度,那么最坏时间复杂度也很好理解了,那就是数组中不存在我要找到元素,或者说最后一个值才是我要找的元素,那么这样我就必须循环完整个数组,那么时间复杂度就是 O(n),这也就是最坏时间复杂度。

6.3 平均时间复杂度

最好时间复杂度和最坏时间复杂度毕竟只有特殊情况才会发生,概率还是相对较小,所以我们很容易就想到我们也需要有一个平均时间复杂度。

我们简单的来分析一下,为了便于分析,我们假设一个元素在数组和不在数组中的概率都为 1/2,然后假如在数组在,那么又假设元素出现在每个位置的概率也是一样的,也就是每个位置出现元素的概率为: 1/n。

所以最终得到的平均时间复杂度应该等于元素在数组中和元素不在数组中两种情况相加。

6.4 元素在数组中的复杂度

因为元素在数组中的概率为 1/2,然后在每个位置出现的概率也为 1/n。假如元素出现在第一个位置,复杂度为 1*(1/2n);假如元素出现在第二个位置,复杂度为 2 * (1/2n),最终得到当前场景下时间复杂度为:1*(1/2n) + 2 * (1/2n) + … + n*(1/2n)=(n+1)/4。

6.5 元素不在数组中的复杂度

前面已经假定了元素不在数组中的概率为 1/2,所以当前场景下的时间复杂度为:n * (1/2),因为元素不在数组中,那么这个算法必然会将整个循环执行完毕,也就循环是 n 次。

最后我们把两种情况的复杂度之和相加就得到了平均时间复杂度:(n+1)/4 + n/2 = (3n+1)/4,最终我们将常数类的系数忽略掉,就得到了平均时间复杂度为 O(n)。

6.7 均摊时间复杂度

均摊时间复杂度的算法需要使用摊还分析法,计算方式相对有点复杂,而且使用场景很有限,本文就不做过多介绍了。

7 空间复杂度

空间复杂度全称就是渐进空间复杂度,用来表示算法的存储空间与数据规模之间的增长关系。和时间复杂度一样,空间复杂度也是用大 O 进行表示。

其实学会了分析时间复杂度,那么空间复杂度的分析就简单了,主要就看我们在一个算法当中到底有没有使用到了额外的空间来进行存储数据,然后判断这个额外空间的大小会不会随着 n 的变化而变化,从而得到空间复杂度。

我们来看一个给数组赋值例子,假设这就是一个算法,我们可以来分析下这个算法的空间复杂度:

public static void init(int n){
int a = 0;
int arr[] = new int[n];
for (int i=0;i<n;i++){
arr[i]=n;
}
}
一开始定义了一个变量,这里需要空间,但是这是一个常量级的(不随 n 的变化而变化),然后再定义了一个数组,数组的长度为 n,这里数组也需要占用空间,而且数组的空间是随着 n 的变化而变化的,其余代码没有占用额外空间,所以我们就可以认为上面示例中的空间复杂度为 O(n)。

对于算法的空间复杂度也可以简单的进行总结一下:

如果申请的是有限个数(常量)的变量,空间复杂度为 O(1)。
如果申请的是一维数组,队列或者链表等,那么空间复杂度为 O(n)。
如果申请的是二维数组,那么空间复杂度为 O(n²)。
如果是在循环体中申请的数组等,可能就需要取嵌套的乘积来作为空间复杂度,这种就需要具体的进一步分析。

8 总结

本文主要讲述了为什么要学习算法,也简单减少了数据结构与算法之间的关系,随后主要介绍了算法中的入门知识:时间复杂度和空间复杂度。想要学好算法,必须要掌握如何分析一个算法的时间复杂度和空间复杂度,只有自己会分析这两个个衡量算法主要性能的标准,才能更好的写出性能优秀的算法,同时我们也讲到了最好时间复杂度,最坏时间复杂度,平均时间复杂度和均摊时间复杂度,不过这四种复杂度的计算方式大家作为了解即可,等实际确实需要使用到再来回顾也不迟。

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

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

相关文章

【解锁技能】学会Python条件语句的终极指南!

文章目录前言一. python条件语句的介绍1.1 什么是条件语句1.2 条件语句的语法1.3 关于内置函数bool()二. 分支语句之单分支三. 多分支语句3.1 二分支语句3.2 多分支语句3.3 嵌套循环总结前言 &#x1f3e0;个人主页&#xff1a;欢迎访问 沐风晓月的博客 &#x1f9d1;个人简介&…

EPICS synApps介绍

一、synApps是什么&#xff1f; 1&#xff09; 一个用于同步束线用户的EPICS模块集合。 2&#xff09; EPICS模块 alive, autosave, busy, calc, camac, caputRecorder, dac128V, delaygen, dxp, ip, ip330, ipUnidig, love, mca, measComp, modbus, motor, optics, quadEM,…

【蓝桥杯选拔赛真题38】python目标值判断 青少年组蓝桥杯python 选拔赛STEMA比赛真题解析

目录 python目标值判断 一、题目要求 1、编程实现 2、输入输出 二、解题思路

【牛客刷题专栏】0x0E:JZ6 从尾到头打印链表(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录前言问题…

CVE-2022-42889 Apache Commons Text 漏洞

0x00 前言 所幸遇到&#xff0c;就简单看看&#xff0c;其中没有啥比较难的地方&#xff0c;仅做记录。10月13日的漏洞。 cve链接可以看下面这个&#xff1a; https://cve.mitre.org/cgi-bin/cvename.cgi?nameCVE-2022-42889 git地址&#xff1a; https://github.com/apache…

前端进阶JS运行原理

JS运行原理 深入了解V8引擎原理 浏览器内核是由两部分组成的&#xff0c;以webkit为例&#xff1a; WebCore&#xff1a;负责HTML解析、布局、渲染等等相关的工作&#xff1b;JavaScriptCore&#xff1a;解析、执行JavaScript代码&#xff1b; 官方对V8引擎的定义&#xff1…

汇编指令学习(MOV,MOVSX,MOVZX,LEA,XCHG)

一、MOV指令1、将十六进制0x1234数值&#xff0c;赋值给eax寄存器mov eax,0x12342、将十六进制0x123数值&#xff0c;赋值给内存地址为ebxmov dword [ebx],0x1233、将edx的高八位赋值给eax的低八位ax&#xff0c;eax的低16位&#xff0c;al&#xff0c;eax的低8位&#xff0c;a…

(三)随处可见的LED广告屏是怎么工作的呢?接入GUI

续上文&#xff0c;本篇我们将尝试接入一个GUI来控制点阵屏。在前两篇中&#xff0c;我们相继介绍了点阵屏的控制原理&#xff0c;以及如何让点阵屏按照我们所想的进行显示。本篇将在此基础上接入一个GUI&#xff0c;使点阵屏的控制更加优雅。限于阅读体验和展示效果&#xff0…

深入理解border以及应用

深入border属性以及应用&#x1f44f;&#x1f44f; border这个属性在开发过程中很常用&#xff0c;常常用它来作为边界的。但是大家真的了解border吗&#xff1f;以及它的形状是什么样子的。 我们先来看这样一段代码&#xff1a;&#x1f44f; <!--* Author: syk 185901…

k8s环境jenkins发布vue项目指定nodejs版本

k8s环境jenkins发布vue项目指定nodejs版本1、背景2、分析3、解决方法3.1、 找到配置镜像位置3.2、 制作新镜像3.3、 推送镜像到私有仓库3.4、 修改配置文件1、背景 发布一个前端项目&#xff0c;它需要nodejs 16.9.0版本支持&#xff0c;而kubesphere 3.2.0集成的jenkins 的镜…

小米mix2s刷win11和android双系统

在给电脑安装系统的过程中&#xff0c;可能会因为各种原因出现windows无法安装的情况&#xff0c;我在给小米mix2s安装win11时发现出现了“计算机意外地重新启动或遇到错误&#xff0c;windows无法安装”的情况&#xff0c;下面就来教一下大家两种解决方法&#xff0c;希望可以…

【解决办法】windows防火墙出入站规则放通telnet方法

【操作方法】windows防火墙出站规则放通telnet方法一、出站规则1.新建出站规则中选择“程序”2.选择路径&#xff0c;点击“下一页”3.选择“允许连接”4.选择所有区域二、入站规则注&#xff1a;打开防火墙添加出入站规则参考【操作方法】windows防火墙添加出入站规则方法 一、…

JUC(二)

1.可重入锁–ReentrantLock原理 1.1.非公平锁的实现原理 1.1.1.加锁解锁流程 1>.先从构造器开始看,默认为非公平锁,可以在构造函数中设置参数指定公平锁 public ReentrantLock() {sync = new NonfairSync(); }public ReentrantLock

【C++】STL 模拟实现之 list

文章目录一、list 的常用接口及其使用1、list 一般接口2、list 特殊接口3、list 排序的性能分析二、list 迭代器的实现1、迭代器的分类2、list 迭代器失效问题3、list 迭代器源码分析4、list 迭代器模拟实现4.1 普通迭代器4.2 const 迭代器4.3 完整版迭代器三、list 的模拟实现…

十分钟学习nfs服务器

NFS服务器简介 NFS的使用 权限参数 简易实验配置一&#xff1a; 要求&#xff1a;客户端借用nfs服务器可以同步服务端的文件 步骤&#xff1a;服务端配置&#xff08;/var/lib/nfs日志存放目录&#xff09; 创建文件&#xff1a;&#xff08;主配置文件有可能存在&#x…

机器学习的特征归一化Normalization

为什么需要做归一化&#xff1f; 为了消除数据特征之间的量纲影响&#xff0c;就需要对特征进行归一化处理&#xff0c;使得不同指标之间具有可比性。对特征归一化可以将所有特征都统一到一个大致相同的数值区间内。 为了后⾯数据处理的⽅便&#xff0c;归⼀化可以避免⼀些不…

spring boot 配合element ui vue实现表格的批量删除(前后端详细教学,简单易懂,有手就行)

目录 一.前言&#xff1a; 二. 前端代码&#xff1a; 2.1.element ui组件代码 2.2删除按钮 2.3.data 2.4.methods 三.后端代码&#xff1a; 一.前言&#xff1a; 研究了其他人的博客&#xff0c;找到了一篇有含金量的&#xff0c;进行了部分改写实现前后端分离&#xff0…

频率信号转电压或电流信号隔离变送器0-1KHz /0-5KHz /0-10KHz转0-2.5V/0-5V/0-20mA

主要特性:>> 精度等级&#xff1a;0.2级>> 全量程内极高的线性度&#xff08;非线性度<0.1%&#xff09; >> 辅助电源/信号输入/信号输出&#xff1a; 2500VDC 三隔离>> 辅助电源&#xff1a;5VDC&#xff0c;12VDC&#xff0c;24VDC等单电源供电&g…

SpringBoot项目启动成功后打印Banner

SpringBoot项目启动成功后打印Banner 背景 可能有些同学看到就觉得&#xff0c;这个都要发文章&#xff1f;这不是整个banner.txt再配置一下spring.banner.locationclasspath:banner.txt就行了吗&#xff1f;还真不是&#xff0c;这个是在项目启动时&#xff0c;先打印的bann…

Big_Data

Linux 计算机硬件软件体系 冯 诺依曼体系结构 计算机处理的数据和指令一律用二进制数表示 顺序执行程序 计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成计算机硬件组成 输入设备输入设备用来将人们熟悉的信息形式转换为机器能够识别的信息形式常见的…