今天,让我们来谈谈数组.说起数组,你肯定是不会陌生的.数组可以用一段连续的空间存储相同类型的数据.我相信这还是没有学习数据结构时的同学的理解.当然这样的理解是完全正确的,不过还不够深入,我们还要想想我们现在如何更深入地去理解数组.
其实呢,一段数组是相对封闭的,请看上图,比如我们申请了一段内存,我们此时是只知道这段我们申请的内存,这段内存的前面或者后面是否存在被申请的内存?存了什么?我们都是一概不知的.并且内存也是相对固定的空间.正是数组基于其相对封闭与相对固定的两种特性.我们每次在使用数组时,就必须需要告诉"内存管理员"我们要到底多少空间.假设一下,如果事先不告诉需要的空间大小(不写大小放元素本质上也是在说明空间的大小),内存不够了就会占用那片连存不存在其他数据都不知道的空间.如果不存在其他数据还好,但如果存在呢???你让被占用空间的数据情何以堪?所以这种情况是禁止发生的.这时你可能就会拿出各种语言中的容器来反驳我了,或者可能会猜到容器内有实现这样功能的技术细节.
而这正好让我引出动态数组的概念,容器似乎可以自动地变化其空间大小,但更准确地说容器是支持动态扩容.本篇的目的就是我们自己去手动实现这样的一个容器.当然我不单单只是实现动态扩容这一特性,而是实现一个具备常用功能的容器.相信如果你自己在手动实现这样的容器之后,就更加清楚地认识到了数组的现在.
先在就让我们开始动手吧!!!
当然在这里,我使用了Java语言来讲解.
如下面的代码所示,使用了泛型,以保证我们的容器是具有普遍性,data 代表一个泛型数组,size代表数据元素的实际个数,并且两个变量是私有的,以保证其安全性.
public class Array <T>{
private T[] data;
private int size;
在这里,我只会说说那些有点理解难度的方法,至于那些看代码就能理解的方法,我就不细讲了,但我会直接提供给你有注释的代码去理解.
先说说很基本的两大操作,一个是添加元素,一个删除元素
先上添加元素的代码
public void add(int index,T value)// index指的数组的索引
{
if(index<0||index>size)
{
throw newIllethrow new IllegalArgumentException("The index is illegal");
}
if(data.length==size)//data.length指的是数组的长度,也就是最多能放多少元素.
{
throw new IllegalArgumentException("The array is full");
}
else
{
//数据搬移
for(int i=size-1;i>=index;i++)
{
data[i+1]=data[i];
}
//添加元素
data[index]=value;
//维护size
size++;
}
}
首先,面对添加操作,我们要想不能够添加的特殊情况,一种是输入的index不合法的情况,另一种是空间已经占满的情况.先说前者,请你对照这代段代码理解,你会发现这么写就是考虑index是否会越界,再说后者,上面的data.length指的是数组的长度,也就是最多能放多少元素,当元素的实际个数size等于数组的长度,就证明整个数组已经被塞满了,再放就放不进了.如果这两种情况都不是的话,就要开始真正的添加操作了.再次之前为了便于理解我画了下面这张图.(真的丑..)
你会发现,在图中我它当作一个索引来理解,这样理解是非常有用的.比如我们在要将在index=0,的位置插入元素,我们就必须先将元素进行往后搬迁的操作 .现在请你想象4中的元素往size位置移动,3中的元素往4移动.....最后0中的元素往1的空间中移动.好了,这个过程就是整个数据搬移的操作了.现在我们就需要将我们刚刚想象的画面转化成为计算机能够听得懂的语言了了.(我的建议是先写for中元素移动的语句data[i+1]=data[i],这样就更好写判断终止条件了).最开始是从最后4中的元素向后移动所以设置i=size-1(在这里将size当作索引是不是很好理解了呢?),在这里的话size-1=4.然后就是一个相对最麻烦的问题了,就在for循环中该填怎样的判断条件?我们要想在最后移动的必然是index中元素,所以写出i>=index.总结一下,如果我们想要准确地写出for循环就是要看最后元素的移动过程和index中的元素的移动过程。最后别忘记一件看似容易却老是忘记的话- size++,这样就能维持size的大小了.
学完添加,再学移除操作就变得非常地容易了.
public T remove(int index)
{
if(index<0||index>=index)
{
throw new IllegalArgumentException("the index is illegal");
}
T tem=data[index];//暂存元素
for(int i=index+1;i<size;i++)
{
data[i-1]=data[i];
}
size--;
return tem;
}
在remove中只需要考虑index是否合法就行了,也许你看见index>=size,可能会感觉size=index有点怪怪的,其实回到刚刚的图,就会发现好理解了,size是有对应的元素的,当index=size,index对应的空间也没有元素,如果要去删除就没有意义了.
在写for语句时,仍然建议先写元素移动的语句a[i-1]=a[i],最开始是index+1位置中的元素要往index的空间移动,所以i=index+1.再考虑最后的元素来写出终止判断条件,因为是size-1的元素要往前移动,所以是i<size(注意这里取不到size,因为size没有对应要移动的元素),最后别忘了size--.
讲到这里,你可能已经发现了,这里添加与删除操作还没有实现动态扩容的功能.现在就让我们来实现这样的功能.
还记得前面吗?,我讲过数组是相对固定存的,那就无法直接改变数组的大小.那还有什么办法呢?)相信你已经想到了(现在配合这下面的三张图来理解吧),我们只需要另外申请一块内存空间,并且将原本数组(data)的元素放进新的空间(newData),最后再将data改为指向newDate的引用.原本的空间会被Java的自动垃圾回收机制清理掉,而newData的声明周期很短,随着方法的结束而消失,但data有更长的生命周期,仍然存在.
具体代码实现如下
public void resize(int capacity)
{
T[] newData=(T[])new Object[capacity];
for(int i=0;i<size;i++)
{
newData[i]=data[i];
}
data=newData;
}
有了resize()方法就可以被添加方法所调用了.
在添加操作中,考虑空间已满时,就不再抛出异常了,而是用resize()去解决空间已满的问题了.注意扩容的大小应当是相对的而非是绝对,也就是说capacity不是一个绝对的值,因为太多空间就会浪费,太少又不够存储数据.所以capacity大小是相对于data.length而言的.在下面的代码中我让capacity=data.length*1.5(扩容了1.5倍),具体扩容多少倍是取决于你自己的,当然这里的1.5还是很合理的,毕竟在Java中的容器也是选择扩容1.5倍的.
public void add(int index,T value)// index指的数组的索引
{
if(index<0||index>size)
{
throw newIllethrow new IllegalArgumentException("The index is illegal");
}
if(data.length==size)//data.length指的是数组的长度,也就是最多能放多少元素.
{
resize(data.length*1.5);
}
else
{
//数据搬移
for(int i=size-1;i>=index;i++)
{
data[i+1]=data[i];
}
//添加元素
data[index]=value;
//维护size
size++;
}
}
同理我们也可以利用resize()为删除操作缩小空间.
public T remove(int index)
{
if(index<0||index>=index)
{
throw new IllegalArgumentException("the index is illegal");
}
T tem=data[index];
for(int i=index+1;i<size;i++)
{
data[i-1]=data[i];
}
size--;
if(size==data.length/2)//数组中的元素只有数组长度的一半了
{
resize(data.length/2);
}
return tem;
}
接下来就是些看代码就会的方法了
public Array(int capacity)
{
data=(T[])new Object[capacity];//设置数组大小
size=0;//最开始没有元素
}
public boolean isEmpty()//判断数组是否为空
{
if(size==data.length)
{
return true;
}
else
{
return false;
}
}
public void set(int index,T value)//再目标位置改为新元素
{
if(index<0||index>=size)//size对应的空间无元素
{
throw new IllegalArgumentException("the index is illegal");
}
else
{
data[index]=value;
}
}
public boolean contains(T value)//看目标元素是否被包含
{
for(int i=0;i<size;i++)
{
if(data[i]==value)
{
return true;
}
else
{
return false;
}
}
}
public int findByElement (T value)//通过元素找索引
{
for(int i=0;i<size;i++)
{
if(data[i]==value)
{
return i;
}
}
}
public T findByIndex(int index)//通过索引找元素
{
if(index<0||index>=0)
{
throw new IllegalArgumentException("the index is illegal");
}
else
{
return data[index];
}
}
好了,关于数组我相信你已经有了更全面的认识了,记得后面自己手动去实现一遍,最后欢迎在评论区留言,咱们后面见!