顺序表(原理)

原理:

1.先讲底层实现

2.讲怎么用

顺序表就是一个动态扩容数组,也就是在数组上完成增删查改,如下图:

这里肯定有人说,既然数组能直接表示一个顺序表,那直接用数组不就得了吗?

既然这样,我们来看一个问题:

有一组数据如下:

如果我们不直接把他封装到数据表里面,单纯的用数组会有一个问题。如下把前三个数放到数组里面。

问题1:当前数组里面有多少个有效的数据。这里肯定会有人脱口而出,三个。那我问你,你如何通过程序去计算呢?这里肯定有人又说,肯定是遍历,遍历到0就停止,如果这样说就有问题了,假如下标3就为0呢,那就把他丢掉了。所以存在即合理。所以我们要想办法 ,通过其他的东西和这个数组结合,达到一个目的,比如说,数组里面放了三个元素,我就知道放了三个。

(用顺序表的目的,假如我们有10个数组,放了三个,我怎么知道它放了三个)

这里给两个类 Test(在这个里面进行测试),MyArrayList(定义类),一个接口IList。

package mylist;
//这个是定义的接口
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() ;
        //判断数组是否满了
        boolean isFull();
        public boolean isEmpty();
}

这里在MyArrayList里面定义一个数组elem,对他进行初始化,如下:

public int[] elem;

/构造方法(分配内存)
 public  MyArrayList(){
        this.elem=new int[10];/直接对这个数组进行初始化
    }

这里我们要想,假如在这10个数组里面放了两个元素,那怎么知道我放了两个呢?

很简单,这里定义一个变量叫useSize,默认为0,每当存储一个数据的时候,useSize就加加。这里useSize配合数组,我们就知道这个数组里面有几个元素。

这里把上面代码进行优化如下:

    public int[] elem;
    public int usedSize;//默认为0


    //优化
    //代表顺序表的默认大小 
    public static final int DEFAULT_SIZE=10;
    //1.分派内存,她一定会调用构造方法,在构造方法里面直接进行初始化(这样写更规范)
    public  MyArrayList(){
        this.elem=new int[DEFAULT_SIZE];
    }

 这里再给一个构造方法:

public  MyArrayList(int capacity){//这个更灵活
        this.elem=new int[capacity];
    }


capacity相当于size(大小)

假如public  MyArrayList(int capacity)中capacity给个10,this.elem=new int[capacity]就默认是10

接下来的目标,如果这个数组里面有数据,要怎么操作这些数据呢。在或者说顺序表里面一个元素都没有的情况下,怎么往进放元素...(要么里面没元素,要么里面有元素放往哪放,存往那存,取从那取..)

 接着往下看,如果我们想遍历elem这个数组,并不是把整个数组遍历一遍,而是有几个就遍历到哪里,如第一幅图:

这里有三个元素,遍历到下标2就停止.

所以我们假设这个数组里面有元素,我们要打印有多少个元素。用如下这个方法:

 @Override
    public void display() {

    }
这个方法的作用是遍历顺序表当中的元素

有多少个元素都在usedSize里面,当然在写这个方法的时候,数组里面一个元素都没有。所以在遍历的时候i要小于usedSize,当然在目前场景下是usedSize为0。在如下的场景下是3.

那么0到3是不是就相当于遍历3次,实现代码如下:

@Override
    public void display() {
        for(int i=0;i<this.usedSize;i++){
            System.out.print(this.elem[i]+" ");//每次直接输出.elem[i]+" "的值
        }
        System.out.println();//回车
    }

这里我们知道怎么打印这个数组里面的东西了,那怎么往这个数组里面放东西呢?这里我们提供了两个方法

    @Override
    public void add(int data) {

    }

    @Override
    public void add(int pos, int data) {

    }

这两个方法参数列表不一样,构成了方法的重载。

通过接口的注释,我们可以看到,新增元素,默认在数组最后新增

那也意味着,假设 下面这幅图

 本来有三个元素,现在要往进放个65,往哪放?

这里就要注意了,并不是直接放到3的下标,然后加加就好,如下:

 @Override
    public void add(int data) {
        this.elem[this.usedSize]=data;
        this.usedSize++;
    }

而是先要判断这个数组是否满了,如下是判断是否满了这个方法

@Override
    public boolean isFull() {
       
    }

