数据结构与算法——7.线性表——1.顺序表

2023-05-16

这篇文章我们来讲一下线性表

1.线性表概述

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

下面介绍两个术语:

前驱元素:若A元素在B元素前面,则称A为B的前驱元素

后继元素:若B元素在A元素后面,则称B为A的后继元素

线性表的特征:

  1. 数据元素之间具有“一对一”的逻辑关系
  2. 第一个数据元素没有前驱,这个数据元素称为头结点
  3. 最后一个数据元素没有后继,这个数据元素称为尾结点
  4. 除了第一个和最后一个数据元素外,其他元素有且仅有一个前驱和后继

如果把线性表用数学语言来定义,则可以表示为(a1,a2,a3,...ai-1,ai,ai+1,...an)ai-1领先与ai,ai领先与ai+1,称ai-1是ai的前驱,ai+1是ai的后继

如下图所示:

线性表的分类:

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

2.顺序表

顺序表是在计算机内存中以数组的形式保存的线性表线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素,使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系

注意:

顺序表是依靠数组来实现的,但是顺序表不是数组,二者是有区别的。数组的长度是死的,一旦定义,就不能增加或删除元素,而顺序表是活的,是可以动态的增删元素的。顺序表和数组除了这点外,其余的特性完全一样,比如都只能存同一种数据,内存中物理地址都相邻等等。可以说,顺序表是动态的数组。这点一定要区分清楚!!!

顺序表的API设计:

3.顺序表的实现

下面,我们用代码来实现一下顺序表

代码如下:


public class SequenceList<T> {

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

    //构造方法
    public SequenceList(int capacity){
        //初始化数组
        this.eles = (T[]) new Object[capacity];
        //初始化长度(默认的存储元素为0,因为刚创建初始化时,它一个元素都没有存)
        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];
    }

    //向线性表中插入元素
    public void insert(T t){
        eles[N++] = t;
    }

    //往指定索引i处添加元素t
    public void insert(int i,T t){
        //先把i索引处的元素及其后面的元素依次向后移动一位
        for (int index = N-1; index >i ; index--) {
            eles[index] = eles[index - 1];
        }
        //再把t元素方到i索引处
        eles[i] = t;
    }

    //删除指定位置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];
        }
        //元素个数减一
        N--;
        return current;
    }

    //查找元素t第一次出现的位置
    public int indexOf(T t){
        for (int i = 0; i < N; i++) {
            if (eles[i] == t)
                return i;
        }
        return -1;
    }
}

前提:

这里事先先说明一下一些情况,这样后面解释类中的方法是如何写的时候就比较容易理解。

下面,跟着我的描述来思考。

我们要做什么?我们要创建一个顺序表。前面说了,这个顺序表就是一个动态数组,它可以实现元素的插入啊,删除啊,求长度啊等等等等一系列操作(这些操作数组无法完成)。那么这个顺序表的本质是什么?是数组。数组的本质是什么?数组的本质是一块连续空间,然后将这些空间分割为一系列小块,然后在这些小块中放数据。哦,那我们就知道了,顺序表的本质也是在内存中开辟一块空间,然后将大空间分割为小空间,然后在小空间里面放数据。只不过顺序表可以实现一些插入删除的操作罢了。至此,顺序表的本质我们明白了。

下面回到代码中来,我们来解释代码。

成员变量:

我们顺序表的成员变量有一个数组,一个整型数据N,数组好理解,放数据用的(即顺序表的核心),那么N是用来干什么的呢?N是用来记录顺序表中的数据个数用的!!! 这时,就有人说了“那N就是第6行数组的长度,即顺序表中数组的长度呗”,错!!!N不是顺序表中数组的长度!!!它们是两个不同的概念!!!顺序表中数组的长度在我们初始化的时候给出,N不是顺序表中数组的长度(再次强调!!!),N是顺序表中有效数据,即用户放入顺序表中数据的个数!!!

这里又涉及到对象的初始化问题了,简单的说一下吧。

当我们在测试类中,new出一个对象的时候,jvm会根据类中数组的类型对其赋予默认的初始值,比如,我们指定泛型T为int类型的,那么在我们new出对象的时候,对象内部的数组eles中的每一个元素都有其默认初始值0,这些值是无效的,而这些值的个数即为我们数组的长度。但是我们这是顺序表,我们需要用它来存数据,假设,我们对象内部数组eles的长度为5,即数组长度为5,但是我们在这个顺序表中存了3个数据,那么顺序表的有效数据就是3,这个3就是N的值,即顺序表的长度。

以上就是对N与数组长度的说明。这点一定要清楚

下面,我们再来逐个分析类中的每个方法。

3.1 构造函数

顺序表的构造方法如下:

解析:

我们需要传的仅仅只是顺序表的长度,这个顺序表的长度就是顺序表内部数组的长度,所以就有了第13行,但是,我们用的是泛型,我们不能new T[ capacity] 啊,所以我们只能new最大的类——object,类型不一致啊,没关系,前面加个强制类型转换就行。这就是第13行的逻辑

此时,我们只是初始化好了顺序表,表中没有放任何数据,那么N的值当然为0了。这就是第15行的逻辑

3.2 置空方法

代码如下:

解析:

首先,我们需要将这个顺序表当做一个整体看待!!!这个N就是记录里面存储数据的个数的,顺序表为空,那么顺序表中存储元素的个数就为0了,所以直接设置N=0就行

有人就要问了,那数组中元素怎么办呢?前面说了,将顺序表当做一个整体看待,它就是一个黑匣子,你看不到里面的内容。所以,完全可以不用管数组中的那些元素。你只需要知道,你使用时,在指定的位置存储了什么,即有限个位置的确定值就行,至于其他位置,它存的是多少,关你何事?

3.3 判断是否为空

代码如下:

解析:

顺序表为空,那就是N=0;所以直接判断N是否为0就行,这里用来隐式类型转换,返回Boolean类型的值 

3.4 获取表长度

代码如下:

解析:

顺序表的长度就是N的值,所以就直接返回N就行 

3.5 获取指定位置的值

代码如下:

解析:

元素是存在数组中的,并且在有限位中,数组的索引和顺序表的索引是一致的,所以获取顺序表指定位置的元素,就是获取数组指定位置的元素

3.6 插入元素

代码如下:

解析:

很简单啊,顺序表索引加1,然后在里面放指定的元素 

3.7 在指定位置插入元素

代码如下:

解析:

在指定位置插入指定元素,因为顺序表的本质是数组,所以就是在数组的中间插入元素。那就简单了啊,在 索引 i 处的元素及其后面的元素依次向后移动一位,一个for循环就能解决,然后在指定位置处插入指定的元素。很简单的事。

注意:这两个插入用来方法的重载,区别是参数不同。

3.8 删除并返回指定处的元素

代码如下:

解析:

这个也很简单啊。删除并返回指定位置的元素,那就先用个变量记录指定位置的元素,然后把这个元素后面的所有元素都往前移动一位,然后将那个记录元素的变量返回就行了啊,so easy! 

3.9 找到元素第一次出现的位置

代码如下:

解析:

一个for循环加一个 if 判断的事,找到了返回指定位置的索引,找不到,返回-1 

3.10 测试

下面,我们写个测试类来测试一下:



public class SequenceTest {
    public static void main(String[] args) {
        //创建顺序表对象
        SequenceList<String> s1 = new SequenceList<>(10);
        //测试插入
        s1.insert("a");
        s1.insert("b");
        s1.insert("c");
        s1.insert(1,"aaaaa");

        //测试获取
        String getResult = s1.get(1);
        System.out.println("1处的元素为:"+getResult);

        //测试删除
        String removeResult = s1.remove(0);
        System.out.println("删除的元素是:"+removeResult);

        //测试清空
        s1.clear();
        System.out.println("清空后的顺序表中的元素个数为:"+s1.length());
    }
}

测试结果如下图:

 完美! 

4.顺序表的遍历

一般作为容器存储数据的数据结构,都需要向外部提供遍历的方式。所以,下面,我们也来看一下顺序遍历方式

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

  1. 让SequenceList实现 Iterable 接口,重写iterator方法;
  2. 在SequenceList内部提供一个内部类SIterable,实现iterator接口,重写hasNext方法和next方法

代码如下:



import java.util.Iterator;

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

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

    //构造方法
    public SequenceList(int capacity){
        //初始化数组
        this.eles = (T[]) new Object[capacity];
        //初始化长度(默认的存储元素为0,因为刚创建初始化时,它一个元素都没有存)
        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];
    }

    //向线性表中插入元素
    public void insert(T t){
        eles[N++] = t;
    }

    //往指定索引i处添加元素t
    public void insert(int i,T t){
        //先把i索引处的元素及其后面的元素依次向后移动一位
        for (int index = N; index >i ; index--) {
            eles[index] = eles[index - 1];
        }
        //再把t元素方到i索引处
        eles[i] = t;
        //元素个数加1
        N++;
    }

    //删除指定位置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];
        }
        //元素个数减一
        N--;
        return current;
    }

    //查找元素t第一次出现的位置
    public int indexOf(T t){
        for (int i = 0; i < N; i++) {
            if (eles[i] == t)
                return i;
        }
        return -1;
    }

    //遍历
    @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++];
        }
    }

}

遍历的原理上面讲了,这里就不过多赘述了。

