Java数据结构(线性表)--线性表的链式存储(单链表)及其实现

Posted Z && Y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java数据结构(线性表)--线性表的链式存储(单链表)及其实现相关的知识,希望对你有一定的参考价值。

1. 单向链表

好好学链表,链表是图和树的基础


1.1 为什么会出现链式存储?

顺序存储虽然是一种很有用的数据结构,但它具有如下局限性:

  • 若要为线性表扩充存储空间,则需要重新创建一个地址连续的更大的存储空间,并把原有的数据都复制到心的存储空间中。
  • 因为顺序表要求逻辑上相邻的数据元素,在物理存储位置上也是相邻的,这就使得要增删改查数据元素会引起约一半的数据元素的移动。

所以:

顺序表适合存储“静态”线性表,即线性表一旦形成后,就很少进行插入与删除操作。对于需要
频繁执行插入和删除操作的“动态”线性表,通常采用链式存储结构,链式存储结构不要求逻辑
上相邻的数据元素物理元素也相邻,它是用一组地址任意的存储单元来存放数据元素的值。
因此,链式存储结构没有顺序存储结构所具有的在某些操作上面的局限性,但却失去了可以
随机存取的特点(可以插在任意位置,只要数组下标不越界),在链式存储结构上只能进行顺序存取(存储在next域)。

1.2 单链表的定义


1.3 链表在内存中的存储结构

链表是有序的列表,但是它在内存中是存储如下
在这里插入图片描述


1.4 单链表(带头结点) 逻辑结构示意图如下

在这里插入图片描述

由图可知,单链表是通过指向后继结点的指针把它的一串结点连接成一个链。
头指针:线性表中第一个元素的存储地址。
一个单链表就是由它的头指针head来唯一标识它。
为了操作方法,在第一个结点之前虚加一个“头结点”,头结点的数据域一般不存放数据,
指针域存放指向第一个结点(也叫做“首结点”)的指针。
若线性表为空表,则头结点的指针域为“空”。
单链表的最后一个结点(尾结点)的指针域为“空”。

1.5 单链表(带头结点)实现的思路分析

1.5.1 初始状态:

在这里插入图片描述


1.5.2 添加结点

在这里插入图片描述

现在在s1和s2之间插入一个s3
在这里插入图片描述

为了方便对比,现在演示不带头结点的单链表表头位置进行插入

  1. 插入前
    在这里插入图片描述
  2. 插入后
    在这里插入图片描述

1.5.4 添加结点补充

添加结点补充:

  • 头插法:每次添加结点添加在头结点的next域
  • 尾插法:每次添加结点添加到当前单链表的表尾

1.5.3 删除结点

  1. 删除前
    在这里插入图片描述
  2. 删除后(删除s3)

被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收

在这里插入图片描述

补充:上面的删除思路有点问题 因为实际我并不知道S3后面是S2。所以最好的删除语句是:

 s1.next = s1.next.next

1.6 单链表(带头结点)Java代码实现

1.6.1 单链表结点的描述

其中,data是数据域,用来存放数据元素的值;next是指针域,用来存放后继结点的地址。以下是结点类的描述:

在这里插入图片描述
在这里插入图片描述

Node.java

package singleList;

/**
 * ClassName: Node
 * Description: 单链表的Node结点类
 *
 * @author Tianjiao
 * @date 2021/7/5 10:07
 */
public class Node {
    public Object data;
    public Node next;

    public Node() {
    }

    public Node(Object data) {
        this.data = data;
    }

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }
}

1.6.2 单链表类的描述

在这里插入图片描述

LinkList.java

package singleList;

/**
 * 带头结点的单链表及其基本操作
 */

import java.util.Scanner;

public class LinkList {
    public Node head;// 单链表的头指针

    // 单链表的构造函数
    public LinkList() {
        head = new Node(); // 初始化头结点
    }

    public LinkList(int n, boolean Order) throws Exception {
        this();// 初始化头结点
        if (Order) // 用尾插法顺序建立单链表
            create1(n);
        else
            // 用头插法逆位序建立单链表
            create2(n);
    }

    // 用尾插法顺序建立单链表。其中n为该单链表的元素个数
    public void create1(int n) throws Exception {
        Scanner sc = new Scanner(System.in);// 构造用于输入的对象
        for (int j = 0; j < n; j++) {
            // 输入n个元素的值
            System.out.println("请输入第" + (j + 1) + "个元素的值:");
            insert(length(), sc.next());// 生成新结点,插入到表尾
        }

    }

    // 用头插法逆位序建立单链表。其中n为该单链表的元素个数
    public void create2(int n) throws Exception {
        Scanner sc = new Scanner(System.in);// 构造用于输入的对象
        for (int j = 0; j < n; j++) {
            // 输入n个元素的值
            System.out.println("请输入第" + (j + 1) + "个元素的值:");
            insert(0, sc.next());// 生成新结点,插入到表头
        }
    }

    // 将一个已经存在的带头结点单链表置成空表
    public void clear() {
        head.data = null;
        head.next = null;
    }

    // 判断当前带头结点的单链表是否为空
    public boolean isEmpty() {
        return head.next == null;// 判断首结点是否为空
    }