那什么时候是满,这里我们就要想,如果usedSize 他的大小已经和数组长度都一样了,你还能放吗,所以在这个方法里面就要判断,如下代码:

public boolean isFull() {

//        if(usedSize == elem.length) {
//            return true;
//        }
//        return false;


        //优化
        return usedSize==elem.length;//他们相等true,不相等flase
    }

所以在add里面要进行判断,如果这个数组满了,就要进行扩容,那么扩容怎么扩(比如说现在长度是10,要扩容成20,那就相当于elem来说,他现在这个数组,他要指向新的数组且长度是2倍且要把原来的数组拷贝过来),那要怎么扩容,是不是直接调用Arrays.copyOf()这个方法,然后elem重新指向拷贝之后的数组。

如下:

public void add(int data) {
        if(isFull()){//如果满了在这里面进行扩容
            elem=Arrays.copyOf(elem,elem.length*2);
        }
        this.elem[this.usedSize]=data;
        this.usedSize++;
    }

当我们扩容之后,在往进放,肯定能放下。

接着往下看,上面这个add默认放到了数组最后,那接下来我们看王指定位置放。

当然,第一步和上面一样,他如果是满的,就进行扩容。

而且,我们也可以自己写一个扩容方法,如下:

private  void checkCapacity() {
        if(isFull()){//如果满了在这里面进行扩容
            elem=Arrays.copyOf(elem,elem.length*2);
        }
    }

此时可以将上面两个方法中改一下:

@Override
    public void add(int data) {

//        if(isFull()){//如果满了在这里面进行扩容
//            elem=Arrays.copyOf(elem,elem.length*2);
//        }

        checkCapacity();
        this.elem[this.usedSize]=data;
        this.usedSize++;
    }

 

    @Override
    public void add(int pos, int data) {
        checkCapacity();
    }


    //自己写的扩容方法
    private  void checkCapacity() {
        if(isFull()){//如果满了在这里面进行扩容
            elem=Arrays.copyOf(elem,elem.length*2);
        }
    }

测试代码如下:

public class Test {
    public static void main(String[] args) {

        MyArrayList myArrayList=new MyArrayList();
        myArrayList.add(1);
        myArrayList.add(2);
        myArrayList.add(3);
    }


}

这里肯定会有人问,为什么要把 checkCapacity()方法定义成 private呢?其实很简单,因为checkCapacity()检查容量问题,他是我们在做这些功能的时候包含的,他不是提供给用户去用的。他是只为当前类中的方法进行服务,不为其他类服务,所以要把它分装起来。

再来回到第二个add方法中,如果这个数组没满,就往里面放(往pos位置放),如果没满,就扩容,但是,我们这里就要注意,我们并不知道pos位置在哪,以下图为例

 

 如果pos要往1下标放,就要把1和2下标上的数据移开,不然就会覆盖,如果往3下标放,可以,如果往-1下标放呢?当然不可以。那如果往4位置放呢?当然也不可以,因为在数据结构当中,每次存储数据的时候,一定记住,必须有一个前驱信息(意思就是,如果往4下标插行不行,取决于你有没有往3下标插过,如果没有,4下标是不能插的),所以在如上图这个合法范围是0<=pos<=3(大于等于0,小于等于3),而3刚好是usedSize,所以0<=pos<=usedSize,所以pos在这个范围类一定是合法的。所以我们要在检查容量之前,先检查下标,如下:

@Override
    public void add(int pos, int data) {
        //这里对pos进行检查
        if(pos<0 || pos>usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            return;
        }
        checkCapacity();
    }

我们也可以自己写个检查pos的合法性的方法,如下:

private void checkPosOnAdd(int pos) {
        if (pos < 0 || pos > usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
        }
    }

这里我们要知道,不合法要返回一个值,接收一下返回值,返回,然后结束这个方法,不能再继续往下执行了,而且如上,打印一个不合法这件事情,当打印的比较多的时候,可能会找不到,那应该怎么办呢?这个时候就有一个东西,抛一个异常。

也就是说,在如下调用checkPosOnAdd(pos)检查异常的时候,如果这里pos不对就抛异常。

public void add(int pos, int data) {
       checkPosOnAdd(pos);
        checkCapacity();
    }

 这个异常相当于pos下标异常,这里自己写一个异常,如下:

