简单实现线性表之顺序存储

245 阅读4分钟

前言

本章用于讲述线性表中线性表顺序存储的介绍与使用, 顺序存储也就是我们常说的数组

顺序存储

介绍:

顺序存储是用一组地址连续的存续单元依次存放线性表中各个数据元素的存储结构;

规格:

因为线性表中的所有数据元素的类型是相同的,所以每一个数据元素在存储器中占用相同大小的空间

特点:
  1. 在线性表中逻辑上相邻的数据元素,在物理存储位置上也是相邻的
  2. 存储密度高,但需要预先分配“足够应用”的存储空间,这可能会造成存储空间的浪费
  3. 便于随机存储
  4. 不便于插入与删除,因为在顺序表中每一次插入与删除操作都会引起大量数据移动
复杂度分析

顺序存储数组属于常见的一种线性结构,可以实现随机存取, 因此访问结点的时间复杂度都为 O(1);但进行插入或删除结点,则需要涉及到大量移动元素, 故其时间复杂度为O(n);平均复杂度为 O(n)

 

前奏准备

本文章讲解所用的编程语言为 Java

为了规范化,分为三个java文件
  • TestList.java(接口类):用于写定义接口
  • TestArrayList.java(类):用于实现接口方法
  • Test.java(类):程序入口

lineList.java

每个方法都是规范的使用,重点为插入删除

 public interface TestList<E> {
     //泛型 定义形参为任何值 规范返回值
 ​
     //在index位置插入
     void insert(int index, E e);
     
     //在末尾添加添加
     void add(E e);
 ​
     //根据索引删除
     void remove(int index);
 ​
     //用于删除第一个元素
     void removeFirst();
 ​
     //用于删除最后一个元素
     void removeLast();
 ​
     //设置index的值为e
     void set(int index, E e);
 ​
     //获取index位置的值
     E get(int index);
 ​
     //获取线性表的大小
     int size();
 ​
     //判断表是否为空
     boolean isEmpty();
 ​
     //获取元素e的索引位置
     int indexOf(E e);
 ​
     //显示所有元素的值
     void display();
 }

TestArrayList

接下来到重写java接口的方法类,声明原始数组与原始长度,

 public class TestArrayList<E> implements TestList<E> {
     // 声明数组
     E[] data;
     // 声明长度
     int len;
 ​
     public TestArrayList(int count) {
         //  创建count大小的数组
         //  *小知识:用泛型进行转型
         this.data = (E[]) new Object[count];
         //  初始化长度为0
         this.len = 0;
     }
     
     // 方法代码...
 }

利用java内置红线报错,Implement Method 进行自动导入方法;当然你也可以手写( ̄▽ ̄)"

Test

在我们定义完接口类与方法类后,就可以开启下面的程序入口了

 public class Test {
     public static void main(String[] args) {
         // 定义泛型<E>接收类型为String 长度count为10
         linetableLG<String> test = new linetableLG<String>(10);
         
         // 运行代码...
    }
 }

前奏准备完毕后,我们就可以开始重写方法了 (●'◡'●)

insert(插入)

前文说了在顺序表中插入元素会每个元素都要向后移动,会对元素造成一定的麻烦,除非做点小处理,让它不消耗少点,但浪费内存还是不可避免的;

插入算法的思路:
  1. 进行判断校验(健壮性),判断是否为空,是否满额,索引是否超出范围
  2. 添加元素数据
  3. 更新数组
代码示例
     @Override
     public void insert(int index, E e) {
         /**
          * index: 插入的位置
          * e: 插入的元素
          */
         // 1. 进行判断校验(健壮性)
         // 数组是否已超出定义长度 (这点后面会优化)
         if (len == data.length) {
             throw new RuntimeException("数组已满");
         }
         // 判断插入的元素是否为空
         if (e == null) {
             throw new NullPointerException("元素不能为空");
         }
         // 判断插入的元素是否为空
         if (index < 0 || index > len) {
             throw new IndexOutOfBoundsException("索引超出范围");
         }
 ​
         // 2. 添加数据过程
         // 这条for循环代码是降序的,我们可以这么理解,
         // i是从最大长度开始减1,在index之前停止循环;
         for (int i = len; i > index; i--) {
             // 数组元素的值等于上一个元素的值
             this.data[i] = this.data[i - 1];
         }
 ​
         // 3. 更新数组
         // 挪位完后将元素插入指定位置
         this.data[index] = e;
         // 别忘了更新数组长度
         len++;
     }
