二.线性表

线性表的概述

**线性表的概念 **线性表属于最基本、最简单、也是最常用的一种数据结构,从逻辑上划分它属于线性结构。一 个线性表是由 n 个具有相同特性的数据元素组成的有限序列,数据元素之间具有一种线性的或“一对一”的逻辑关系,如下图所示:
image.png
从严谨的角度上来讲,线性表应该满足以下三个要求:
(1) 第一个数据元素没有直接前驱,这个数据元素被称为开始节点;
(2) 最后一个数据元素没有直接后继,这个数据元素被称为终端节点;
(3) 除了第一个和最后一个数据元素外,其它数据元素有且仅有一个直接前驱和一个直接后继。
**线性表的特点 **
(1) 顺序性
在线性表中,相邻数据元素之间存在着序偶关系,也就是存在着先后关系。
(2) 相同数据类型
在线性表中,每一个数据元素都属于相同数据类型。相同数据类型意味着在内存中存储时,每个元素会占用相同的内存空间,便于后续的查询定位。
(3) 有限性
在线性表中,数据元素的个数 n 就是为线性表的长度,n 是一个有限值。当 n=0 时线性表为空 表。在非空的线性表中每个数据元素在线性表中都有唯一确定的序号,例如第一个元素的序号是 0,第 i 个元素的序号为 i-1。在一个具有 n > 0 个数据元素的线性表中,数据元素序号的范围是[0, n-1]。
**线性表的存储结构 **
线性表拥有两种不同的存储结构,分别为:
(1) 顺序存储结构(顺序表):顺序表是采用一组地址连续的存储单元来依次存放线性表中的各个元素。
image.png
(2) 链式存储结构(链表):链表中的存储元素的地址不一定是连续的,元素节点中存放数据元素 以及相邻元素的地址信息。
image.png

顺序表

顺序表的特点

**顺序表根据索引查询元素的特点 **
因为顺序表(此处,也就是数组)内存空间是连续的,并且存储的数据属于相同数据类型。 因此,我们可以通过数组的“首地址+索引”就能快速的找到数组中对应索引的元素值,从而得出数 组的优点:查找快
image.png
索引操作数组原理:数组首地址 + 存放数据的字节数*索引。
**顺序表删除元素的特点 **
需求:删除数组{11, 22, 33, 44, 55, 66}索引为 2 的元素,删除后的数组为:{11, 22, 44, 55, 66}。
实现步骤:
(1) 把删除索引之后的元素往前挪动一位(从前往后)。
(2) 把数组的最后一个元素设置为默认值
image.png
**顺序表插入元素的特点 **
需求:在数组{11, 22, 44, 55, 66}索引为 2 的位置插入元素 33,插入后的数组为:
{11, 22, 33, 44, 55, 66}。
实现步骤:
(1) 先检查数组是否需要扩容。如果数组的空间长度和数组添加元素个数相等,则需做扩容操作。
1.定义一个空间长度更大的新数组。
2.把原数组中的元素全部拷贝到新数组中。3.让原数组指向新数组,也就是让原数组保存新数组的地址值。
(2) 把插入索引及其之后的元素往后挪动一位(从后往前挪动)。
(3) 把插入的元素赋值到插入索引的位置中
image.png
**顺序表优劣势的总结 **
优点:无须关心表中元素之间的关系,所以不用增加额外的存储空间;可以根据索引快速地操作表中任意位置的元素,并且操作任何一个元素的耗时都是一样。
缺点:插入和删除操作需要移动大量元素。使用前需事先分配好内存空间,当线性表长度变化较大时,难以确定存储空间的容量。分配空间过大会造成存储空间的巨大浪费,分配空间过小,难以适应问题的需求

模拟arraylist

package com.more;

import java.util.ArrayList;

public class MyArrayList {

    /**
     * 定义一个数组,用于保存集合中的数据
     */
    private Object[] elementData;

    /**
     * 实际数组存放元素的个数
     */
    private int size;


