1 理解Java如何构建链表
1.1 为什么要用链表
1.1.1 计算机存储数据的两种物理结构
首先计算机在存储数据的过程中,会有两种物理结构:顺序存储和链式存储。顺序存储就是计算机在保存数据的时候会自动的开辟一整块连续的空间,用于存放数据。而链式存储就是程序每次要保存一个数据的时候就会在内存中随机开辟一个空间也就是节点来存储数据,同时为了便于找到这个数据,就会让上一个数据节点的指针指向当前开辟的节点。
1.1.2 两种物理结构的优缺点
顺序存储格式在程序中可以由数组进行创建,顺序存储的优点:当我们进行查找和修改数据的时候比较简单,直接通过数组下表就可以直接进行访问。缺点:我们在添加和删除数据的时候就会比较复杂,因为假设有长度为n的数组,那么在添加和删除 i 位置的数据也就意味着要依次移动后面n-i个数据,那么最坏时间复杂度为O(n)。
链式存储的优点:便于插入和删除数据;缺点:不利于查找和修改数据
1.2 如何创建链表
由1.1.1中得知链式存储的每一个数据节点Node都是由要存储的数据以及指向下一个节点的指针组成,但是在Java语言中没有C/C++中的指针变量,因此在Java中可以直接使用引用类型来作为指向下一个数据节点的指针。Java中的引用类型包括数组和类等。
public class Node{
private int data;
private Node next;
public Node(){}
public Node(int data){
this.data = data;
this.next = null;
}
public Node(int data, Node next){
this.data = data;
this.next = next;
}
public int getData(){return this.data;}
public void setData(int data){this.data = data;}
public Node getNext(){return this.next;}
public Node setNext(Node next){this.next = next;}
}
2 链表增加和删减元素
2.1 增加元素
2.1.1 头部增加
链表有两种形式,一种是带头节点的链表,一种是不带头节点的链表,带头节点的链表是在初始化链表的时候就给链表开辟了一个Node的存储空间当作头节点,再添加元素的时候直接开辟新的空间并连接到此头节点之后就可以。
带头节点的链表在头部增加元素的时候直接创建新的元素链接到头节点的next指针即可,但是当原来的第一个元素存在的时候,也就是头节点的next指针不为空的时候,就需要将待添加节点的next指针指向原来的第一个节点,然后再将头节点的next指针指向待添加节点的地址,否则就会造成原来的节点与链表断开,造成数据丢失。
2.1.2 中间添加
在中间添加头节点时与带头节点的链表类似,先将待添加节点的next指针指向原来的节点地址,然后再将上一个节点的next指针指向当前待添加节点的地址。顺序不能倒换,否则就会产生数据丢失的情况。
2.1.3 尾部添加
尾部添加较为简单,利用循环遍历到最后一个节点,然后将最后一个节点的指针指向待添加节点即可。
public void pushBack(int data){
Node searchNode = L;
// find the final node cause final node's next pointer -> null !!
while(searchNode.getNext() != null){
searchNode = searchNode.getNext();
}
searchNode.setNext(new Node(data, null));
}
2.2 删除元素
2.2.1 头部删除
带头节点的链表,删除头部节点较为容易,直接将头指针的next指针由指向第一个节点改为指向第二个节点即可。
public void popHead(){
this.L.setNext(this.L.getNext().getNext());
}
2.2.2 中间删除
链表删除中间某位置的节点,需要从头节点开始遍历,一直到待删除节点的位置处停止,其中要创建一个Node的变量,用于保存待删除节点的上一个节点地址,因为链表的特点就是,每一个节点只知道自己的位置以及下一个节点的位置,链表的其他位置对于某一个节点来说相当于黑匣子。
public void pushData(int i, int data){
Node searchNode = L;
int location = 0;
while(searchNode.getNext() != null){
location++;
if(location == i)
break;
searchNode = searchNode.getNext();
}
Node nextNode = searchNode.getNext();
searchNode.setNext(new Node(data, nextNode));
}
2.2.3 尾部删除
链表的尾部删除是利用循环直至最后一个节点(标志是Node.getNext() == null)然后将上一个节点的next指针指向null即可。不同于C++需要自己回收垃圾数据,Java会自己进行垃圾的回收。
3 双向链表
3.1 构造
public class DoubleHeadNode {
private int data;
private DoubleHeadNode next;
private DoubleHeadNode prev;
public DoubleHeadNode(){}
public DoubleHeadNode(int data, DoubleHeadNode next, DoubleHeadNode prev){
this.data = data;
this.next = next;
this.prev = prev;
}
public int getData() {
return data;
}
public DoubleHeadNode getNext() {
return next;
}
public DoubleHeadNode getPrev() {
return prev;
}
public void setData(int data) {
this.data = data;
}
public void setNext(DoubleHeadNode next) {
this.next = next;
}
public void setPrev(DoubleHeadNode prev) {
this.prev = prev;
}
}
3.2 插入
双向链表的插入与单向链表有所区别,由于双向链表的每一个节点都有前向指针和后向指针,因此插入操作的时候需要改变的值就高达4个,分别是待插入节点的前向后向指针,父节点的后向指针和son节点的前向指针,因此先更新哪个值就成了比较关键的问题,顺序出错可能会导致数据丢失和指向直接的现象。
由于待插入节点和链表在之前没有任何关系,因此首先改变的应该是此节点的两个指针,待插入节点的next指针接收father节点的next指针,待插入节点的prev指针接收son节点的prev指针。更改完待插入节点的数据以后就可以,分别更改father节点的next指针和son节点的prev指针了(修改顺序没有前后之分)。
father节点的next指针指向待插入节点,son节点的prev指向待插入节点。
3.3 删除
删除操纵与插入操作类似,也需要操作4个指针的指向,首先是由于待删除节点删除以后便没有关系了,因此此节点应该最后操作,所以先更改father节点的next指针和son节点的prev指针。
father节点的next指针接收待删除节点的next指针,son节点的prev指针接收待删除节点的指针。
最后将待删除节点的next指针和prev指针指向null,使之变成垃圾由Java自动回收。