教材《数据结构与算法第二版》
关键字【连续空间、长度不变】
数组是内存空间的一个连续空间,你肯定见过内存条,你可以想象你的数组存放在内存条的某一段上,特点是连续空间通过下标(index)去管理位置。
例如,下面表示,声明了一个数组,长度为50,里面的元素类型是 int 类型。而且,需要注意,数组的声明当然还有其他声明方式,但不是这里的学习重点。
int[] ints = new int[50]
数据结构-数组的特点是一串连续的空间,例如,上面的数组表示,快递上门派件,街道旁边有一排房子,分别是0-49号(对,因为数组下标是从0开始的),要找到其中的某个主人Bob可能很慢,需要挨家挨户核对主人名字才行。所以派件都会直接核对门牌号,达到最快地投放快件,因为门牌号和下标一样,是这条街道上区分房子的最显著标志。这就是连续空间的好处。如果看到49号后面没有了,我们就不会再找下去了(事实上也找不下去了,因为街道已经没了,这个街道的最大长度就是50)。
大O表示法(时间复杂度),忽略机器性能,忽略编译执行的效率,只关注 数据量对时间复杂度的影响。比如,快递员派件,走到1号和走到49号房子,走到任何一个房子的时间复杂度是一致的,都是1,派两家的件的时间复杂度为2。给每家都派件(遍历)的时间复杂度是O(N),N表示了有多少家房子。
插入:
无序数组的插入是最快的,直接在数组的末尾插入元素就可以了。新元素插入在头部的无序数组数组很少,也不会有人去这么干,为什么呢,接着看下去就知道了。使用大O表示法为 O(1) ,简单粗暴,找到最后一个元素的屁股,强势插入!
有序数组就没这么简单了,要求插入之前,先要找到新元素逻辑上应该在的位置,比如有一串数组,长度为26,下标是[0-25],里面预先存放了['a','b','d',_,_,_......] 类型是char,方便编辑,char直接使用字母,不用单引号表示。这个数组已经存放了3个元素了,我要插入新元素c ,因为有序数组的条件,我必须按照英文字母排序方式去放置c,把c和数组里已有的有序内容进行比较,就知道c应该在b和d之间(这个比较的过程,计算机会把c和每个元素比较,比前一个大,比后一个小,就是c的位置了。),好了,可以直接插入吗,肯定是不可以的。数组需要把后面的元素妥善安置一下,所以,d被向后移动一个下标,到它的新家-下标为3的内存区域,d原来的位置-下标2的内存区域就放置了c。这样:[a,b,c,d,_,_...]就完成了有序数组的插入。
关键点来了:有序数组的插入会经过 :-比较——移动——插入-,三个步骤完成。上面的例子只是移动了一个d,如果d后面还有f-z这么多元素的,那就表示右面的所有元素都要移动,这个操作就导致了有序数组的插入效率是很差的,因为“移坑”。
删除:
删除——无论是有序数组,还是无序数组,非末尾的删除,都会导致中间某个index里面的元素不存在,那么后面的元素也需要逐个向前移动,填补这个位置,我称之为“填坑”。我曾经问我自己,就不能空着吗?然后我给了自己一耳光。
遍历:
无论是有序数组还是无序数组,遍历都非常快,因为数组的连续空间,遍历只需要按照下标,挨家挨户敲门就可以了。
总结:
数组是存放数据常见的格式。数组更多适用于读取,无序数组的插入是最快的。有序数组的插入,无序/有序的删除都涉及到后续数据的填坑,所以插入删除效率不高。
下一章是关于数组的排序方法。
课外题:
如果声明了数组后,下标没有放置元素进入,这些下标里有什么呢null,空,0?如果你不确定,动手复制main函数运行一下就知道了。结合java数据类型的初始化,相信这是很简单的。
public static void main (String[] args){
int[] ints = new int[5];
System.out.println("int数组初始化后,元素为:" + ints[4]);
char[] ars = new char[26];
System.out.println("char数组初始化后,元素为:"+ars[4]+"....");
String[] strs = new String[5];
System.out.println("String数组初始化后,元素为:"+strs[4]);
boolean[] boos = new boolean[5];
System.out.println("String数组初始化后,元素为:"+boos[4]);
}