//用来抛pos不合法的异常
//RuntimeException:运行时错误
public class PosIllegality extends RuntimeException {
    public PosIllegality(String msg) {
        super(msg);
    }
}

在如下就插入下标异常,然后声明一下,在调用的时候可能有这个异常,所以在调用的时候要小心。

//自己写的检查pos的方法,专门用来检查pos的合法性
    private void checkPosOnAdd(int pos) {
        if (pos < 0 || pos > usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("插入元素下标异常" + pos);
        }
    }

当然add里面也要声明一下,如下:

 @Override
    public void add(int pos, int data) {
        //这里对pos进行检查
//        if(pos<0 || pos>usedSize) {//在这种情况下不合法
//            System.out.println("不合法!");
//            return;
//        }
        try{
            checkPosOnAdd(pos);
        }catch(PosIllegality e){
            e.printStackTrace();
        }

        checkCapacity();
    }

 

 这段代码的意思是,对pos检查是否合法,如果合法,容量也够了,就可以往进插了。这里我们先来检验一下不合法时抛得异常。如下:

处理异常直接ruturn即可,如下:

 @Override
    public void add(int pos, int data) {
        //这里对pos进行检查
//        if(pos<0 || pos>usedSize) {//在这种情况下不合法
//            System.out.println("不合法!");
//            return;
//        }
        try{
            checkPosOnAdd(pos);
        }catch(PosIllegality e){
            e.printStackTrace();
            return;
        }
       
        checkCapacity();
    }

把上面说的都完成之后,接下来我们看怎么插入, 还是来看这幅图,如果要把pos放到1下标,就要移元素,把36和25给移开。

这里我们就要想,那怎么移动呢?

这里我们只能从最后一个元素25开始移动,如果移动36的话,往后移动,就会把25给覆盖了,这肯定是不行的。为了方便理解,这里把把2下标定义成i,而这里代码实现的逻辑就是[i+1]=[i],然后进行i减减。直到 i<pos 即可(如果当i>=pos的时候,还要继续减减 ),然后把pos放到下标1的位置。代码如下:

 @Override
    public void add(int pos, int data) {
        //这里对pos进行检查
//        if(pos<0 || pos>usedSize) {//在这种情况下不合法
//            System.out.println("不合法!");
//            return;
//        }
        try{
            checkPosOnAdd(pos);
        }catch(PosIllegality e){
            e.printStackTrace();
             return;
        }

        checkCapacity();

        //第一步:从最后一个有效数据开始往后移动,第二步:当i<pos就结束
        for (int i=usedSize-1;i>=pos;i--){
            elem[i+1]=elem[i];
        }
        //第三步:存放元素到pos位置
        elem[pos]=data;
        //第四步:usedSize++
        usedSize++;
    }

 代码结果:

 如上就是往任意位置放的整个逻辑。

上面我们把add写完了,剩下的就比较简单了,接着往下看contains,如下:

判断是否包含某个元素之前,我们想一下,如果是空的,那要不要判断呢?当然,如果是空的,就不需要判断,因此,在这里我们新增一个方法,如下:

@Override
    public boolean isEmpty() {
         return usedSize==0; //当usedSize==0的时候为空
    }

 contains的代码实现如下:

@Override
    public boolean contains(int toFind) {
        if (isEmpty()) {
            return false;//如果为空,直接return false即可
        }
        //如果不为空,直接遍历这个数组即可
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
                return true;
            }
        }
    return false;
    }

这里我们测试一下,看是否包含1.

结果是正确的,说明包含。

这里我们看一下代码中用红色箭头标注的这个步骤

 注意:如果这里查找引用数据类型,一定要重写方法。

接下来看indexOf()。

indexOf()也是查找,只不过它查找的的是位置。这里按照上面的逻辑,但是这里如果是空的返回-1,因为正常的数组下标没有负数,代码如下:

@Override
    public int indexOf(int toFind) {
        if (isEmpty()) {
            return -1;//如果为空,直接return false即可
        }
        //如果不为空,直接遍历这个数组即可
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
                return i;
            }
        }
        return -1;
    }

接下来我们看get()方法。

 再来看这幅图。

如图,如果pos是这副图上0,1,2中任意一个下标,则都可以获取,如果要获取的pos是负数,或者是下标3,则不能获取。此时上面检测pos是否合法的那个方法就不能用了,这里重新定义一个检查pos是否合法的方法 checkPosOnAddGet(),如下:

