线性表的顺序存储结构Java

线性表的顺序存储

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
在这里插入图片描述
线性表(a1,a2,a3,…,an)的顺序存储示意图如上图。
首先,介绍一下线性表的顺序存储结构:
Java语言(其他语言一样)种用一维数组来实现顺序存储结构,即第一个元素存放到数组中下标为0的位置上,当然也可以是下标为1,这个都可以。然后把线性表相邻的元素存储在数组中相邻的位置。

在本文,不管是我们的ArrayList和LinkedList,我们都会继承我们事先准备好的List接口,让这两个类去实现List接口中的方法。下面先看一下List接口代码。

import java.util.Iterator;
/**
 * @author 七夏
 * @param <E>
 */
public interface List<E> extends Iterable<E> {
    int getSize();  //获取线性表中元素的有效个数
    boolean isEmpty();  //判断线性表是否为空表
    void add(int index,E e);    //在线性表指定的index角标出插入元素e
    void addFirst(E e);    //在线性表的表头处插入元素e
    void addLast(E e);  //在线性表的表尾处插入元素
    E get(int index);   //获取线性表中的指定角标index处的元素
    E getFirst();   //获取表头的元素
    E getLast();    //获取表尾元素
    void set(int index,E e);    //修改线性表中指定角标index处的元素
    boolean contains(E e);  //判断线性表中是否包含元素e
    int find(E e);  //查找元素e的角标(从左到右默认第一个出现的元素角标)
    E remove(int index);    //删除并返回线性表中指定角标index的元素
    E removeFirst();    //删除并返回表头元素
    E removeLast(); //删除并返回表尾元素
    void removeElement(E e);    //删除指定元素
    void clear();   //清空线性表
}

该接口我们继承了Iterable接口,所以在我们的ArrayList类中,我们要实现其Iterable中的方法Iterator(),即迭代器。并且我们在实现Iterator这个方法的时候是写一个内部类来实现Iterator这个接口中的haxNext和next方法。

线性表的顺序存储结构代码

package DS01.动态数组;
import java.util.Iterator;
/**
 * @author 七夏
 * @version  1.0
 * @param <E>
 * 创建线性表List的顺序存储结构实现类ArrayList
 */
public class ArrayList<E>implements List<E>{
    //声明一个E类型的一维数组
    private E[] data;
    //维护元素个数
    private int size;
    //默认最大容量为10
    private static int DEFAULT_CAPACITY = 10;
    //创建一个为默认容量的顺序表
    public ArrayList(){
        this(DEFAULT_CAPACITY);
    }
    //创建一个容量由用户指定的顺序表
    public ArrayList(int capacity){
    	//如果用户指定的容量小于0的话,将会抛出一个异常
    	//下面的代码中的抛出异常大致类似
        if(capacity < 0){
            throw new IllegalArgumentException("初始容量不能为负数:"+capacity);
        }
        data = (E[])(new Object[capacity]);
        this.size = 0;
    }
    //用户传入一个数组,将该数组封装成一个顺序表
    public ArrayList(E[] data){
        if(data == null){
            throw new IllegalArgumentException("数组不能为空:null");
        }
        this.data = (E[])(new Object[data.length]);
        for (int i = 0; i < data.length; i++) {
            this.data[i] = data[i];
        }
        this.size = data.length;
    }
}

