顺序表(Java语言)

  • 前言
  • 线性表
  • 顺序表
  • 概念及结构
  • 基本结构
  • 打印顺序表
  • 获取顺序表的长度
  • 判断顺序表是否满
  • 在pos位置增加一个元素
  • 判定是否包含某个元素
  • 查找某个元素对应的位置,找不到返回-1
  • 获取pos位置的元素
  • 判断顺序表是否为空
  • 给pos位置的元素设置设为value
  • 删除第一次出现的toRemove
  • 清空顺序表
  • 总的MyArrayList类


前言

当我们接触到顺序表与列表时,就涉及到了数据结构的知识,顺序表和链表是数据结构最为基础的部分,所谓数据结构就是存储数据的方式,有同学会问C语言数据结构和Java语言数据结构有什么不同?其实没有不同,数据结构是一门单独的学科,就好比C语言是中文,Java是英文,数据结构是数学,我们可以用英文表达数学的逻辑结构,也可以用中文表达数学的逻辑结构,所以数据结构也是如此。

线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

顺序表

概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:

  • 静态顺序表:使用定长数组存储。
  • 动态顺序表:使用动态开辟的数组存储。

我们以动态顺序表为例:

基本结构
public class MyArrayList {
    public int[] elem;
    public int usedSize;//有效的数据个数

    public MyArrayList() {
        this.elem = new int[10];
    }
}

首先我们创建一个顺序表的类,类中有两个属性,一个是数组elem(就可以把顺序表看成一个数组),一个是int类型的usedSize(用来表示有效数据的个数,也就是元素的个数),构造方法是创建一个10个元素的数组并赋给elem数组。

打印顺序表
//打印顺序表
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i] + " ");
        }
        System.out.println();
    }

很好理解,跟打印数组元素一模一样。

获取顺序表的长度
//获取顺序表的有效数据长度
    public int size() {
        return this.usedSize;
    }

很好理解,usedSize就是数组元素的个数,也是顺序表的长度,直接返回就可以了。

判断顺序表是否满
//判断顺序表是否满
    public boolean isFull() {
        return this.elem.length==this.usedSize;
    }

如果数组的长度等于元素的个数,那么this.elem.length==this.usedSize;就是true,否则就是false。

在pos位置增加一个元素
//在pos位置新增加一个元素
    public void add(int pos, int data) {
        if (pos < 0 || pos > usedSize) {
            System.out.println("pos位置不合法!");
            return;
        } else {
            if (isFull()) {
                this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
            } else {
                for (int i = this.usedSize - 1; i >= pos; i--) {
                    this.elem[i + 1] = this.elem[i];
                }
                this.elem[pos] = data;
                this.usedSize++;
            }
        }

    }

首先我们需要判断pos位置是否合法(pos不能为负数,pos不能大于usedSize的值,否则都可能会引起数组越界),然后判断数组是否为满,若满需要扩容(这里是扩容数组长度的两倍),其次从最后一个元素开始依次赋值给后一个元素,最后在pos位置赋值data,并且usedSize++。这一部分理解起来比较困难,我们画图理解一下。(前面的判断位置是否合法,是否为满不多赘述)

假设有4个元素:

java顺序番号_顺序表


我们要将55元素放在下标为1的位置,我们知道需要将22,33,44依次往后移,但是如果先移动22,那么33就会被22所覆盖,那么33元素就会丢失,同理44也是如此,因此我们需要将44先往后移,再将33,22往后移动,给55腾出位置。

java顺序番号_开发语言_02


这个过程就对应了上面for循环的代码,当然还有一种情况就是我们也可以紧接着44后面直接添加55元素,所以我们不需要移动22,33,44,因此在for循环中,我们需要判断pos(插入位置的下标)和usedSize(元素的个数)的关系,如果pos下标小于usedSize,那么就需要给对应的位置腾出空间,还有一种就是pos==usedSize,直接将data的值赋给elem[pos]即可。

判定是否包含某个元素
//判定是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

很好理解,遍历整个数组,如果与所找的元素相等,返回true,否则返回false。

查找某个元素对应的位置,找不到返回-1
//查找某个元素对应的位置,找不到返回-1。
    public int search(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

跟是否包含一个元素类似,返回值改为了下标或者-1。

获取pos位置的元素
/获取pos位置的元素
    public int getPos(int pos) {
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos位置不合法!");
            return -1;//包含了顺序表为空的情况
        }
//        if (isEmpty()){
//            System.out.println("顺序表为空!");
//            return -1;//
//        }
        return this.elem[pos];
    }

首先要判断pos位置是否合法,不能为负数也不能超过数组的长度,然后返回elem[pos]的值。

