Java数据结构(4) 链表——单向链表
1.链表的简介
链表是有序的列表,但以节点的方式存储数据,是链式存储。
一个节点包括:数据域(data):存放数据,链接域(next):指向下一个节点。各节点不一定是连续存储
用代码表示就如下所示:
Node节点类:
/**
* 代码实现链表——节点类
*/
class Node {
private Object data;//数据域
//...
private Node next;//节点
//节点的构造方法:初始化数据域,将节点指向空
public Node(Object data) {
this.data = data;
this.next = null;
}
}
LinkedList链表类:
/**
* 代码实现链表——链表类
*/
public class LinkedList {
private Node first;
private Node last;
//链表的方法:判空,打印链表,在尾部添加节点,删除节点等
//...
}
2.单向链表代码实现
节点类:数据域举例为 整型的书籍序列号 ,字符型的书籍名称。同上述简介中节点类一致,在构造方法中初始化数据域中的数据,并将next节点指向空。
单向链表类只实现了:判空,打印链表,在尾部添加节点三个基本方法。
/**
* 代码实现单向链表——节点类
*/
class Node {
int bookId;//书籍序列号
String bookName;//书籍名称
Node next;//节点
//节点的构造方法:初始化数据域,将节点指向空
public Node(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
this.next = null;
}
}
/**
* 代码实现单向链表(判空,打印链表,在尾部添加节点)
*/
public class LinkedList {
private Node first;
private Node last;
//判空
public boolean isEmpty() {
return first == null;//布尔表达式,头节点空返回true,反之返回false
}
//打印链表(从链表头打印到链表尾)
public void printLinkedList() {
Node current = first;//定义一个暂时的节点,赋值为头节点
while (current != null) {
System.out.println(current.bookId + current.bookName);
current = current.next;
}
}
//在尾部添加节点
public void addNode(Node newNode) {
if (this.isEmpty()) {//链表为空时,插入节点(头尾节点均为新节点)
first = newNode;
last = newNode;
} else {//链表不为空时,以下两个语句不可颠倒,否则之前的尾节点会被覆盖。
last.next = newNode;//新节点赋值为 当前尾节点的下一节点
last = newNode;//此时链表的尾节点 为刚插入的新节点
}
}
//单向链表测试
public static void main(String[] args) {
LinkedList linkedList=new LinkedList();
linkedList.addNode(new Node(12086,"《数据结构》"));
linkedList.addNode(new Node(18002,"《计算机网络》"));
System.out.println("单向链表为空?"+linkedList.isEmpty());
linkedList.printLinkedList();
}
}
测试结果如下:
单向链表为空?false
12086《数据结构》
18002《计算机网络》
3.单向链表的节点删除与插入
只有判空,打印链表,在尾部添加节点三个基本方法是不够的,还需要节点删除与插入。
这里将节点删除和节点插入两个方法单独提出来,是因为这两个方法是最重要的
删除节点分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。
方法概述:
- 删头节点:新头节点为原头节点的下一节点
- 删尾节点:新尾节点为原尾节点的前一节点
- 删中间节点:需删除节点的前一节点指向需删除节点的后一节点
具体代码如下(更多细节在注释中有所标注):
//删除节点 分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。
public void delete(Node needDeleteNode) {
Node newNode;
Node tempNode;
//1.删除的节点为头节点
if (first.bookId == needDeleteNode.bookId) {
first = first.next;//新头节点赋值为原始头结点的下一个节点
} else if (last.bookId == needDeleteNode.bookId) {
//2.删除的节点为尾节点//需要先遍历节点至 尾节点的前一个节点
newNode = first;
while (newNode.next != last) newNode = newNode.next;//直至newNode为尾节点的前一个节点
newNode.next=last.next;//原尾节点的前一个节点 的next节点置空(其实就是原尾节点失去指向)
last=newNode;//新尾节点为 原尾节点的前一个节点
}else {//3.删除的为中间的某个节点。需要知道需删除节点的前一节点
newNode=first;
tempNode=first;
while (newNode.bookId!=needDeleteNode.bookId){//遍历链表至newNode为needDeleteNode
tempNode=newNode;//tempNode暂存newNode
newNode=newNode.next;
//循环结束时tempNode为newNode前一个节点
}
//循环结束:
// newNode为needDeleteNode
// tempNode为newNode的前一节点,即needDeleteNode的迁移节点
//删除节点即:需删除节点的前一节点指向需删除节点的后一节点
tempNode.next=needDeleteNode.next;
}
}
插入节点分为三种情况:插入的节点为头节点,插入的节点为尾节点,插入的为中间的某个节点。
方法概述:
- 插头节点:原头节点的前一节点赋值为新插入节点,头节点赋值为新插入节点,若原链表空则头尾节点为新插入节点
- 插尾节点:原尾节点的下一节点赋值为新插入节点,尾节点赋值为新插入节点,若原链表空则头尾节点为新插入节点
- 插中间节点:(原链表不为空)插到a节点后,即a节点的下一节点赋值为新插入节点,a节点原来的下一节点
(在上述代码中addNode()方法即在尾部添加节点)
具体代码如下:
//删除节点 分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。
public void delete(Node needDeleteNode) {
Node newNode;
Node tempNode;
//1.删除的节点为头节点
if (first.bookId == needDeleteNode.bookId) {
first = first.next;//新头节点赋值为原始头结点的下一个节点
} else if (last.bookId == needDeleteNode.bookId) {
//2.删除的节点为尾节点//需要先遍历节点至 尾节点的前一个节点
newNode = first;
while (newNode.next != last) newNode = newNode.next;//直至newNode为尾节点的前一个节点
newNode.next = last.next;//原尾节点的前一个节点 的next节点置空(其实就是原尾节点失去指向)
last = newNode;//新尾节点为 原尾节点的前一个节点
} else {//3.删除的为中间的某个节点。需要知道需删除节点的前一节点
newNode = first;
tempNode = first;
while (newNode.bookId != needDeleteNode.bookId) {//遍历链表至newNode为needDeleteNode
tempNode = newNode;//tempNode暂存newNode
newNode = newNode.next;
//循环结束时tempNode为newNode前一个节点
}
//循环结束:
// newNode为needDeleteNode
// tempNode为newNode的前一节点,即needDeleteNode的迁移节点
//删除节点即:需删除节点的前一节点指向需删除节点的后一节点
tempNode.next = needDeleteNode.next;
}
}
//插入节点:三种情况:插入的节点为头节点,插入的节点为尾节点,插入的为中间的某个节点。
//传入的节点needInsertNode 数据域与节点域均已初始化 固可根据needInsertNode.next判断
public void insertNode(Node needInsertNode) {
Node tempNode;
Node newNode;
if (this.isEmpty()) {//原链表空,插头插尾都一样 头尾均为新节点
first = needInsertNode;
last = needInsertNode;
} else {
if (needInsertNode.next == first) {//1.原链表非空,插头
first = needInsertNode;//新头节点为needInsertNode
} else if (needInsertNode.next == null) {//2.原链表非空。插尾
last.next = needInsertNode;//原尾节点的下一节点为新插入节点
last = needInsertNode;//新尾节点为新插入节点
} else {//3.插中间 需要得到待插入节点的前一节点
// (后一节点已知:即传入的Node类型参数的next属性)
// 即needInsertNode的next节点(已知) 的前一节点
newNode = first;
tempNode = first;
while (needInsertNode.next != newNode.next) {//遍历链表至newNode.next为needInsertNode.next
tempNode = newNode;//temp暂存newNode
newNode = newNode.next;
//循环结束时tempNode为newNode前一个节点
}
//循环结束:
// needInsertNode.next = newNode.next,即 newNode=needInsertNode
// tempNode=newNode的前一节点,即needInsertNode的前一节点
//所以得到了待插入节点的前一节点
tempNode.next = needInsertNode;
needInsertNode.next = newNode;
}
}
}
测试代码:
LinkedList linkedList = new LinkedList();
Node node1 = new Node(12086, "《数据结构》");
Node node2 = new Node(18002, "《计算机网络》");
Node node3 = new Node(19121, "《数据库概论》");
linkedList.addNode(node1);
linkedList.addNode(node2);
linkedList.addNode(node3);
System.out.println("单向链表为空?" + linkedList.isEmpty());
linkedList.printLinkedList();
//测试插入
System.out.println("测试插入");
Node node4 = new Node(20918, "《操作系统》");
//需要初始化节点的next属性
// node4.next=linkedList.first;//插头
node4.next = node3;//插中间 例如插到node3之前
// node4.next = null;//插尾
linkedList.insertNode(node4);
linkedList.printLinkedList();
//测试删除
System.out.println("测试删除");
//linkedList.delete(linkedList.first);//删头
//linkedList.delete(linkedList.last);//删尾
linkedList.delete(node3);//删中间
linkedList.printLinkedList();
测试结果:
单向链表为空?false
12086《数据结构》
18002《计算机网络》
19121《数据库概论》
测试插入
12086《数据结构》
20918《操作系统》
18002《计算机网络》
19121《数据库概论》
测试删除
12086《数据结构》
20918《操作系统》
18002《计算机网络》
关于单链表的两个常见问题:单向链表的反转与串联,将在下一篇博客提及