对线性表有很多操作,文章中主要讲述增,删,查和扩容(修改和查找类似,只要“查”到对应元素将其修改即可)。


  • **线性表中,增的最好情况,时间复杂度为O(1);最坏情况下为O(n),因为它平均移动一半的元素。**插入数据时的实现过程如下图:
    插入前:在这里插入图片描述
    插入后:
    在这里插入图片描述
    @Override
    public void add(int index, E e) {
        if(index < 0 || index > size){
            throw new IllegalArgumentException("角标越界");
        }
        if(size == data.length){
            //扩容,扩为原来的2倍
            resize(data.length*2);
        }
        for(int i = this.size;i > index;i--){
            data[i] = data[i-1];
        }
        data[index] = e;
        size++;
    }

  • **线性表中,增的最好情况,时间复杂度为O(1);最坏情况下为O(n),因为它平均移动一半的元素。**删除的过程和插入相似,只是元素移动的位置相反而已。
    @Override
    public E remove(int index) {
        if(isEmpty()){
            throw new IllegalArgumentException("线性表为空");
        }
        //判断传入的角标是否越界
        if(index < 0 || index >=size){
            throw new IllegalArgumentException("角标越界");
        }
        E res = data[index];
        for (int i = index+1; i < size; i++) {
            data[i-1] = data[i];
        }
        size--;
        if(size <= data.length/4 && data.length/2 >= 10){
            resize(data.length/2);
        }
        return res;
    }

  • 线性表对于查找来说很简单,因为是数组,所以就要看我们需要查找的“标志”是什么。比如,我们想要找到下标为i的元素,或者是我们想要找到一个确定的元素。在此,文章就先说说一下寻找一个确定的元素的情况。
    @Override
    public int find(E e) {
        if(isEmpty()){
            throw new IllegalArgumentException("线性表为空");
        }
        for (int i = 0; i < size; i++) {
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1;
    }
  • “扩容”
    对于顺序存储的线性表,是用数组来实现的,因为数组在开始创建的时候,其空间的大小可能会远远大于需要存储的元素,所以,我们需要根据存储的元素来大概确定一下数组的容量。并且扩容的方法我们不对外界提供,因此设置为private。
    private void resize(int newLength) {
        E[] newData = (E[])(new Object[newLength]);
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        //将新的newData数组的地址赋予原先data数组
        data = newData;
    }

以上,我们不仅仅有增、删和查,我们还有查找元素,判空,获取有效元素个数,toString等等方法,我们在下面将一一列出(toString方法的重写中,我们用到了StringBuilder)。

这里我们的迭代器是,先定义一个index=-1,让index像遍历数组一样的方式去重写hasNext和next这两个方法:

  • ①hasNext函数中,先判断index是否小于size-1,意思就是,index角标是否在顺序表的范围之内(如果index等于size-1时,也就是说此时的index已经是该顺序表的最后一个元素,则说面已经没有下一个元素了);
  • ②next方法是,获取index++后的元素(如果,index=-1时,我们就获取它的index++后的元素值,即角标为0的元素)。
	@Override
    public int getSize() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }
    @Override
    public void addFirst(E e) {
        add(0,e);
    }

    @Override
    public void addLast(E e) {
        add(size,e);
    }

    @Override
    public E get(int index) {
        if(isEmpty()){
            throw new IllegalArgumentException("线性表为空");
        }
        if(index < 0 || index >size){
            throw new IllegalArgumentException("角标越界");
        }
        return data[index];
    }

    @Override
    public E getFirst() {
        return get(0);
    }

    @Override
    public E getLast() {
        return get(size-1);
    }

    @Override
    public void set(int index, E e) {
        if(isEmpty()){
            throw new IllegalArgumentException("线性表为空");
        }
        if(index < 0 || index >=size){
            throw new IllegalArgumentException("角标越界");
        }
        data[index] = e;
    }

    @Override
    public boolean contains(E e) {
        return find(e) != -1;
    }

    @Override
    public int find(E e) {
        if(isEmpty()){
            throw new IllegalArgumentException("线性表为空");
        }
        for (int i = 0; i < size; i++) {
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1;
    }
    @Override
    public E removeFirst() {
        return remove(0);
    }

    @Override
    public E removeLast() {
        return remove(size-1);
    }

    @Override
    public void removeElement(E e) {
        int index = find(e);
        if(index != -1){
            remove(index);
        }else{
            throw new IllegalArgumentException("元素不存在");
        }
    }

    @Override
    public void clear() {
        size = 0;
        data = (E[])(new Object[DEFAULT_CAPACITY]);
    }
        public int getCapacity(){
        return data.length;
    }

    @Override
    public String toString() {
        /**
         * ArrayList:10/20
         * [1,2,3,4,5,6,7,8,9,10]
         */
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("ArrayList: %d/%d \n",size,data.length));
        sb.append('[');
        if(isEmpty()){
            sb.append(']');
        }else{
            for (int i = 0; i < size; i++) {
                sb.append(data[i]);
                if(i == size-1){
                    sb.append(']');
                }else{
                    sb.append(',');
                }
            }
        }
        return sb.toString();
    }

    @Override
    public Iterator<E> iterator(){
        return new ArrayListIterator();
    }
    private class ArrayListIterator implements Iterator{
        private int index = -1;
        @Override
        public boolean hasNext() {
            return index < size-1;
        }

        @Override
        public E next() {
            index++;
            return data[index];
        }
    }

在顺序存储结构的线性表中,为什么说删除和增加元素都是平均O(n)的时间复杂度呢?
这是因为,如果要插入到第i个位置或者删除第i个位置的元素,则需要移动n-i个元素位置,在概率论中,我们删除或者增加的概率都是相同的,增和删的位置在前,则移动的多,相反移动的少,最终,平均移动的次数和最中间那个元素移动的次数相等,为(n-1)/2。所以时间复杂度为O(n)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值