    public int size() {
        return this.size;
    }

    public MyArrayList() {
        //默认为10
        this.elementData = new Object[10];

    }

    public MyArrayList(int size) {
        if (size < 0) {
            throw new RuntimeException("参数不合法,size=" + size);
        }
        this.elementData = new Object[size];
    }


    public void add(Object obj) {
        ensureCapacityInternal();
        elementData[size] = obj;
        size++;
    }


    public Object get(int index) {
// 1.判断索引是否合法,合法的取值范围:[0, size - 1]
        rangeCheck(index);
// 2.根据索引获取对应的元素值
        return elementData[index];
    }


    /**
     * 根据索引删除元素
     */
    public void remove(int index) {
        rangeCheck(index);
        for (int i = index; i < size - 1; i++) {
            elementData[i] = elementData[i + 1];
        }
        elementData[size - 1] = null;
        size--;
    }


    /**
     * 根据索引插入元素
     *
     * @param index   插入元素的索引位置
     * @param element 需要插入的元素
     */
    public void add(int index, Object element) {
// 1.判断索引是否合法,合法的取值范围:[0, size]
// --> 插入的元素可以在实际添加元素的最末尾
        if (index < 0 || index > size) {
            throw new ArrayIndexOutOfBoundsException("索引异常,index:" + index);
        }
// 2.判断数组是否需要扩容
        ensureCapacityInternal();
// 3.插入索引及其之后的元素往后挪动一位(从后往前挪动)
// 3.1 获得插入索引及其之后的所有索引值
        for (int i = size - 1; i >= index; i--) {
// 3.2 把前一个元素往后挪动一位
            elementData[i + 1] = elementData[i];
        }
// 4.在插入索引位置实现赋值操作
        elementData[index] = element;
// 5.更新 size 的值
        size++;
    }


    /**
     * 判断是否需要扩容
     */
    private void ensureCapacityInternal() {
        //判断数组是否需要执行扩容
        if (size == elementData.length) {
            Object[] temp = new Object[elementData.length * 2 + 1];
            for (int i = 0; i < size; i++) {
                temp[i] = elementData[i];
            }
            elementData = temp;
        }
    }


    /**
     * 检查索引是否合法(get 和 remove)
     *
     * @param index 需要检查的索引值
     */
    private void rangeCheck(int index) {
// 判断索引是否合法,合法取值范围:[0, size - 1]
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException("索引异常,index:" + index);
        }
    }

}

链表

单链表

**单链表的定义 **
单链表采用的是链式存储结构,使用一组地址任意的存储单元来存放数据元素。在单链表中,存储的每一条数据都是以节点来表示的,每个节点的构成为:元素(存储数据的存储单元) + 指针(存储下一个节点的地址值),单链表的节点结构如下图所示:
image.png
另外,单链表中的开始节点,我们又称之为首节点;单链表中的终端节点,我们又称之为尾节
点。如下图所示:
image.png

**根据序号获取节点的操作 **
在线性表中,每个节点都有一个唯一的序号,该序号是从 0 开始递增的。通过序号获取单链表的节点时,我们需要从单链表的首节点开始,从前往后循环遍历,直到遇到查询序号所对应的节点时为止。
以下图为例,我们需要获得序号为 2 的节点,那么就需要依次遍历获得“节点 11”和“节点 22”,然后才能获得序号为 2 的节点,也就是“节点 33”。因此,在链表中通过序号获得节点的操作效率是非常低的,查询的时间复杂度为 O(n)。
image.png