    // 求带头结点单链表中的数据元素个数并由函数返回其值
    public int length() {
        Node p = head.next;// 初始化,p指向首结点,length为计数器
        int length = 0;
        while (p != null) {// 从首结点向后查找,直到p为空
            p = p.next;// 指向后继结点
            ++length;// 长度增1
        }
        return length;
    }

    // 读取带头结点单链表中的第i个数据元素
    public Object get(int i) throws Exception {
        Node p = head.next;// 初始化,p指向首结点,j为计数器
        int j = 0;
        while (p != null && j < i) {// 从首结点向后查找,直到p指向第i个元素或p为空
            p = p.next;// 指向后继结点
            ++j;// 计数器的值增1
        }
        if (j > i || p == null) { // i小于0或者大于表长减1
            throw new Exception("第" + i + "个元素不存在");// 输出异常
        }
        return p.data;// 返回元素p
    }

    // 在带头结点单链表中第i个数据元素之前插入一个值为x的数据元素
    public void insert(int i, Object x) throws Exception {
        Node p = head;// 初始化p为头结点,j为计数器
        int j = -1; // 第i个结点前驱的位置
        while (p != null && j < i - 1) {// 寻找i个结点的前驱
            p = p.next;
            ++j;// 计数器的值增1
        }
        if (j > i - 1 || p == null) // i不合法
            throw new Exception("插入位置不合理");// 输出异常

        Node s = new Node(x); // 生成新结点
        s.next = p.next;// 插入单链表中
        p.next = s;
    }

    // 将线性表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常
    public void remove(int i) throws Exception {
        Node p = head;// p指向要删除结点的前驱结点
        int j = -1;
        while (p.next != null && j < i - 1) {// 寻找i个结点的前驱
            p = p.next;
            ++j;// 计数器的值增1
        }
        if (j > i - 1 || p.next == null) { // i小于0或者大于表长减1
            throw new Exception("删除位置不合理");// 输出异常
        }
        p.next = p.next.next;// 删除结点
    }

    // 在带头结点的单链表中查找值为x的元素,如果找到,则函数返回该元素在线性表中的位置,否则返回-1
    public int indexOf(Object x) {
        Node p = head.next;// 初始化,p指向首结点,j为计数器
        int j = 0;
        while (p != null && !p.data.equals(x)) {// 从单链表中的首结点元素开始查找,直到p.data指向元素x或到达单链表的表尾
            p = p.next;// 指向下一个元素
            ++j;// 计数器的值增1
        }
        if (p != null)// 如果p指向表中的某一元素
            return j;// 返回x元素在顺序表中的位置
        else
            return -1;// x元素不在顺序表中
    }


    // 输出线性表中的数据元素
    public void display() {
        Node node = head.next;// 取出带头结点的单链表中的首结点元素
        while (node != null) {
            System.out.print(node.data + " ");// 输出数据元素的值
            node = node.next;// 取下一个结点
        }
        System.out.println();
    }

    // 在非递减的有序单链表中插入一个值为x的数据元素,并使单链表仍保持有序的操作
    //方法一
    public void insert(int x) {
        Node p = head.next;
        Node q = head;// q用来记录p的前驱结点
        int temp;
        while (p != null) {
            temp = (Integer) p.data;
            if (temp < x) {
                q = p;
                p = p.next;
            } else
                break;
        }

        Node s = new Node(x); // 生成新结点
        s.next = p;// 将s结点插入到单链表的q结点与p结点之间
        q.next = s;
    }

    // 在非递减的有序单链表中插入一个值为x的数据元素,并使单链表仍保持有序的操作
    //方法二
    public void insert1(int x) {
        Node p = head.next;

        while (p.next != null && (Integer) p.next.data < x) {
            p = p.next;
        }
        Node s = new Node(x); // 生成新结点
        s.next = p.next;// 将s结点插入到单链表的q结点与p结点之间
        p.next = s;
    }

    // 实现对单链表就地逆置(采用的是头插法)
    public void reverse() {
        Node p = head.next;
        head.next = null;
        Node q;
        while (p != null) {
            q = p.next;
            p.next = head.next;
            head.next = p;
            p = q;
        }
    }

    // 实现删除单链表中数据域值等于x的所有结点的操作,并返回被删除结点的个数
    public int removeAll(Object x) {
        Node p = head.next;// 初始化,p指向首结点,j为计数器
        Node q = head; // 用来记录p的前驱结点
        int j = 0;// 用来记录被删除结点的个数
        while (p != null) { // 从单链表中的首结点开始对整个链表遍历一次
            if ((p.data).equals(x)) {
                q.next = p.next;
                ++j;// 计数器的值增1
            } else
                q = p;
            p = p.next;// 指向下一个元素
        }
        return j;// 返回被删除结点的个数
    }
}


以上是关于Java数据结构(线性表)--线性表的链式存储(单链表)及其实现的主要内容,如果未能解决你的问题,请参考以下文章

线性表的链式存储结构 ( 链表 )

线性表的链式存储——线性表的链式存储结构

数据结构开发:线性表的链式存储结构

用C语言编写链式存储结构下实现线性表的创建,插入,删除,按值查找

线性表中的顺序存储与链式存储

计算机二级Java语言卷005