微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

什么是顺序表?

1.什么是线性表?

  • 线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。

    在这里插入图片描述

线性表的特征:数据元素之间具有一种“一对一”的逻辑关系。

  • 一个数据元素没有前驱,这个数据元素被称为头结点;
  • 最后一个数据元素没有后继,这个数据元素被称为尾结点;
  • 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继。

线性表的分类
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。

2.什么是顺序表?

线性表的顺序存储又称为顺序表,实际上就是一个数组;它用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。第1个元素存储在线性表的起始位置,第 i 个元素的存储位置后面紧接着存储的是第 i+1个元素,称 i 为元素​在线性表中的位序。

在这里插入图片描述

3.顺序表的静态分配

顺序表在静态分配时,使用“静态”的数组存放数据元素。静态的意思是:数组的大小和空间是事先预定好的,因此一旦空间占满,如果出现新的数据,就会产生溢出,最终导致程序崩溃。
缺点:开小了不够用,开大了会存在浪费。

(1)顺序表的元素插入

public class SequenceList<T>{
    //存储元素的数组
    private T[] eles;
    //记录当前顺序表中的元素个数
    private int N;

    //构造方法
    public SequenceList(int capacity){
        //初始化数组
        this.eles=(T[])new Object[capacity];
        //初始化长度
        this.N=0;
    }

    //将一个线性表置为空表
    public void clear(){
        this.N=0;
    }

    //判断当前线性表是否为空表
    public boolean isEmpty(){
       return N==0;
    }

    //获取线性表的长度
    public int length(){
        return N;
    }

    //获取指定位置的元素
    public T get(int i){
        return eles[i];
    }

    //向线型表中添加元素t
    public void insert(T t){
        if (N==eles.length){
            throw new RuntimeException("当前表已满");
        }

        eles[N++]=t;
    }
    
}

(2)顺序表的元素指定位置插入

//在i元素处插入元素t
public void insert(int i,T t){
    if (N==eles.length){
        throw new RuntimeException("当前表已满");
    }
    
	if (i<0 || i>N){
		throw new RuntimeException("插入的位置不合法");
	}

    //先把i索引处的元素及其后面的元素依次向后移动一位
    for(int index=N;index>i;index--){
        eles[index]=eles[index-1];
    }
    //再把t元素放到i索引处即可
    eles[i]=t;

    //元素个数+1
    N++;
}

(3)顺序表的元素指定位置删除

//删除指定位置i处的元素,并返回该元素
public T remove(int i){

	if (i<0 || i>N-1){
		throw new RuntimeException("当前要删除的元素不存在");
	}
	
    //记录索引i处的值
    T current = eles[i];
    //索引i后面元素依次向前移动一位即可
    for(int index=i;index<N-1;index++){
        eles[index]=eles[index+1];
    }
    //元素个数-1
    N--;

    return current;
}

(4)顺序表的元素查找

//查找t元素第一次出现的位置
public int indexOf(T t){
	if(t==null){
		throw new RuntimeException("查找的元素不合法");
	}
	
    for(int i=0;i<N;i++){
        if (eles[i].equals(t)){
            return i;
        }
    }
    return -1;
}

(5)顺序表的元素遍历

在java中,遍历集合的方式一般都是用的是foreach循环,如果想让我们的SequenceList也能支持foreach循环,则需要做如下操作:

  1. 让SequenceList实现 Iterable接口,重写 iterator方法
  2. 在SequenceList内部提供一个内部类 SIterator,实现 Iterator接口,重写 hasNext方法和 next方法
public class SequenceList<T> implements Iterable<T>{
	
	……上述代码省略

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

    private class SIterator implements Iterator{
        private int cusor;
        public SIterator(){
            this.cusor=0;
        }
        @Override
        public boolean hasNext() {
            return cusor<N;
        }

        @Override
        public Object next() {
            return eles[cusor++];
        }
    }
}

(6)代码测试

public class SequenceListTest {

    public static void main(String[] args) {
        //创建顺序表对象
        SequenceList<String> sl = new SequenceList<String>(10);

        //测试插入
        sl.insert("小明");
        sl.insert("小比");
        sl.insert("小迪");
        sl.insert(1,"小斯");

        for (String s : sl) {
            System.out.println(s);
        }

        System.out.println("------------------------------------------");

        //测试获取
        String getResult = sl.get(1);
        System.out.println("获取索引1处的结果为:"+getResult);
        //测试删除
        String removeResult = sl.remove(1);
        System.out.println("删除的元素是:"+removeResult);
        for (String s : sl) {
            System.out.println(s);
        }
        //测试清空
        sl.clear();
        System.out.println("清空后的线性表中的元素个数为:"+sl.length());
    }
}

在这里插入图片描述

4.顺序表的动态分配

采用动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充数组空间的目的,而不需要为线性表一次性地划分所有空间。

考虑容器的容量伸缩性,其实就是改变存储数据元素的数组的大小,那我们需要考虑什么时候需要改变数组的大
小?

(1)添加元素时:

  • 添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,我们这里创建一个是原数组两倍容量的新数组存储元素。

在这里插入图片描述

//向线型表中添加元素t
public void insert(T t){
    if (N==eles.length){
        resize(2*eles.length);
    }

    eles[N++]=t;
}

//在i元素处插入元素t
public void insert(int i,T t){
    if (N==eles.length){
        resize(2*eles.length);
    }

    //先把i索引处的元素及其后面的元素依次向后移动一位
    for(int index=N;index>i;index--){
        eles[index]=eles[index-1];
    }
    //再把t元素放到i索引处即可
    eles[i]=t;

    //元素个数+1
    N++;
}

//根据参数newSize,重置eles的大小
public void resize(int newSize){
    //定义一个临时数组,指向原数组
    T[] temp=eles;
    //创建新数组
    eles=(T[])new Object[newSize];
    //把原数组的数据拷贝到新数组即可
    for(int i=0;i<N;i++){
        eles[i]=temp[i];
    }
}

(2)移除元素时:

  • 移除元素时,应该检查当前数组的大小是否太大,比如正在用100个容量的数组存储10个元素,这样就会造成内存空间的浪费,应该创建一个容量更小的数组存储元素。如果我们发现数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素。

在这里插入图片描述

//删除指定位置i处的元素,并返回该元素
public T remove(int i){
    //记录索引i处的值
    T current = eles[i];
    //索引i后面元素依次向前移动一位即可
    for(int index=i;index<N-1;index++){
        eles[index]=eles[index+1];
    }
    //元素个数-1
    N--;

    if (N<eles.length/4){
        resize(eles.length/2);
    }

    return current;
}

//根据参数newSize,重置eles的大小
public void resize(int newSize){
    //定义一个临时数组,指向原数组
    T[] temp=eles;
    //创建新数组
    eles=(T[])new Object[newSize];
    //把原数组的数据拷贝到新数组即可
    for(int i=0;i<N;i++){
        eles[i]=temp[i];
    }
}

5.顺序表的时间复杂度

  • get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
  • insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
  • remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复
    杂度为O(n);

由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显

6.ArrayList实现

java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能

原文地址:https://www.jb51.cc/wenti/3282071.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