线性表的定义与实现

简介: 假设我们有 n 个同类型数据元素的有限序列,记为:L = (a1, a2, a3, ..., ai, ..., an)它就构成了一个线性表。前面说过任何一种逻辑结构都可以使用两种存储结构(顺序存储,链式存储)来实现,使用顺序存储时,会在内存中分配连续的空间来存储数据元素,在程序代码中经常使用数组来实现。但问题来了,很多时候我们无法知道到底预先分配多大的空间合适,如果数据量较大时,内存中不存在那么大的连续空间,所以会导致内存分配失败。这个时候,就可以使用链式存储来实现,具体选用哪种方案根据需求决定。


一、定义



假设我们有 n 个同类型数据元素的有限序列,记为:

L = (a1, a2, a3, ..., ai, ..., an)

它就构成了一个线性表。前面说过任何一种逻辑结构都可以使用两种存储结构(顺序存储,链式存储)来实现,使用顺序存储时,会在内存中分配连续的空间来存储数据元素,在程序代码中经常使用数组来实现。但问题来了,很多时候我们无法知道到底预先分配多大的空间合适,如果数据量较大时,内存中不存在那么大的连续空间,所以会导致内存分配失败。这个时候,就可以使用链式存储来实现,具体选用哪种方案根据需求决定。

链式存储可以使用任何地址的空间存放数据元素,也就是可以空间不连续,它的实现也很简单,就是在存放每个数据结点,里面包含数据元素和一个指向后一个数据元素的引用(或指针)。

微信图片1111.png

使用顺序存储实现的线性表叫做顺序表,使用链式存储实现的线性表叫做链表


二、线性表基本操作分析



前面章节说到,分析数据结构要分析各种结构的基本操作,我们首先定义线性表的一组基本操作,然后编写代码来实现。首先分析线性表的插入、删除、查找、遍历等基本操作,再使用不同的存储结构来实现。


1. 插入


我们已经创建了两种存储结构的线性表,数据元素为 (a1, a2, a3, a4, a5),现在需要在 a2 的的后面插入一个新的数据元素 a6,在使用顺序存储实现时,我们需要将 a2 后面的数据 a3, a4, a5 都要往后移动才能插入。在使用链式存储时,而只需要定位到 a2,然后把 a2.next 赋给新结点 a6.next,再把新结点 a6 赋给 a2.next,通过引用的交换就可以将新数据插入到链表中,由此可看出链表比顺序表的插入效率要高。


微信图片2222.png

2. 删除


如果我们要删除线性表中的一个数据元素,和插入有点类似,比如需要在给定线性表 (a1, a2, a3, a4, a5) 中删除第三个元素 a3,在顺序结构中,需要把 a3 后面的数据 a4, a5 都要往前移动,再把最后一个数据元素清空。在链式结构中,只需要定位到 a3 前一个元素 a2,然后把 a2.next.next 赋值给 a2.next 就可以删掉链表中的 a3,所以,链表的删除操作不需要移动后面的元素,效率比顺序表要高。


微信图片3333.png

3. 查找


如果我们需要在线性表中通过下标查找数据元素,在顺序存储中,因为数据是有序存放在一个内存段中的,所以直接可以使用下标来获取,而在链式存储中,数据是无序的,所以要从头结点开始遍历来找到对应下标的数据元素,由此可知,链表的查找效率低于顺序表。

微信图片4444.png

4. 遍历


当我们要对一个线性表进行遍历时,虽然不管是使用顺序存储还是链式存储,都要对所有元素进行读取,效率看上去不相上下,但不要忘了,顺序表是存在连续空间上的,所以在遍历的时候只需要进行地址偏移就可以读取到所有元素,而链表每读取元素的后一个元素,都要进行一次寻址操作,所以效率也是低于顺序表的。


微信图片5555.png

下面新建一个线性表常规操作的接口,然后我们用顺序结构(数组)和链式结构来实现这个接口,来实现对线性表的操作,读者可以分别对两种存储结构实现的接口的时间复杂度进行分析。

public interface LinearTable<T> {
    /**
     * 初始化线性表数据
     */
    LinearTable<T> init(T... t);
    /**
     * 判断线性表是否为空
     */
    boolean empty();
    /**
     * 清空线性表
     */
    boolean clear();
    /**
     * 返回线性表的大小
     */
    int size();
    /**
     * 插入数据元素
     */
    boolean insert(int pos, T t);
    /**
     * 删除数据元素
     */
    boolean delete(int pos);
    /**
     * 通过位置查找数据元素
     */
    T findElement(int pos);
    /**
     * 通过数据元素查找位置
     */
    int findPosition(T t);
    /**
     * 打印线性表所有数据
     */
    void print();
}

三、线性表基本操作实现



顺序结构使用数组来存储数据,链式结构使用结点来存储数据。


1. 顺序存储实现

