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.5.4 添加结点补充
添加结点补充:
- 头插法:每次添加结点添加在头结点的next域
- 尾插法:每次添加结点添加到当前单链表的表尾
1.5.3 删除结点
- 删除前
- 删除后(删除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数据结构(线性表)--线性表的链式存储(单链表)及其实现的主要内容,如果未能解决你的问题,请参考以下文章