数组这种数据结构的基本实现
Contents
1 为什么数组元素下标从0开始?
回答这个问题之前,先了解一下数组的特性:
首先,数组这种数据类型,在内存中是申请一片连续的存储空间。比如:如果每个数组元素占据1MB内从空间,申请长度为1000的数组,则内存中必须得有连续的1000MB的可用空间,否则数组分配空间申请失败。对比来看,如果申请的是一个链表的话,只要内存中的可用空间有1000MB即可,并不要求这些存储空间一定是连续的。
正是因为这种要求内存空间是连续的性质,就衍生出数组这种数据类型可以根据元素的索引下标快速定位元素,且其时间复杂度是O(1)。这是怎么实现的呢?因为内存连续,假定第1个元素内存地址为1000,每个元素占4个字节,那么,现在我想要访问第100个元素的话,则可以快速计算出内存地址=首元素地址+(100-1)*4=1396。更进一步,如果规定索引下标从0开始,那么要访问第100个元素的话,其实是要获取第99个元素。此时,其内存地址=首元素地址+99X4=1396。相比较而言,CPU就少了一次减法运算。
其次,数组这种数据类型是单一数据类型,长度固定,不可动态扩展长度。空间开辟小,则不够用,无法扩展,空间开辟大,用不完怎么办,空间浪费。
2 数组不仅是一种数据类型,还是一种数据结构
是的,这是在之前并没有认真认识到的一种数据结构,原来数组本身就是一种数据结构,还是一种非常常用的基础数据结构。
3 自己手写一个数组类
/** * @Author:asher * @Date:2021/3/26 20:28 * @Description:PACKAGE_NAME * @Version:1.0 */ //自己动手写一个Array类,让其封装一个整型数组 public class Array { //封装一个int[]数组的成员变量 private int[] data; //表示数组实际存放的元素个数 private int size; //构造方法,传入一个整型长度capacity,初始化一个数组 public Array(int capacity) { data = new int[capacity]; //初始化时,数组分配capacity个存储空间,但是没有存放任何元素,是空数组,size=0; size = 0; } //默认构造方法,初始化长度为10的数组 public Array() { this(10); } //封装一个获取数组实际存放多少个元素的方法 public int getSize() { return size; } //封装一个获取数组总共可以存放多少个元素的方法 public int getCapacity() { return data.length; } //封装一个判断数组是否为空的方法 public boolean isEmpty() { return size == 0; } }
4 向数组中添加元素
a 向数组末尾添加元素:
前面,我们在自定义数组类的时候,指定了一个非常好用的成员变量,size,它不但表示当前数组中有几个元素,而且还指向数组中第一个元素为空的位置。比如,当size=3的时候,数组中,此时有3个元素,分别是data[0],data[1],data[2],同时data[3]是空的,该索引位置处没有元素。当我们此时,向数组中添加元素的时候,我们就应该把新元素放到data[3]的位置。所以,我们可以定义一个向数组末尾添加元素的方法:
public void addLast(int e){ //如果数组的实际存放元素个数==数组长度,抛出异常 if( size == data.length){ throw new IllegalArgumentException("addLast failed.Array is full"); } //否则,将元素e插入到size位置处,同时,size++; data[size] = e; size++; }
b 向任意位置添加元素:
public void add(int e,int index){ //判断数组是否满 if(size==data.length){ throw new IllegalArgumentException("数组满,add failed."); } //判断插入位置index是否合法 if(index<0||index>=data.length){ throw new IllegalArgumentException("插入位置不合法"); } //如果插入位置在index取值范围在[0,size]之间,意味着插入位置合法。 //则先将[index,size-1]位置处的元素全部后移一位 if(index<=size){ for(int i=size;i>index;i--){ data[size]=data[size-1]; } //然后空出index位置,将元素e插入到index位置处,并且size++ //其实,此时i=index,只是离开了变量作用域 data[index]=e; size++; }else{ //插入位置在[size+1,length-1],也认为是插入位置不合法,或者此时直接将元素放到size位置处 data[size]=e; size++; } }
c 向数组首位置插入元素
有了向数组任意位置插入元素的add(int e,int index)的方法,则可以借用此方法,实现一个向数组首位置插入元素的方法,只需要传入一个位置参数0即可,add(int e,0)
public void addFirst(int e){ add(e,0); }
d 改造向数组末尾添加元素
同时,我们也可以借助add(int e,int index)任意位置插入元素的方法,进而改造。只需,传入位置参数size即可,addLast(int e,size)
e 覆写父类Object的toString()
Java中,任何基本数据类型在打印输出时,编译器会自动将其转化成字符串打印输出。而对于自定义类类型的实例变量,在打印输出时,就需要覆写Object类的toString()方法,否则打印出来的将是该实例变量的内存地址值。其格式大概是:类名@实例变量内存地址。因此,对于我们这个自定义的Array类,我们需要重写toString()。
@Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(String.format("Array size: %d,capacity = %d \n", size, data.length)); stringBuilder.append("["); //注意,此时遍历的时候,只能到达size位置处,因为可能数组开辟了100个空间的capacity,但是,数组实际存放了size=10个元素 for (int i = 0; i < size; i++) { stringBuilder.append(data[i]); //如果没有到达最后一个元素时,则添加一个"空格+,+空格" if (i != size-1) { stringBuilder.append(" , "); } } stringBuilder.append("]"); return stringBuilder.toString(); }
f 测试addLast(int e),add(int e,int index)和addFirst(int e)方法
有了toString()方法,我们就可以定义一个main()方法,来分别测试这几个方法。
public static void main(String[] args) { Array array = new Array(20); for (int i = 0; i < 10; i++) { array.addLast(i); } System.out.println(array); array.add(111, 1); System.out.println(array); array.addFirst(-1); System.out.println(array); } //输出结果为 Array size: 10,capacity = 20 [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 11,capacity = 20 [0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 12,capacity = 20 [-1 , 0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9]
5 按元素值查找并返回索引下标
//从数组中查找值等于e的元素,如果找到返回其索引位置,未找到,返回-1 public int find(int e) { for (int i = 0; i < size; i++) { if (e == data[i]) { return i; } } return -1; }
6 按元素值修改元素
//如果数组中存在元素值等于e的元素,则修改它的值为newValue,否则不做任何操作 // 修改数组中元素值等于e的元素为newValue,如果 public void modify(int e, int newValue) { int temp = find(e); if (temp != -1) { data[temp] = newValue; } }
7 测试按元素值查找和修改元素
public static void main(String[] args) { Array array = new Array(20); for (int i = 0; i < 10; i++) { array.addLast(i); } System.out.println(array); array.add(111, 1); System.out.println(array); array.addFirst(-1); System.out.println(array); System.out.println("The index of element 1 is:"+array.find(1)); System.out.println("The index of element 18 is:"+array.find(18)); array.modify(9, 999); System.out.println("After modify element 9 to 999:"); System.out.println(array); } //结果 Array size: 10,capacity = 20 [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 11,capacity = 20 [0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 12,capacity = 20 [-1 , 0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] The index of element 1 is:3 The index of element 18 is:-1 After modify element 9 to 999: Array size: 12,capacity = 20 [-1 , 0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 999]
8 按索引下标查找、修改元素
//获取索引位置为index的元素 public int get(int index) { //如果index<0,或者数组不为空时,index>=size if (index < 0 || (isEmpty() && index >= size)) { throw new IllegalArgumentException("get failed,index is invalid."); } //否则的话,返回该元素 return data[index]; } //获取第一个位置元素 public int getFirst(){ return get(0); } //获取最后一个位置元素 public int getLast(){ return get(size-1); } //修改索引位置为index的元素 public void set(int index,int e) { //如果index<0,或者数组不为空时,index>=size if (index < 0 || (isEmpty() && index >= size)) { throw new IllegalArgumentException("get failed,index is invalid."); } //否则的话,直接修改元素 data[index] = e; }
测试:
public static void main(String[] args) { Array array = new Array(20); System.out.println(array.get(0)); for (int i = 0; i < 10; i++) { array.addLast(i); } System.out.println(array); array.add(111, 1); System.out.println(array); array.addFirst(-1); System.out.println(array); System.out.println("The index of element 1 is:"+array.find(1)); System.out.println("The index of element 18 is:"+array.find(18)); array.modify(9, 999); System.out.println("After modify element 9 to 999:"); System.out.println(array); //测试修改索引下标为3的元素为333 array.set(3, 333); System.out.println(array); } //输出 Array size: 10,capacity = 20 [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 11,capacity = 20 [0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Array size: 12,capacity = 20 [-1 , 0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] The index of element 1 is:3 The index of element 18 is:-1 After modify element 9 to 999: Array size: 12,capacity = 20 [-1 , 0 , 111 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 999] Array size: 12,capacity = 20 [-1 , 0 , 111 , 333 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 999] //不注释System.out.println(array.get(0));的话, 按预期的抛出异常: Exception in thread "main" java.lang.IllegalArgumentException: get failed,index is invalid. at Array.get(Array.java:100) at Array.main(Array.java:135)
9 判断数组中是否包含元素e
// 判断数组中是否包含元素e public boolean contains(int e){ for(int i=0;i<size;i++){ if(data[i]==e){ return true; } } return false; }
10 按索引下标删除数组中的元素并返回
public int remove(int index){ if(index<0||index>=size){ throw new IllegalArgumentException("remove failed.index is invalid"); } //删除之前,把data[index]放入临时变量,删除之后,再返回临时变量 int temp=data[index]; for(int i=index;i+1<size;i++){ data[i]=data[i+1]; } size--; return temp; } //同理,参照add()方法,有addFist()和addLast(),可以有removeFirst(),removeLast()方法 //删除第1个元素并返回 public int removeFirst() { return remove(0); } //删除最后1个位置元素并返回 public int removeLast() { return remove(size - 1); }
11 按元素值删除数组中的元素
借助前面定义的查找方法find(),按索引下标删除的方法remove(),是否包含元素的方法contains(),可以实现删除特定元素的方法,以及删除数组中所有的特定元素。
//删除指定元素e public void removeElement(int e) { int index = find(e); if (index != -1) { remove(index); } } //删除所有的指定元素e public void removeAllElement(int e) { //如果数组中包含特定元素,就一直删 while (contains(e)) { //删除哪个元素呢?删除特定索引位置的元素,这个特定索引位置通过find()方法得到 remove(find(e)); } }
12小结
我们通过自己动手写了一个数组类,并添加了增删改查的方法。这个数组还有一些局限性,比如不能支持任意类型的数据,长度不支持动态扩展。下一篇文章中着手将这个数组改写为支持泛型的数组。
2条评论
Pingback:
Pingback: