什么是链表
-
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,有一系列结点(地址)组成,结点可动态的生成。
-
结点包括两个部分:
(1)存储数据元素的数据域(内存空间) 。
(2)存储指向下一个结点地址的指针域 。
-
链表分为 :
(1)单链表
(2)双链表
(3)单向循环链表
(4)双向循环链表
本文就介绍一下如何编写一个单链表 ! ! !
类结构
大致样子
这是单向链表的大致样子 , Sentinel节点就是用于标识一个头节点的作用
代码写出一个节点的样子
public class SinglyLinkedList {
// 哨兵节点,本身的值只作为一个标识,无其他实际意义,初始状态下一个节点为空
private Node head = new Node(-1, null);
// 内部类, 对使用者隐藏了内部的实现, 使用者只需要知道SinglyLinkedList即可
private static class Node {
int value; // 存储数据的空间
Node next; // 指向下一个节点的指针
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
}
上面这串代码注意点很多喔 !!!
- Node类采用了内部类的实现方式 , 对使用者来说, 无需再调用Node类, 只需专注于使用SinglyLinkedList类即可
- 哨兵节点的存在只是作为一个标识, 也叫做哑元 , 用于简化链表边界的判断
- private修饰是必须存在的, 用于保护链表的数据安全性
addFirst方法(头节点插入)
需要改动的只有两个地方
- 哨兵节点的next指向
- 新插入节点的next指向
这就是链表插入数据效率快的情况及原因
public void addFirst(int value){
//存在两种情况, 根据哨兵节点的下一个节点进行判断
Node next = head.next;
Node newNode=null;
//链表为空
if (next==null){
//直接把哨兵节点的下一个节点指向新创建的节点,新创建的节点的next节点为空
newNode=new Node(value,null);
}
//链表非空
else {
//同样把哨兵节点的下一个接待你指向新创建的节点,只是新创建的next节点指向上一个头节点
newNode=new Node(value,next);
}
head.next=newNode;
}
loop方法(遍历链表)
这里采用了链表之外的节点pointer , 来记录这些需要遍历的节点
public void loop(Consumer<Integer> consumer){
for (Node pointer=head.next;pointer!=null;pointer=pointer.next){
//这里采用了函数式接口, 让使用者完成方法的逻辑处理, 我们只是规定了使用者只能操作的内容
consumer.accept(pointer.value);
}
}
addLast(尾节点插入)
需要改动的只有两个地方
- 原尾节点的next指针指向新的节点
- 新插入节点的next指向null
这就是链表插入数据效率慢的情况及原因, 因为链表越长 , 则链表的遍历到最后一个节点需要花费的时间越久.
public Node findLast(){
if (head.next==null){
return null;
}
Node pointer;
for ( pointer=head.next;pointer.next!=null;pointer=pointer.next){
//此处只是为了让pointer指针到最后一个元素
}
return pointer;
}
public void addLast(int value){
Node last = findLast();
if (last==null){
System.out.println("此时为空链表");
head.next=new Node(value,null);
}else {
last.next=new Node(value,null);
}
}
get方法(根据索引获取节点的值)
此处使用了i变量与index的值进行比较遍历, 方便获取值
public int get(int index){
int i=0;
for (Node pointer=head.next;pointer!=null;pointer=pointer.next,i++){
if (index==i){
return pointer.value;
}java
}
return -1;
}
insert(根据索引插入节点)
只需三件事
- 找目标索引上一个节点的位置
- 新插入的节点做为上一个节点的next
- 新插入的节点的next为原索引的值
public Node findByIndex(int index){
int i=0;
for (Node pointer=head.next;pointer!=null;pointer=pointer.next,i++){
if (index==i){
return pointer;
}
}
return null;
}
public void insert(int index,int value){
Node byIndex = findByIndex(index-1);
if (byIndex==null){
head.next=new Node(value,null);
}
byIndex.next=new Node(value,findByIndex(index));
}
remove(根据索引删除节点)
此处做了一个判断, 对头节点的情况进行了处理, 其他步骤依然是完成next指针的改变.
对于原节点不需要担心, Java会自动垃圾回收没有被引用的对象.
public int remove(int index){
if (index==0){
Node byIndex = findByIndex(0);
head.next=findByIndex(1);
return byIndex.value;
}
Node byIndex = findByIndex(index-1);
Node node = findByIndex(index);
byIndex.next=node.next;
return node.value;
}
测试结果符合预期的结果