Java数据结构—顺序表
一、顺序表简介
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构(逻辑上也是依次存储),一般情况下采用数组存储。在数组上完成数据的增删查改。
具体要实现接口中的以下方法
public interface IList {
// 新增元素,默认在数组最后新增
public void add(int data);
// 在 pos 位置新增元素
public void add(int pos, int data) ;
// 判定是否包含某个元素
public boolean contains(int toFind) ;
// 查找某个元素对应的位置
public int indexOf(int toFind) ;
// 获取 pos 位置的元素
public int get(int pos) ;
// 给 pos 位置的元素设为 value
public void set(int pos, int value) ;
//删除第一次出现的关键字key
public void remove(int toRemove) ;
// 获取顺序表长度
public int size() ;
// 清空顺序表
public void clear() ;
// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
public void display();
}
二、接口的实现
2.1准备工作
首先创建一个包,包里包含一个接口,一个测试类,以及一个实现接口的类。
将要实现的方法定义到接口中,然后在实现类里重写实现这些方法。
1.实现的类里,包含一个成员变量即数组,此时只是声明了数组,并没有分配内存。
2.另一个成员变量usedSize用于存放存入数组的数据个数,默认为0。
3.一个常量表示数组的默认大小。
4.两个构造方法。不带参数的构造方法,将数组初始化为默认大小;带参数的构造方法,通过传入的参数将数组初始化为指定大小。
public class MyArrayList implements IList {
//1.定义一个数组 还没分配内存 要new一个新对象才分配内存
public int[] array;
//2.定义数组默认大小
public static final int DEFAULT_SIZE = 10;
//3.定义变量用来存放存入数组的数据个数
public int usedSize;//默认为零
//2.1调用无参数构造方法 初始化数组
public MyArrayList(){
this.array = new int[DEFAULT_SIZE];
}
//2.2或者调用一个参数构造方法 指定数组大小
public MyArrayList(int size){
this.array = new int[size];
}
}
2.2实现接口方法
做好准备工作,现在就开始依次实现这些方法。
2.2.1打印顺序表
public void display();
遍历数组打印出数组内容,只打印有效数据,所以只用遍历到usedSize即可。
public void display() {
for (int i = 0; i < usedSize; i++) {
System.out.println(this.array[i]);
}
}
2.2.2新增元素
默认在数组最后新增
public void add(int data);
第一反应是直接array[usedSize] = data;但是,数据结构是非常严谨的,我们要考虑数组是否满了,没满直接追加,usedSize++,满了就先扩容然后再追加。
copyOf方法,先将原数组内容拷贝一份,然后将原数组扩大指定大小,再将拷贝内容放入新数组,接着把引用指向新数组。
在指定位置增加数据
public void add(int pos,int data);
同样的,要严谨的思考这个问题
问题一:索引是否合法?
在-1位置当然是不能插入数据的,那么在6位置能否插入呢?答案也是不能的,因为线性表的数据必须有前驱即它的前面必须有数据。所以我们要保证索引在[0,usedSize之间]。
问题二:如何插入?
先让i指向最后一个元素,然后让数据往后挪,挪完一个之后i–,直到i移到pos位置时,还得让pos上的数据往后移,腾出pos的位置位置后,再将数据放进去。注意:记得让usedSize++
问题三:索引正常时,万一数组满了呢?
因此也要先判断数组是否满了,那么我们就可以将前面写过的判断容量和扩容的方法单独写一个方法。
这时候我发现,当索引不合法时,只是简单的打印一下不合法信息,然后return并不能解决问题,因为当操作很多时,索引不合法,即使不合法的信息打印出来了,也会被日志掩盖,然后接着执行后面的代码,所以,这时候应该抛一个异常。
public void add(int pos, int data) {
//问题一:索引是否合法
// if(pos<0 || pos>this.usedSize){
// System.out.println("位置不合法");
// return;
// }
if(pos<0 || pos>this.usedSize){
try {
checkPosOnAdd(pos);
}catch (PosIllegality e) {
e.printStackTrace();
return;
}
}
//问题二:是否满了 满了扩容
capacity();
//问题三:插入元素
for (int i = this.usedSize-1; i >=pos ; i--) {
array[i+1] = array[i];
}
array[pos] = data;
usedSize++;
}
//检查插入时pos的合法性
private void checkPosOnAdd(int pos) throws PosIllegality{
if(pos < 0 || pos > usedSize) {
System.out.println("不符合法!");
throw new PosIllegality("插入元素下标异常: "+pos);
}
}
public class PosIllegality extends RuntimeException{
public PosIllegality(String msg) {
super(msg);
}
}
2.2.3 判断是否包含某个元素
这个方法的实现就是遍历数组,看有没有与查找的值相同的,看似没有需要考虑的,其实是有的,万一这个数组为空呢?所以要写一个判断是否为空的方法。
public boolean contains(int toFind){
if(isEmpty()){
return false;
}
for (int i = 0; i < usedSize; i++) {
//如果是查找引用数据类型 一定记住 重写方法
if (array[i] == toFind){
//找到返回ture
return true;
}
}
//找不到返回false
return false;
}
有些方法比较简单,就不一一赘述
2.2.4 获取pos位置的元素
1.pos是否合法,这里不能沿用插入元素时的异常,因为插入时pos要[0,usedSize],但是获取元素,usedSize位置肯定是没有元素的,所以pos应该在[0,usedSize-1]
2.是否为空
2.2.5给pos的值设置为value
1.检查pos是否合法
2.6删除指定元素
1.检查是否存在这个值
3.从索引位置开始,然后后一个往前一个覆盖
4.usedSize–;
2.6将数组清空
对于基本数据类型来说可以直接将UsedSize置为0,内存没有被回收,但是没有影响,因为数据占四个字节,即使回收了内存,数组默认为0,依旧占四个字节。
对于引用数据类型,不能直接置为0,会造成内存泄漏。当置为0时内容并没有被回收。
JVM会自动回收内存,前提:对象没有被引用是才能回收,即使UsedSize置为0了,但是0下标仍然在引用对象。
方法一:elem = null;直接将数组置为空,那么数组没有被引用,JVM就会直接被回收
方法二:循环
for(){
elem[i] = null;
}
缺点:1.插入元素时,最坏情况如果插入到第一个位置,事件复杂度为O(n),
2.删除数据时,最坏情况,删除0位置,时间复杂度O(n)
3.扩容时空间会冗余
优点:查找元素时,给定下标进行查找,O(1)
总结:比较适合进行给定下标查找元素
M就会直接被回收**
方法二:循环
for(){
elem[i] = null;
}
缺点:1.插入元素时,最坏情况如果插入到第一个位置,事件复杂度为O(n),
2.删除数据时,最坏情况,删除0位置,时间复杂度O(n)
3.扩容时空间会冗余
优点:查找元素时,给定下标进行查找,O(1)
总结:比较适合进行给定下标查找元素