判断顺序表是否为空
//判断顺序表是否为空
    public boolean isEmpty() {
        return this.usedSize == 0;
    }

很好理解,返回this.usedSize == 0;的boolen值即可。

给pos位置的元素设置设为value
//给pos位置的元素设置设为value
    public void setPos(int pos, int value) {
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos位置不合法!");
            return;//包含了顺序表为空的情况
        }
//        if (isEmpty()){
//            System.out.println("顺序表为空!");
//            return;
//        }
        this.elem[pos] = value;
    }

同样的,判断pos位置是否合法,合法将value赋值给elem[pos]即可。

删除第一次出现的toRemove
public void remove(int toRemove) {
        if (isEmpty()) {
            System.out.println("顺序表是空!");
            return;
        }
        int index = search(toRemove);
        if (index == -1) {
            System.out.println("没有删除的数字!");
            return;
        }
        for (int i = index; i < this.usedSize - 1; i++) {
            this.elem[i] = this.elem[i + 1];
        }
        this.usedSize--;
    }

首先判断顺序表是否为空值,其次找到toRemove的下标,这里我们用search方法就可以找到,search方法返回值为-1,则打印没有删除的数字!否则从index开始,将后一个元素的值赋值给前一个元素,并且usedSize–。我们画图理解一下。

假设有5个元素:11,55,22,33,44,我们将55删除

java顺序番号_数据结构_03


首先我们将22的值赋给下标为1的元素,其次33,44。并且usedSize–最后成为这样:

java顺序番号_数据结构_04


虽然我们没有将4下标的44删除,但是我们的usedSize变成了4,这就意味着数组中只有4个元素,并不会影响到整个数组,当我们添加数组元素是也会将44给覆盖掉,因此不需要我们过多的处理。

清空顺序表
public void clear() {
        this.usedSize = 0;
    }

同样的,清空顺序表只需要我们将usedSize赋值为0即可完成。

总的MyArrayList类
public class MyArrayList {
    public int[] elem;
    public int usedSize;//有效的数据个数

    public MyArrayList() {
        this.elem = new int[10];
    }

    //打印顺序表
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i] + " ");
        }
        System.out.println();
    }

    //获取顺序表的有效数据长度
    public int size() {
        return this.usedSize;
    }

    //在pos位置新增加一个元素
    public void add(int pos, int data) {
        if (pos < 0 || pos > usedSize) {
            System.out.println("pos位置不合法!");
            return;
        } else {
            if (isFull()) {
                this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
            } else {
                for (int i = this.usedSize - 1; i >= pos; i--) {
                    this.elem[i + 1] = this.elem[i];
                }
                this.elem[pos] = data;
                this.usedSize++;
            }
        }

    }

    //判断顺序表是否满
    public boolean isFull() {
        return this.elem.length==this.usedSize;
    }

    //判定是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

    //查找某个元素对应的位置,找不到返回-1。
    public int search(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

    //获取pos位置的元素
    public int getPos(int pos) {
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos位置不合法!");
            return -1;//包含了顺序表为空的情况
        }
//        if (isEmpty()){
//            System.out.println("顺序表为空!");
//            return -1;//
//        }
        return this.elem[pos];
    }

    //判断顺序表是否为空
    public boolean isEmpty() {
        return this.usedSize == 0;
    }

    //给pos位置的元素设置设为value
    public void setPos(int pos, int value) {
        if (pos < 0 || pos >= this.usedSize) {
            System.out.println("pos位置不合法!");
            return;//包含了顺序表为空的情况
        }
//        if (isEmpty()){
//            System.out.println("顺序表为空!");
//            return;
//        }
        this.elem[pos] = value;
    }

    public void remove(int toRemove) {
        if (isEmpty()) {
            System.out.println("顺序表是空!");
            return;
        }
        int index = search(toRemove);
        if (index == -1) {
            System.out.println("没有删除的数字!");
            return;
        }
        for (int i = index; i < this.usedSize - 1; i++) {
            this.elem[i] = this.elem[i + 1];
        }
        this.usedSize--;
    }

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

我们发现顺序表达效率相对比较低的,我们从时间复杂度和空间复杂度进行分析:

  • 时间复杂度:我们在增加和删除某个值是我们需要移动后面的元素,这样大大增加了时间复杂度。
  • 空间复杂度:例如:我们有十个元素,当我们需要在增加一个元素时,我们对数组进行扩容,使得数组是原来的两倍,也就是二十个元素,但是我们只用到了十一个元素,这样大大增加了空间复杂度。

因此有没有更好的数据结构,解决顺序表的缺点呢?答案就是链表,至于链表的相关知识介绍下一篇博客会详细讲解。如果有什么建议或则错误,欢迎私信和评论,谢谢大家。