public class ArrayLinearTable<T> implements LinearTable<T> {
    private Object[] elementData;
    public ArrayLinearTable(int size) {
        elementData = new Object[size];
    }
    @Override
    public LinearTable<T> init(T... t) {
        elementData = t;
        return this;
    }
    @Override
    public boolean empty() {
        return elementData.length == 0;
    }
    @Override
    public boolean clear() {
        for (int i = 0; i < elementData.length; i++) {
            elementData[i] = null;
        }
        return true;
    }
    @Override
    public int size() {
        return elementData.length;
    }
    @Override
    public boolean insert(int pos, T t) {
        if (pos > elementData.length - 1) {
            throw new ArrayIndexOutOfBoundsException();
        }
        for (int i = pos; i < elementData.length - 1; i++) {
            elementData[i + 1] = elementData[i];
        }
        elementData[pos] = t;
        return true;
    }
    @Override
    public boolean delete(int pos) {
        if (pos > elementData.length - 1) {
            throw new ArrayIndexOutOfBoundsException();
        }
        for (int i = pos; i < elementData.length - 1; i++) {
            elementData[i] = elementData[i + 1];
        }
        elementData[elementData.length - 1] = null;
        return true;
    }
    @SuppressWarnings("unchecked")
    @Override
    public T findElement(int pos) {
        return (T) elementData[pos];
    }
    @Override
    public int findPosition(T t) {
        if (t == null) {
            return -1;
        }
        for (int i = 0; i < elementData.length; i++) {
            if (t.equals(elementData[i])) {
                return i;
            }
        }
        return -1;
    }
    @Override
    public void print() {
        System.out.println(Arrays.toString(elementData));
    }
}


2. 链式存储实现

public class LinkedLinearTable<T> implements LinearTable<T> {
    private Node head;
    private int size = 0;
    private class Node {
        private T t;
        private Node next;
        public Node() {
        }
        public Node(T t, Node next) {
            this.t = t;
            this.next = next;
        }
    }
    @Override
    public LinearTable<T> init(T... t) {
        if (t == null || t.length == 0) {
            return this;
        }
        head = new Node(t[size++], null);
        Node pre = head;
        for (int i = 1; i < t.length; i++) {
            pre.next = new Node(t[i], null);
            pre = pre.next;
            size++;
        }
        return this;
    }
    @Override
    public boolean empty() {
        return head == null;
    }
    @Override
    public boolean clear() {
        head = null;
        return true;
    }
    @Override
    public int size() {
        return size;
    }
    @Override
    public boolean insert(int pos, T t) {
        if (pos > size) {
            throw new OutOfMemoryError();
        }
        Node n = new Node(t, null);
        if (pos == 0) {
            n.next = head;
            head = n;
        } else {
            Node pre = head;
            for (int i = 0; i < pos - 1; i++) {
                pre = pre.next;
            }
            n.next = pre.next;
            pre.next = n;
        }
        size++;
        return true;
    }
    @Override
    public boolean delete(int pos) {
        if (pos > size - 1) {
            throw new OutOfMemoryError();
        }
        if (pos == 0) {
            head = head.next;
        } else {
            Node pre = head;
            for (int i = 0; i < pos - 1; i++) {
                pre = pre.next;
            }
            pre.next = pre.next.next;
        }
        size--;
        return true;
    }
    @Override
    public T findElement(int pos) {
        Node n = head;
        for (int i = 0; i < pos; i++) {
            n = n.next;
        }
        return n.t;
    }
    @Override
    public int findPosition(T t) {
        int i = 0;
        Node n = head;
        while (n != null && !t.equals(n.t)) {
            i++;
            n = n.next;
        }
        return i;
    }
    @Override
    public void print() {
        Node n = head;
        while (n != null) {
            System.out.print(n.t + ",");
            n = n.next;
        }
        System.out.println();
    }
}


四、链式存储扩展



前面讲到的链式结构实现的线性表都是单向链表,我们还可以增加结点的引用来创建更多的链表结构,比如双向链表、单向循环链表和双向循环链表等,只需要掌握一种最简单最基础的单向链表,就可以举一反三搞懂其他各种特殊链表结构。


微信图片6666.png


目录
相关文章
|
11月前
|
数据采集 小程序 API
通义千问Qwen2.5-Coder 全系列来咯!强大、多样、实用
千问团队开源了强大的 Qwen2.5-Coder 系列模型,涵盖 0.5B 到 32B 六种尺寸,旨在推动开放代码模型的发展。该系列模型在代码生成、修复和推理等方面表现出色,支持多种编程语言,并在多个基准测试中达到 SOTA 水平。此外,Qwen2.5-Coder 还提供了丰富的应用场景,如代码助手、Artifacts 和 Interpreter,满足不同开发者的需求。
3883 106
|
7月前
|
人工智能 数据可视化 前端开发
Probly:开源 AI Excel表格工具,交互式生成数据分析结果与可视化图表
Probly 是一款结合电子表格功能与 Python 数据分析能力的 AI 工具,支持在浏览器中运行 Python 代码,提供交互式电子表格、数据可视化和智能分析建议,适合需要强大数据分析功能又希望操作简便的用户。
905 2
|
小程序 Java 程序员
基于java的坦克大战游戏的设计与实现--答辨PPT--【毕业论文】
基于java的坦克大战游戏的设计与实现--答辨PPT--【毕业论文】
|
消息中间件 JavaScript 前端开发
SpringBoot+Vue 实现网页版人脸登录、人脸识别,逼格很高!!!
SpringBoot+Vue 实现网页版人脸登录、人脸识别,逼格很高!!!
|
关系型数据库 PHP Apache
Win 10 下安装DVWA
安装laragon :https://www.laragon.org/download/ 下载 DVWA: 方法1:https://github.com/ethicalhack3r/DVWA.git 克隆到laragon的www即根目录下。
1463 0
|
Java 索引 关系型数据库
【Java学习笔记之十】Java中循环语句foreach使用总结及foreach写法失效的问题
foreach语句使用总结增强for(part1:part2){part3}; part2中是一个数组对象,或者是带有泛性的集合. part1定义了一个局部变量,这个局部变量的类型与part2中的对象元素的类型是一致的. part3当然还是循环体.   foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。
1504 0
|
Linux 调度
Linux用户空间与内核空间(理解高端内存)
Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数据可能不在内存中。
2124 0