数据结构——SimpleArrayList和SimpleLinkedList的实现

本文介绍了SimpleArrayList和SimpleLinkedList两种数据结构的实现。SimpleArrayList基于数组,支持快速随机访问但插入删除效率较低,实现了Iterable接口。SimpleLinkedList采用双链表,适合频繁插入删除操作,同时实现了迭代器失效机制。
摘要由CSDN通过智能技术生成

SimpleArrayList为用数组存储数据元素的方式实现的表,它是在SimpleList的基础上改进的。
SimpleLinkedList采取的是双链表的实现方式。

SimpleArrayList

数组实现的表的特性:

  1. 可以非常快的随机访问表中第x个元素的值,所以当我们需要比较频繁的对数据进行随机访问存取的时候,这不失为一个很好的选择。
  2. 但是它的缺点也很明显,就是对于元素的插入和删除的花销都是线性增长的。因为无论是插入还是删除都需要涉及到元素的移位操作。其最坏的结果就是要在表的头部插入或删除元素,这需要把所有的元素都进行移位操作。
  3. 因为使用数组存储的方式,如果要实现可动态扩展数组大小的话,就需要一些额外的花销。当插入元素的时候发现数组大小不够用了的时候就要对数组进行扩展,同时还要拷贝旧数组中的元素到新的数组中。

在这个SimpleArrayList中将实现Iterabel接口,但是没有实现当元素被修改时迭代器也失效的效果,这个留到SimpeLinkedList中实现。

属性

private int[] elements : 数组元素
private int size : 表中元素个数
private final static int DEFAULT_CAPACITY : 默认表大小

构造器

有两个构造器,一个是无参的构造器,将构造一个默认大小的表;另一个则是可以初始化表容量大小的构造器。

public SimpleArrayList(){
    clear();
}

public SimpleArrayList(int capacity) throws IllegalArgumentException{
    if(capacity < 0)
        throw new IllegalArgumentException("Capacity should be positive number.");
    size = 0;
    ensureCapacity(capacity);
}

方法

SimpleArrayList中大部分方法实现和SimpleList很类似,就不赘述了。

ensureCapacity(): 扩展数组的大小,并且拷贝原数组中的元素。在这个函数中只有当新设置的容器大小至少等于当前容器大小的时候才有效,否则将忽略这个请求。

public void ensureCapacity(int newCapacity){
    /* 判断新表大小是否大于等于当前容器中元素个数 */
    if(newCapacity < size())
        return;
    T[] old = elements;
    /* 创建新数组并拷贝旧数组元素 */
    elements = (T[]) new Object[newCapacity];
    for(int i=0; i < size(); i++)
        elements[i] = old[i];
}

trimToSize():本函数可以释放数组中多余的空间,将数组的大小设置为当前表中元素个数。当我们已经确定了表的大小几乎不用发生改变了的时候可以进行该操作释放空间资源。

public void trimToSize(){
    ensureCapacity(size());
}

add():为插入元素的函数。 当数组大小不够用时会扩展数组。
append():为在尾部追加元素的操作,这个操作不涉及元素的移位。

public void add(int idx, T ele) throws ArrayIndexOutOfBoundsException{
    if(idx < 0 || idx > size())
        throw new ArrayIndexOutOfBoundsException("Idx is out of bounds.");
    /* 判断表是否已满,实则进行扩容操作  */
    if(size() == elements.length)
        ensureCapacity(2 * size() + 1);
    /* 移动idx后的所有元素 */
    for(int i=size(); i > idx; i--)
        elements[i] = elements[i-1];
    /* 设置插入的元素值 */
    elements[idx] = ele;
    size++;
}

public void append(T ele){
    add(size(), ele);
}

remove():移除元素。

public T remove(int idx) throws ArrayIndexOutOfBoundsException{
    if(idx < 0 || idx >= size())
        throw new ArrayIndexOutOfBoundsException("Idx is out of bounds.");
    /* 保存旧元素值 */
    T removeElement = elements[idx];
    /* idx后面的元素向前移动一位  */
    for(int i=idx; i<size()-1; i++)
        elements[i] = elements[i+1];
    size--;
    return removeElement;
}

实现了Iterable接口的方法,返回一个Iterator对象。我自己实现了一个SimpleArrayListIterator类来实现迭代器。

@Override
public Iterator<T> iterator() {
    return new SimpleArrayListIterator();
}

内部类(SimpleArrayListIterator)

private class SimpleArrayListIterator implements Iterator<T>{
    private int current = 0;
    @Override
    public boolean hasNext() {
        return current != size()? true : false;
    }

    @Override
    public T next() {
        /* 判断是否到尾部,即其后还有没有元素 */
        if(!hasNext())
            throw new NoSuchElementException("Has no next object.");
        return elements[current++];
    }

    @Override
    public void remove(){
        /* 前置减,先计算后取值,所以删除的是刚调用next()后所指向的元素 */
        SimpleArrayList.this.remove(--current);
    }
}

SimpleLinkedList

链式实现的表的结构:

  1. 链表不能进行元素的随机访问,每次需要访问某个特定元素的时候都要从表头会表尾巴开始查找(双链表可以在表尾开始查找元素)。所以这些操作的花销都是线性增长的,并不鼓励对链表进行频繁的get, set操作。
  2. 链表最大的优点就是对于已知位置的插入和删除操作是非常快的,只需要常数时间就可以完成,而不需要对任何元素进行移位操作。链表适合于要对表频繁的进行插入和删除操作的情况。
  3. 链表采用的链式结构,节点都是非连续的存储在内存中的,只要内存空间充足,表的大小就可以看作是无限的。所以不需要额外的调整表大小的花销。

SimpleLinkedList采用了双链表的实现方式,并且在SimpleArrayList的基础上,不但实现了Iterable接口,还通过新增一个modCount变量来实现迭代器失效的效果。

成员变量

private int size:表元素个数;
private int modCount:修改记录数,迭代器用来判断迭代期间是否发生修改;
private Node<T> head:指向头节点的引用;
private Node<T> tail:指向尾结点的引用。

构造器

public SimpleLinkedList(){
    clear();
}

函数

clear():重置链表为空表。

public void clear(){
    head = new Node<T>(null, null, null);
    tail = new Node<T>(null, head, null);
    head.next = tail;
    size = 0;
    modCount++;
}

获取表中元素个数和判断是否是空表。

public int size(){
    return size;
}

public boolean isEmpty(){
    return size() == 0;
    //return head.next == tail;
}

getNode():在链表中定位指定位置的元素,这里先判断要定位的元素处于元素前半部分还是后半部分,然后根据元素所在位置选择在头部还是尾部开始定位元素。

/**
 * 定位到索引idx所表示的结点,idx取值范围是0~size
 * @param idx 节点索引位置
 * @return 返回idx位置的节点对象
 * @throws IndexOutOfBoundsException 
 */
public Node<T> getNode(int idx) throws IndexOutOfBoundsException{
    /* 判断idx是否越界,是则抛出异常 */
    if(idx < 0 || idx > size())
        throw new IndexOutOfBoundsException();

    Node<T> p;
    /* 如果要定位的元素在前半部分,则从前半部分开始定位s */
    if(idx < size()/2){
        p = head.next;
        for(int i=0; i < idx; i++)
            p = p.next;
    }
    /* 元素在后半部分,则从尾部开始定位 */
    else{
        p = tail;
        for(int i = size(); i > idx; i--
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值