目录
数组对于初学者来说使用波频率也挻高的,所以有必须简单说一下。
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。
一、java中的数组
java中的数组是默认是无序的,所谓的无序是说其值保存是没有规律性,无序的
数组(Array)用一组连续的内存空间来存储一组具有相同类型的数据。
比如下图的一组数组
二、通过操作分析数组的优缺点
比如我们下载建立一个数组,是关于菜类的
String[] arr= {"生菜","油麦菜","大白菜","小白菜","芹菜"};
它们的内存分配如下图所示:因为String是对象放在堆中,栈只保存首地址
为了方便理解我就把图简单弄成下面这样的:后来都是用这个简图来表示
我们对以下4种操作进行分析
读取:查看数据结构中某一位置上的数据。对于数组来说,这意味着查看某个索引所指的数据值。例如,查看索引2上有什么菜,就是一种读取。
查找:从数据结构中找出某个数据值的所在。对于数组来说,这意味着检查其是否包含某个值,如果包含,那么还得给出其索引。例如,检查" 小白菜"是否存在于菜清单之中,给出其对应的索引,就是-一种查找。
插入:给数据结构增加一个数据值。对于数组来说,这意味着多加一个格子并填入一个值。例如,往购物清单中多加一项"菠菜", 就是一种插入。
删除:从数据结构中移走-一个数据值。对于数组来说,这意味着把数组中的某个数据项移走。例如,把购物清单中的"大白菜"移走,就是一种删除。
本章我们]将会研究这些操作在数组上的运行速度。同时,我们也将学到第一个重要理论:
操作的速度,并不按时间计算,而是按步数计算。
为什么呢?
因为,你不可能很绝对地说,某项操作要花5秒。它在某台机器上要跑5秒,但换到一台旧一点的机器,可能就要多于5秒,而换到一台未来的超级计算机,运行时间又将显著缩短。所以,受硬件影响的计时方法,非常不可靠。
然而,若按步数来算,则确切得多。如果A操作要5步,B操作要500步,那么我们可以很肯定地说,无论是在什么样的硬件上对比,A都快过B。因此,衡量步数是分析速度的关键。
此外,操作的速度,也常被称为时间复杂度。在算法中我们提到速度、时间复杂度、效率、性能,但它们其实指的都是步数。
事不宜迟,我们现在就来探索上述4种操作方式在数组上要花多少步。
2.1 读取
读取是按下标读的,一步就够了,知道索引号,就可以基于开始地址进行计算,只计算一次就够了。
例如:菜清单数组的索引和内存地址,如下图所示。
如果我们要找“小白菜”,知道它的索引号为3,直接到arr[3]的值就是内存首地址,再通过内存首地址读String对象的值就行了
2.2 查找
还是找“小白菜”,如果按查找的方式,那就得从索引0开始逐个读取值并进行判断是否是“小白菜”
索引0的值不是想要的,下一个索引1上
索引1的值也不是想要的,下一个索引2上
索引2的值也不是想要的,下一个索引3上
.
索引3 找到了,是想要的,计算机不用再往后跳了,因为结果已经得到。
在这个例子中,因为我们检查了4 个格子才找到想要的值,所以这次操作总计是4 步。
这种逐个格子去检查的做法,就是最基本的查找方法——线性查找。
但在那之前,我们再思考一下,在数组上进行线性查找最多要多少步呢?
如果我们要找的值刚好在数组的最后一个格子里(如本例的“芹菜”),那么计算机从头到尾检查每个格子,会在最后才找到。同样,如果我们要找的值并不存在于数组中,那么计算机也还是得查遍每个格子,才能确定这个值不在数组中。
于是,一个5 格的数组,其线性查找的步数最大值是5,而对于一个500 格的数组,则是500。
以此类推,一个N 格的数组,其线性查找的最多步数是N(N 可以是任何自然数)
可见,无论是多长的数组,查找都比读取要慢,因为读取永远都只需要一步,而查找却可能需要多步。
2.3 插入
往数组里插入一个新元素的速度,取决于你想把它插入到哪个位置上。
假设我们想要在购物清单的末尾插入"包菜"。那么只需一步。因为之前说过了,计算机知道数组开头的内存地址,也知道数组包含多少个元素,所以可以算出要插入的内存地址,然后一步跳到那里插入就行了。图示如下。
但在数组开头或中间插入,就另当别论了。这种情况下,我们需要移动其他元素以腾出空间,于是得花费额外的步数,如果字符数组不够的话,还得重新写一个数组,也可以使用动态数据List,list对象会重新申请一块大小是原来容量的两倍的内存空间,然后将当前所有元素以及待添加元素复制到新的内存空间中。
如上所示,整个过程有4 步,开始3 步都是在移动数据,剩下1 步才是真正的插入数据。
最低效(花费最多步数)的插入是插入在数组开头。因为这时候需要把数组所有的元素都往右移。
于是,一个含有N 个元素的数组,其插入数据的最坏情况会花费N + 1 步。即插入在数组开头,导致N 次移动,加上一次插入
2.4 删除
最后要说的“删除”,则相当于插入的反向操作。
数组的删除就是消掉其某个索引上的数据。
我们找回最开始的那个数组,删除索引2 上的值,即"大白菜"。
结果,整个删除操作花了3 步。其中第1 步是真正的删除,剩下的2 步是移数据去填空格。
所以,删除本身只需要1 步,但接下来需要额外的步骤将数据左移以填补删除所带来的空隙。
跟插入一样,删除的最坏情况就是删掉数组的第一个元素。因为数组不允许空元素,当索引0 空出,那么剩下的所有元素都要往左移去填空。
对于含有5 个元素的数组,删除第一个元素需要1 步,左移剩余的元素需要4 步。而对于500个元素的数组,删除第一个元素需要1 步,左移剩余的元素需要499 步。可以推出,对于含有N个元素的数组,删除操作最多需要N 步。
三、总结
数组在以下标随机读取的速度是很快的,但是查找、删除、插入相对是很慢的,数组越长越慢!
所以数组适合放数据以下标方式读取,不适合做查找、删除、插入操作。