线性表的顺序存储与实现

线性结构的特点是:在数据元素的非空有限集中,

  • (1)存在唯一的一个被称做“第一个”的数据元素
  • (2)存在唯一的一个被称做“最后一个”的数据元素
  • (3)除第一个之外,集合中的每个数据元素均只有一个前驱
  • (4)除最后一个之外,集合中的每个元素均只有一个后继

1.线性表定义

线性表(linear_list)是最常用且最简单的一种数据结构。线性表是n个类型相同数据元素的有限序列,通常记作(a0,a1,…,ai-1,ai,ai+1,…,an-1)。至于每个数据元素的具体含义,在不同的情况下各不相同,它可以是一个数或一个符号,也可以是一页书,甚至其他更复杂的信息。例如,26个英文字母的字母表:(A,B,C,…,Z)。

在线性表的相邻数据元素之间存在着序偶关系,即ai-1是ai的直接前驱,则ai是ai-1的直接后继。唯一没有直接前驱的元素a0一端称为表头,唯一没有后续的元素an-1一端称为表尾。

线性表中元素的个数n(n>=0)定义为线性表的长度,n = 0时称为空表。在非空表中的每个数据元素都有一个确定的位置,也即序号,例如a0的序号是0,ai的序号是i。

在这里要特别注意线性表和数组的区别:
(1)从概念上讲,线性表是一种抽象数据类型,而数组是一种具体的数据结构。线性表与数组的逻辑结构是不一样的,线性表是元素之间具有1对1的线性关系的数据元素的集合,而数组是一组数据元素到数组下标的一一映射。
(2)从物理性质上来看,数组中相邻的元素是连续地存储在内存中的;线性表只是一个抽象的数据结构,并不具有具体的物理形式。相反,线性表需要通过其他有具体物理形式的数据结构来实现。
(3)在线性表的具体实现中,表中相邻的元素不一定存储在连续的内存空间中,除非表示用数组来实现的。

线性表是一种非常灵活的数据结构,它的长度可根据需要进行增长或缩短,即对线性表的数据元素不仅可以进行访问,还可进行插入和删除等。

2.线性表的顺序存储与实现

线性表的顺序存储是用一组地址连续的存储单元依次存储线性表的数据元素。它的特点是,以数据元素的存储地址相邻来表示线性表中数据元素之间相邻的逻辑关系。每一个数据元素的存储地址和线性表的起始地址相差一个数据元素在线性表中的序号成正比的常数。因此,只要确定了线性表的起始地址,线性表中的任何一个数据元素都可以随机存取。因此线性表的的顺序存储结构是一种随机的存储结构。

线性表的顺序存储

由于高级语言中的数组也具有随机存储的特性,因此我们都是用数组来描述数据结构的顺序存储结构。在使用数组实现线性表的操作中,经常会碰到在数组中进行数据元素的查找、添加、删除等操作。

在数组中添加数据元素,通常是在数组中下标为i(0 <= i <= n)的位置添加数据元素,而将原来下标从i开始的数组中所有后续元素依次后移。整个操作如下图所示:

在数组下标i处插入元素e

如果对上述操作的执行时间进行分析,那么对于不同的插入位置,情况是不同的。最坏的情况下,在数组的第0个位置进行插入,那么数组的所有元素都要后移,时间复杂度为O(n)。

同样的,对于删除也是一样的情况,都需要移动元素,时间复杂度也是O(n),如下:

在数组下标i处删除元素

另外,对于查找,我们也需要去遍历数组,进行比较,时间复杂度也是O(n)。


在Java中,ArrayList其实就是一个顺序存储的线性表,它的底层通过数组来实现,可以根据需要动态扩展数组的大小。下面我们通过数组实现自己的线性表MyArrayList,并实现一些常用的方法,实现Java中特有的迭代器方法。如下:

package linearlist;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class MyArrayList<T> implements Iterable<T> {

    // 默认容量
    private static final int DEFAULT_CAPACITY = 10;
    // 实际大小
    private int theSize;
    // 数组
    private T[] theItems;

    public MyArrayList() {
        doClear();
    }

    public int size() {
        return theSize;
    }

    public boolean isEmpty() {
        return size() == 0;
    }

    /**
     * 清除多余空间
     */
    public void trimToSize() {
        ensureCapacity(size());
    }

    /**
     * 根据索引获取元素
     * 
     * @param index
     * @return
     */
    public T get(int index) {
        if (index < 0 || index >= size()) {
            throw new IndexOutOfBoundsException();
        }
        return theItems[index];
    }

    /**
     * 设置新值,返回旧值
     * 
     * @param index
     * @param newVal
     * @return
     */
    public T set(int index, T newVal) {
        if (index < 0 || index >= size()) {
            throw new IndexOutOfBoundsException();
        }
        T oldVal = theItems[index];
        theItems[index] = newVal;
        return oldVal;
    }

    /**
     * 根据索引添加元素
     * 
     * @param index
     * @param x
     */
    public void add(int index, T x) {
        // 扩展容量
        if (theItems.length == size()) {
            ensureCapacity(size() * 2 + 1);
        }
        // 向后移动
        for (int i = theSize; i > index; i--) {
            theItems[i] = theItems[i - 1];
        }
        // 添加
        theItems[index] = x;
        // 实际容量增加1
        theSize++;
    }

    /**
     * 默认向最后添加
     * 
     * @param x
     * @return
     */
    public boolean add(T x) {
        add(size(), x);
        return true;
    }

    /**
     * 根据索引删除节点
     * 
     * @param index
     * @return
     */
    public T remove(int index) {
        T removedItem = theItems[index];
        // 向前移动
        for (int i = index; i < size() - 1; i++) {
            theItems[i] = theItems[i + 1];
        }
        // 实际容量减小1
        theSize--;
        return removedItem;
    }

    private void doClear() {
        theSize = 0;
        ensureCapacity(DEFAULT_CAPACITY);
    }

    /**
     * 数组扩展
     * 
     * @param newCapacity
     */
    public void ensureCapacity(int newCapacity) {
        if (newCapacity < theSize) {
            return;
        }
        T[] old = theItems;
        // 泛型不能实例化,只能这么写
        theItems = (T[]) new Object[newCapacity];
        for (int i = 0; i < size(); i++) {
            theItems[i] = old[i];
        }
    }

    /**
     * 获取元素在集合中的索引
     * 
     * @param x
     * @return
     */
    public int indexOf(T x) {
        if (x == null) {
            for (int i = 0; i < theSize; i++) {
                if (theItems[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < theSize; i++) {
                if (x.equals(theItems[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 判断是否包含某个元素
     * 
     * @param x
     * @return
     */
    public boolean contains(T x) {
        return indexOf(x) >= 0;
    }

    /**
     * 迭代器方法
     */
    @Override
    public Iterator<T> iterator() {
        return new ArrayListIterator();
    }

    /**
     * 迭代器。内部类
     * 
     * @author Gavin
     *
     */
    private class ArrayListIterator implements Iterator<T> {

        private int current = 0;

        @Override
        public boolean hasNext() {
            return current < size();
        }

        @Override
        public T next() {
            // 如果已经没有元素了
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return theItems[current++];
        }

        public void remove() {
            MyArrayList.this.remove(--current);
        }
    }

    @Override
    public String toString() {
        StringBuilder line = new StringBuilder("[");
        for (int i = 0; i < size(); i++) {
            line.append(theItems[i]);
            if (i != size() - 1) {
                line.append(", ");
            } else {
                line.append("]");
            }
        }
        return line.toString();
    }
}

说明:在MyArrayList类中共有3个成员变量,DEFAULT_CAPACITY是默认的数组大小,theSize是数组中实际的元素个数,theItems就是实际的数组,用来存储线性表中的元素。另外,MyArrayList类实现了Iterable接口,可以通过迭代器来遍历元素。

对于查找、删除、和插入操作,时间复杂度都是O(n)。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值