private void checkPosOnAddGet(int pos) throws  PosIllegality {
        if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("获取指定下标元素异常" + pos);
        }
    }

所以,在get这个方法中,先要检查一下pos 的合法性,然后看他是否为空,如果为空,就抛一个异常,如下定义的异常和代码:

 @Override
    public int get(int pos) {
        checkPosOnAddGet(pos);
        //如果是空的,就抛一个异常
        if(isEmpty()){
            throw  new MyArrayListEmpty("获取指定下标元素时"+"顺序表为空");
        }
        return elem[pos];
    }

   

接下来看set方法给 pos 位置的元素设为 value,就是说

还是下面这幅图 

给 pos 位置的元素设为 value,意思就是比如pos在2下标,而2下标的数是56,现在把他改成159,所以set的意思就是更新的意思。

set的方法如下:

@Override
    public void set(int pos, int value) {

    }

当然,这里我们还是要判断pos合不合法 ,这里就和上面 checkPosOnAddGet里面的代码一样,pos不能大于等于usedSize,这里我将这个方法改成checkPosOnAddGetSet,如下:

private void checkPosOnAddGetSet(int pos) throws  PosIllegality {
        if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("获取指定下标元素异常" + pos);
        }
    }

接下来就简单了,直接让1下标的pos等于value.,代码如下:

@Override
    public void set(int pos, int value) {
        checkPosOnAddGetSet(pos);
        elem[pos]=value;
    }

这里看一下测试结果:

接下来看size

对于size就更简单,直接如下代码即可

@Override
    public int size() {
        return this.usedSize;
    }

 接着看remove

还是如下图,假如这是数据表 

假如我们现在要删除36,首先,我们先要找一下放36在哪里个位置,然后删除36就很简单。

比如我们找到36在下标1的位置,我们要删36,之后让25往前移动,然后修改usedSize(减减)数据即可。

三步:
1.找到要删除的数据
2.挪动数据
3.修改size


@Override
    public void remove(int toRemove) {
        int index=indexOf(toRemove);
        if(index==-1){
            System.out.println("没有这个数字!");
        }
        //如果index不等于-1
        for(int i=index;i<usedSize-1;i++){
            elem[i]=elem[i+1];
        }
        usedSize--;
    }

这个代码中, for(int i=index;i<usedSize-1;i++)中 ,如果如上图,有0到5个下标,假如

它每个下标都有元素,一定要;i<usedSize-1,如果i<used Size,则可能会越界。

结果如下:

接下来看clear

 还是这幅图

清空是指把上面图中的3个元素重置,那该怎么做呢?

这个是要分情况的,对于当前这里面如果放的是基本数据类型,直接将usedSize置为i0,如上图,如果usedSize为0,再来一个新的数据也没关系,因为新数据会把 21盖掉,因为他每次都在往usedSize位置放,所以对于当前代码,直接将usedSize=0就好,不管它里面有些撒数据,没关系。

代码结果如下:

这里要注意一个问题,像上面这个数组是int类型的数组,如果现在把他改成引用类型的数组,此时,要把数组清空,在这里usedSize就不可以等于0了,如果把他置为0,这里就出现了内存泄漏的问题。所以如果是引用类型,就通过for循环一个一个将数组置为null。


所有的代码如下:

package mylist;
//定义的类
import javax.xml.bind.annotation.XmlType;
import java.util.Arrays;

public class MyArrayList implements IList {
    //创建一个方法,可以知道在顺序表中放了几个元素,如下:
    public int[] elem;
    //2.怎么分辨他放了几个元素呢?重新定义一个变量,叫usedSize,它每次存一个数字叫加一
    public int usedSize;//默认为0
    //优化
    public static final int DEFAULT_SIZE=10;
    //1.分派内存,她一定会调用构造方法,在构造方法里面直接进行初始化(这样写更规范)
    public  MyArrayList(){

        this.elem=new int[DEFAULT_SIZE];
    }
    public  MyArrayList(int capacity){//这个更灵活

        this.elem=new int[capacity];
    }

    @Override
    public void display() {
        for(int i=0;i<this.usedSize;i++){
            System.out.print(this.elem[i]+" ");
        }
        System.out.println();
    }
    @Override
    public void add(int data) {
//        if(isFull()){//如果满了在这里面进行扩容
//            elem=Arrays.copyOf(elem,elem.length*2);
//        }
        checkCapacity();
        this.elem[this.usedSize]=data;
        this.usedSize++;
    }