**根据序号删除节点的操作 **
根据序号删除节点的操作,我们首先应该根据序号获得需要删除的节点,然后让“删除节点的前一个节点”指向“删除节点的后一个节点”,这样就实现了节点的删除操作。
以下图为例,我们需要删除序号为 2 的节点,那么就让“节点 22”指向“节点 44”即可,这样就删除了序号为 2 的节点,也就是删除了“节点 33”。
image.png
通过序号来插入节点,时间主要浪费在找正确的删除位置上,故时间复杂度为 O(n)。但是,单论删除的操作,也就是无需考虑定位到删除节点的位置,那么删除操作的时间复杂度就是 O(1)。
**根据序号插入节点的操作 **
根据序号插入节点的操作,我们首先应该根据序号找到插入的节点位置,然后让“插入位置的上一个节点”指向“新插入的节点”,然后再让“新插入的节点”指向“插入位置的节点”,这样就实现了节点的插入操作。 以下图为例,我们需要在序号为 2 的位置插入元素值“00”,首先先把字符串“00”封装为一 个节点对象,然后就让“节点 22”指向“新节点 00”,最后再让“节点 00”指向“节点 33”,这样就插入了一个新节点。
image.png
通过序号来插入节点,时间主要浪费在找正确的插入位置上,故时间复杂度为 O(n)。但是,单论插入的操作,也就是无需考虑定位到插入节点的位置,那么插入操作的时间复杂度就是 O(1)。

顺序表和单链表的比较
(1) 存储方式比较
顺序表采用一组地址连续的存储单元依次存放数据元素,通过元素之间的先后顺序来确定元素之间的位置,因此存储空间的利用率较高。单链表采用一组地址任意的存储单元来存放数据元素,通过存储下一个节点的地址值来确定节点之间的位置,因此存储空间的利用率较低。
(2) 时间性能比较
顺序表查找的时间复杂度为 O(1),插入和删除需要移动元素,因此时间复杂度为 O(n)。若是需要频繁的执行查找操作,但是很少进行插入和删除操作,那么建议使用顺序表。
单链表查找的时间复杂度为 O(n),插入和删除无需移动元素,因此时间复杂度为 O(1)。若是需要频繁的执行插入和删除操作,但是很少进行查找操作,那么建议使用链表。
补充:根据序号来插入和删除节点,需要通过序号来找到插入和删除节点的位置,那么整体的时间复杂度为 O(n)。因此,单链表适合数据量较小时的插入和删除操作,如果存储的数据量较大,那么就建议使用别的数据结构,例如使用二叉树来实现。
(3) 空间性能比较
顺序表需要预先分配一定长度的存储空间,如果事先不知道需要存储元素的个数,分配空间过大就会造成存储空间的浪费,分配空间过小则需要执行耗时的扩容操作。
单链表不需要固定长度的存储空间,可根据需求来进行临时分配,只要有内存足够就可以分配,在链表中存储元素的个数是没有限制的,无需考虑扩容操作。

双链表

**双链表的定义 **
双链表也叫双向链表,它依旧采用的是链式存储结构。在双链表中,每个节点中都有两个指针,分别指向直接前驱节点(保存前一个节点的地址值)和直接后继节点(保存后一个节点的地址值), 如下图所示。
image.png
所以,从双链表中的任意一个节点开始,都可以很方便地访问它的直接前驱节点和直接后继节点,如下图所示。
image.png
**单链表和双链表的区别 **
逻辑上没有区别,他们均是完成线性表的内容,主要的区别是结构上的构造有所区别。
(1) 单链表
对于一个节点,有储存数据的 data 和指向下一个节点的 next。也就是说,单链表的遍历操作都得通过前节点—>后节点。
(2) 双链表
对于一个节点,有储存数据的 data 和指向下一个节点的 next,还有一个指向前一个节点的 pre。也就是说,双链表不但可以通过前节点—>后节点,还可以通过后节点—>前节点。

环形链表

**环形链表概述 **
环形链表依旧采用的是链式存储结构,它的特点就是设置首节点和尾节点相互指向,从而实现
让整个链表形成一个环。在我们实际开发中,常见的环形链表有:
(1) 环形单链表
在单链表中,尾节点的指针指向了首节点,从而整个链表形成一个环,如下图所示:
image.png
(2) 环形双链表
在双链表中,尾节点的指针指向了首节点,首节点的指针指向了尾节点,从而整个链表形成一个环,如下图所示:
image.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值