注意,在指定位置添加元素的方法做出了一点小修改,最终方法如下图:

画个图,自己看一下就明白了。

5.顺序表的容量可变

我们上面实现的顺序表,真的是一个完整是顺序表吗?Of Course Not !!Why??Because 它的容量不可变啊。就比如说,我定义它的长度为5,但是我偏要存10个数据,行吗?Of Course Not Too !!你看java中的List列表是这样的?当然不是了。所以,它还缺一个容量可变的功能。下面,让我们来实现它!

代码如下:


import java.util.Iterator;

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

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

    //构造方法
    public SequenceList(int capacity){
        //初始化数组
        this.eles = (T[]) new Object[capacity];
        //初始化长度(默认的存储元素为0,因为刚创建初始化时,它一个元素都没有存)
        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];
    }

    //向线性表中插入元素
    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++;
    }

    //删除指定位置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];
        }
        //元素个数减一
        N--;
        if (N<eles.length/4){
            reSize(eles.length/2);
        }
        return current;
    }

    //查找元素t第一次出现的位置
    public int indexOf(T t){
        for (int i = 0; i < N; i++) {
            if (eles[i] == t)
                return i;
        }
        return -1;
    }

    //根据参数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];
        }
    }

    //遍历
    @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++];
        }
    }

}

解析:

什么时候需要容量改变?插入元素或删除元素时。插入好理解,删除怎么说?假设,我们定义数组长度为100w,一开始也存储了100w个数据,但是我们删啊删,删到最后只剩1个数据,你还要占用100w个数据空间吗?这不浪费空间吗?所以删除时也要改变容量。

怎么改变?简单,改变顺序表中数组的大小就行,怎么变?变为原来的2倍就行。所以思路清晰了。我们先定义临时数组,存储eles的值,然后将eles的大小变为原来的2倍,然后再将临时数组中的值传给新的eles就行,so easy!

然后就是在插入元素和删除元素时判断一下就行啦,很简单。

代码上面给了,下面测试一下:

结果如下图所示:

 

完美!

6.小结

 这篇文章主要讲了顺序表。会者不难,难者不会。还是那句话:容量不可变前,顺序表是低级版的动态数组;容量可变后,顺序表是完全版的动态数组。它的底层就是靠数组实现的。一个数组的长度,一个记录顺序表元素个数的变量,在一定的范围内(顺序表元素个数 <= 数组的长度),二者相同,即顺序表和数组的索引相同,所以对顺序表的操作就是对数组的操作!!!就这,没啥,so easy的事。

最后,给个图。看着图,瞅着代码,多思考思考:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构与算法——7.线性表——1.顺序表 的相关文章

随机推荐

  • 嵌入式面试分享3—IIC,SPI,TCP,UDP

    IIC上拉电阻的作用 xff1f 保证数据的稳定 xff0c 减少干扰 xff0c 为了避免总线信号的混乱 xff0c IIC的空闲状态只能有外部上拉 xff0c 而此时空闲设备被拉到了高阻态 xff0c 也就是相当于断路 xff0c 整个
  • 什么是事件流以及事件流的应用场景

    一 事件流的定义 xff1a 页面触发一个事件 xff0c 会按照一定的顺序响应事件 xff0c 事件的响应过程为事件流 通俗来讲就是网页对点击事件的排序就是事件流 二 事件流的分类 1 事件冒泡 从明确事件源到不明确事件源一次向上响应 2
  • 【数据库(SQL)】总结篇

    C 43 43 语言部分 总结篇 操作系统 总结篇 计算机网络 总结篇 设计模式 总结篇 本文目录 1 SQL1 1 介绍一下数据库分页1 2 介绍一下SQL中的聚合函数1 3 表跟表是怎么关联的1 4 说一说你对外连接的了解1 5 说说S
  • 网络编程 _pthread/fork

    1 查看while源代码 include lt stdlib h gt include lt stdio h gt include lt errno h gt include lt string h gt include lt netdb
  • [ROS学习笔记]ROS中使用激光雷达(RPLIDAR)

    RPLIDAR是低成本的二维雷达解决方案 xff0c 由SlamTec公司的RoboPeak团队开发 xff0c 本次学习用的是RPLidar A1型号激光雷达 xff0c 它能扫描360 xff0c 6米半径的范围它适合用于构建地图 xf
  • gazebo仿真环境搭建+配置+小车运动仿真

    ubuntu版本 xff1a 20 04 gazebo版本 xff1a gazebo11 1 打开gazebo 终端输入 gazebo 或者直接点gazebo软件图标 2 前往建筑编辑器 点击上方 Edit Buiding Edit 或者快
  • 总线学习(BUS)

    1 总线的概念 总线是指计算机设备和设备之间传输信息的公共数据通道 总线是连接计算机硬件系统内多种设备的通信线路 xff0c 一个重要特征是由总线上的所有设备共享 xff0c 可以将计算机系统的多种设备连接到总线上 如果是某两个设备或设备之
  • 贝叶斯滤波与卡尔曼滤波 一

    什么是滤波 xff1f 无论是建立的模型方程来推测出的数据 xff0c 还是用传感器直接测量出的数据 xff0c 总不是那么理想的拟合曲线 xff0c 总存在偏差 方差 xff0c 而滤波就是为了尽可能的减小这些方差 xff0c 减少噪声的
  • JS入门笔记:获取文档对象

    DOM获取元素的方法 1 getElementById 参数 a 参数为元素的id xff0c 并且是字符串形式 b 返回的是一个元素对象 c 使用console dir 打印获取的元素可以更好的察看其相关属性和方法 2 根据标签名来获取元
  • VCC、VDD、VSS、GND区别

    一 具体分析 xff1a 1 在电子电路中 xff0c VCC是电路的供电电压 VDD是芯片的工作电压 2 在普通的电子电路中 xff0c 一般VCC gt VDD 3 在COMS器件中 xff0c VDD是CMOS的漏极引脚 xff0c
  • 立创EDA入门

    如有错误 xff0c 感谢指正 如有错误 xff0c 感谢指正 xff0c 请私信博主 xff0c 有辛苦红包 xff0c 拜 一字之师 请根据目录寻找自己需要的段落 导语 xff1a 本博客为个人整理EDA学习记录帖 xff0c 如有错误
  • DockerFile构建过程

    DockerFile构建过程 了解镜像加载原理 Docker镜像加载原理 UnionFS 联合文件系统 xff09 UnionFS 联合文件系统 xff09 Union文件系统 UnionFS 是一种分层 轻量级并且高性能的文件系统 xff
  • 杂记——1.Navicat连接远程数据库时出现的2003错误

    1 问题描述 当我们用Navicat连接自己的远程数据库时 xff0c 在IP地址与密码都输入正确的情况下 xff0c 点击测试连接时有时会出现以下情况 导致连接失败 xff0c 这就会困扰许多新手小伙伴 xff0c 为什么我的IP与密码都
  • 杂记——9.eclipse启动Tomcat

    这篇文章 xff0c 我们简单的来说一下如何用eclipse启动Tomcat 具体步骤如下所述 第一步 xff1a 打开eclipse xff1a 第二步 xff1a 点击上方的 Window 第三步 xff1a 点击Preferences
  • 杂记——12.腾讯会议使用OBS虚拟摄像头实现多屏幕共享的解决方法

    这篇文章将来讲述一下腾讯会议如何使用OBS虚拟摄像头来实现多屏幕共享 目录 1 下载地址 2 下载与安装 2 1 OBS Studio的下载与安装 2 2 OBS VirtualCam 虚拟摄像头插件的下载与安装 3 运行与操作 4 小问题
  • 一种基于OpenCV的陪护机器人

    近年来人工智能不断发展 xff0c 从工业领域扩散到多个领域 xff0c 功能逐渐变多 xff0c 从以前的工业机器人到现如今的服务类机器人 xff0c 人工智能在不断提升与完善 本文针对老年人 xff0c 儿童 xff0c 病人等实际的应
  • 操作系统——13.处理机调度的时机、切换与过程、方式

    这篇文章我们继续来学习进程调度的相关知识 目录 1 概述2 2 进程调度的时机 3 进程调度的方式 4 进程的切换与过程 5 小结 1 概述2 首先 xff0c 我们来看一下本节类容的大体框架 xff1a 2 进程调度的时机 进程调度 xf
  • 开发手册——一、编程规约_1.命名风格

    这篇文章主要梳理了在java的实际开发过程中的编程规范问题 本篇文章主要借鉴于 阿里巴巴java开发手册终极版 下面我们一起来看一下吧 1 强制 代码中的命名均不能以下划线或美元符号开始 xff0c 也不能以下划线或美元符号结束 反例 xf
  • 杂记——16.idea中导入maven项目

    这篇文章我们来讲一下如何从Gitee上拉取项目 xff0c 并将该项目导入到idea中 目录 1 拉取项目 2 idea导入项目 3 更改相关的配置 3 1更改maven仓库 3 2更改数据库的连接池 1 拉取项目 第一步 xff1a 找到
  • 数据结构与算法——7.线性表——1.顺序表

    这篇文章我们来讲一下线性表 1 线性表概述 线性表是最基本 最简单 xff0c 也是最常用的一种数据结构 一个线性表是n个具有相同特性的数据元素的有限序列 下面介绍两个术语 xff1a 前驱元素 xff1a 若A元素在B元素前面 xff0c