    @Override
    public boolean isFull() {
//        if(usedSize == elem.length) {
//            return true;
//        }
//        return false;
        //优化
        return usedSize==elem.length;
    }

    @Override
    public void add(int pos, int data) {
        //这里对pos进行检查
//        if(pos<0 || pos>usedSize) {//在这种情况下不合法
//            System.out.println("不合法!");
//            return;
//        }
        try{
            checkPosOnAdd(pos);
        }catch(PosIllegality e){
            e.printStackTrace();
             return;
        }

        checkCapacity();

        //第一步:从最后一个有效数据开始往后移动,第二步:当i<pos就结束
        for (int i=usedSize-1;i>=pos;i--){
            elem[i+1]=elem[i];
        }
        //第三步:存放元素到pos位置
        elem[pos]=data;
        //第四步:usedSize++
        usedSize++;
    }

    //自己写的检查pos的方法,专门用来检查pos的合法性
    private void checkPosOnAdd(int pos) throws  PosIllegality {
        if (pos < 0 || pos > usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("插入元素下标异常" + pos);
        }
    }
    //自己写的扩容方法
    private void checkCapacity() {
        if(isFull()){//如果满了在这里面进行扩容
            elem=Arrays.copyOf(elem,elem.length*2);
        }
    }

    @Override
    public boolean contains(int toFind) {
        if (isEmpty()) {
            return false;//如果为空,直接return false即可
        }
        //如果不为空,直接遍历这个数组即可
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
                return true;
            }
        }
    return false;
    }

    @Override
    public boolean isEmpty() {
        return usedSize==0;
    }

    @Override
    public int indexOf(int toFind) {
        if (isEmpty()) {
            return -1;//如果为空,直接return false即可
        }
        //如果不为空,直接遍历这个数组即可
        for (int i = 0; i < usedSize; i++) {
            if (elem[i] == toFind) {//如果elem[i]等于你要找的这个值,返回true,否则返回false
                return i;
            }
        }
        return -1;
    }

    @Override
    public int get(int pos) {
        checkPosOnAddGet(pos);
        //如果是空的,就抛一个异常
        if(isEmpty()){
            throw  new MyArrayListEmpty("获取指定下标元素时"+"顺序表为空");
        }
        return elem[pos];
    }

    private void checkPosOnAddGet(int pos) throws  PosIllegality {
        if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("获取指定下标元素异常" + pos);
        }
    }

    @Override
    public void set(int pos, int value) {
        checkPosOnAddGetSet(pos);
        elem[pos]=value;
    }
    private void checkPosOnAddGetSet(int pos) throws  PosIllegality {
        if (pos < 0 || pos >= usedSize) {//在这种情况下不合法
            System.out.println("不合法!");
            throw new PosIllegality("获取指定下标元素异常" + pos);
        }
    }

    @Override
    public void remove(int toRemove) {
        int index=indexOf(toRemove);
        if(index==-1){
            System.out.println("没有这个数字!");
        }
        //如果index不等于-1
        for(int i=index;i<usedSize-1;i++){
            elem[i]=elem[i+1];
        }
        usedSize--;
    }

    @Override
    public int size() {
        return this.usedSize;
    }

    @Override
    public void clear() {
        this.usedSize=0;
    }


}

package mylist;
//在这个里面进行测试
public class Test {
    public static void main(String[] args) {

        MyArrayList myArrayList=new MyArrayList();
        myArrayList.add(1);
        myArrayList.add(2);
        myArrayList.add(3);
        myArrayList.add(1,199);
        myArrayList.display();
       System.out.println(myArrayList.contains(1));
        myArrayList.set(2,159);
        myArrayList.display();
        myArrayList.remove(1);
        myArrayList.display();

        myArrayList.clear();
        System.out.println("============");
        myArrayList.display();

    }
package mylist;
//第一个异常
//用来抛pos不合法的异常
//RuntimeException:运行时错误
public class PosIllegality extends RuntimeException {
    public PosIllegality(String msg) {
        super(msg);
    }
}
package mylist;
//第二个异常
public class MyArrayListEmpty extends RuntimeException{
    public MyArrayListEmpty(String msg){

    }
}

接口在最开始就写了,这里就不写了。 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值