图文解析

如: insert(2,99)

数据结构1

remove(删除)

前文说了在顺序表中插入元素会照成麻烦,当然,删除也会;

删除算法的思路:
  1. 进行判断校验(健壮性),判断索引是否超出范围
  2. 删除元素数据
  3. 更新长度
代码示例
  @Override
     public void remove(int index) {
         /**
          * index: 要删除的位置
          */
         // 1. 进行判断校验(健壮性)
         if (index < 0 || index > len-1) {
             throw new IndexOutOfBoundsException("索引超出范围");
         }
         // 2. 删除数据过程
         // i为输入的指定索引,自增
         for (int i = index; i < len-1; i++) {
             // 当前索引的元素替换为下一个索引元素的值,后面的值进行逐一挪位
             this.data[i] = this.data[i + 1];
         }
         // 3. 更新长度
         this.len--;
     }
图文解析

如: remove(3) image-20220426172010787

add(添加)

add算法的思路:

调用重写好的 insert 方法进行使用

代码示例
     @Override
     public void add(E e) {
         // len:在尾部  e: 插入的元素
         this.insert(len, e);
     }
 ​

removeFirst与 removeLast(头尾删除)

头尾删除算法的思路:

调用重写好的 remove 方法进行使用

代码示例
     @Override
     public void removeFirst() {
         /**
          * 用于删除第一个元素
          */
         this.remove(0);
     }
 ​
     @Override
     public void removeLast() {
         /**
          * 用于删除最后一个元素
          */
         this.remove(this.len - 1);
     }

get(获取)

获取指定索引元素的值

add算法的思路:

判断索引是否越界,符合则返回元素下标的值

代码示例
     @Override
     public E get(int index) {
         /**
          * index: 指定元素的索引
          */
         if (index < 0 || index > len) {
             throw new RuntimeException("索引越界");
         }
         return this.data[index];
     }

indexOf(获取索引)

获取指定元素的索引

indexof算法的思路:
  1. 判断数据是否存在
  2. 查找数据
  3. 返回响应
代码示例
    @Override
    public int indexOf(E e) {
        /**
         * index: 获取数据的位置
         */
        // 1. 判断(健壮性)
        if (e == null) {
            throw new NullPointerException("要获取的数据不存在");
        }
        // 2. 查找过程
        for (int i = 0; i < this.len; i++) {
            // equals() 方法用于判断 Number 对象与方法的参数进是否相等
            if (this.data[i].equals(e)) {
                return i;
            }
        }
        // 没找到的返回值
        return -1;
    }

size(长度)

获取数组数据的长度,直接 返回长度

代码示例
    @Override
    public int size() {
        return this.len;
    }

isEmpty(是否为空)

获取数组数据是否为空,直接 校验结果

代码示例
    @Override
    public boolean isEmpty() {
        return this.len == 0;
    }

display(执行)

用于执行打印所有元素的值

indexof算法的思路:

循环数据元素将其打印,这里为了美观将为null的值筛选了

代码示例
    @Override
    public void display() {
        for (int i = 0; i < this.data.length; i++) {
            if (this.data[i] != null) {
                System.out.print(i);
                System.out.println(this.data[i]);
            }
        }
    }

resize(执行)

这个方法为拓展,用于解决减少内存浪费

resize算法的思路:
  1. 新建数组
  2. 将原数组数据添加
  3. 将data指向新的数组
代码示例
// 私自方法 用于内部方法使用 不作为运行调用    
private void resize(int newMaxSize) {
        // 1. 创建新的数组
        Object[] newData = new Object[newMaxSize];
        // 2. 把原数组数据添加到新数组
        for (int i = 0; i < len; i++) {
            newData[i] = this.data[i];
        }
        // 3. 让data指向新的数组
        this.data = (E[]) newData;
}

更改 insert 方法与 remove 方法

/** insert 将原先的满额抛出错误该为将数组内存长度扩大两倍 */  
        // 1. 进行判断校验(健壮性)
        if (len == data.length) {
            // throw new RuntimeException("数组已满");
            this.resize(2 * this.data.length);
        }

/** remove 判断实际长度是否为给定长度的4分之一 并且 给定长度最少为20 */  
        if (len <= this.data.length / 4 && this.data.length > 20) {
            // 如果浪费过多 则砍一半
            this.resize(this.data.length / 2);
        }

打印示例

最后我们将所有方法测试一下~

以上就是 线性表之顺序存储的介绍与方法定义使用 o( ̄▽ ̄)d