单向链表,是由一个一个的节点串联在一起,实际存放数据的是节点,节点中包含一个数据,以及指向下一个节点的指针。
Java语言单向链表环境下,头指针和虚拟头结点在使用上有何区别?
头指针指向空(即
Node head = null
),不存在的节点,此时链表为空;
虚拟头节点是实际存在的,只不过它存放的元素是空,指向的下一个节点也是空。
一旦设立了虚拟头节点,整个链表中所有的节点都会有1个前驱节点,这样一来,我们在任意位置执行增、删、改、查都将变得简单且操作统一。如果没有设置虚拟头结点而是使用头指针,则须要if-else逻辑判空的操作。
如~编写一个简单java文件,用以实现且仅实现添加(按照顺序插入)字符串的元素操作。
在这个java文件中,编写实现了两种插入链表元素的方法:
- 使用头指针实现链表的元素插入;
- 使用虚拟头结点实现链表的元素插入;
/**这里自定义一个链表,仅实现了添加(按照顺序插入)字符串的元素操作 */
public class LinekedList {
private Node<String> head;/**头指针*/
/** 按照顺序依次添加-使用头指针实现链表的元素插入*/
public void addElement(String value) {
Node<String> node = new Node<>(value);
if (head == null) {
node.next = head; // 将node插入到head的前面,完成插入。
head = node; // 移动head指针到node上(将node作为新的非空head指针)为后续插入做准备。
} else {
node.next = head.next; // 将node指针指向head的后驱节点
head.next = node; // 将head指针指向node节点,完成插入。
head = node; // 移动head指针移动到新插入的node节点上,为后续插入做准备。
// (即将原先指向非空head节点的指针改换指向到node节点上,将node节点作为新的非空头指针)
}
}
/**虚拟头结点*/
private Node<String> dummyHead = new Node<>(null,null);
/** 按照顺序依次添加-使用虚拟头结点实现链表的元素插入 */
public void fillElement(String value) {
Node<String> node = new Node<>(value);
node.next = dummyHead.next; // 将node指针指向dummyHead的后驱节点
dummyHead.next = node; // 将dummyHead指针指向node节点,完成插入。
dummyHead = node; // 移动dummyHead指针到新插入的node节点上,为后续插入做准备。
// (即将原先指向dummyHead的指针改换指向到node节点上,将node节点作为新的虚拟头结点)
}
/**虚拟头结点*/
private Node<String> dummyHeader = new Node<>(null,null);
/**按照下标插入*/
public void fillElement(int index, String value) {
Node<String> dummyHeaderTemp = dummyHeader;
while(index-- > 0) {
dummyHeader = dummyHeader.next; // 获取到所要添加节点位置的前驱节点
// (其实就是移动指向虚拟头结点的指针到所要添加节点位置的前驱节点,
// 然后将该前驱节点作为新的虚拟头结点,此时新的虚拟头结点data和next都非空)
}
Node<String> node = new Node<>(value);
node.next = dummyHeader.next; // 将node指针指向dummyHeader的后驱节点
dummyHeader.next = node; // 将dummyHeader指针指向node节点,从而完成插入。
dummyHeader = dummyHeaderTemp; // 将dummyHeader指针指向原虚拟头结点dummyHeader
// 目的是,再次以下标插入时仍然以虚拟头结点作为开始。
}
class Node<E> {
private E data;
public Node next;
public Node(E data){
this.data = data;
}
public Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
}
链表的优点~
- 链表能灵活地分配内存空间;
- 能在 O(1) 时间内删除或者添加元素,前提是该元素的前一个元素已知,当然也取决于是单链表还是双链表,在双链表中,如果已知该元素的后一个元素,同样可以在 O(1) 时间内删除或者添加该元素。
链表的缺点~
- 不像数组能通过下标迅速读取元素,每次都要从链表头开始一个一个读取;
- 查询第 k 个元素需要 O(k) 时间。
应用场景:如果遇到的问题中很多需要快速查询,链表可能并不适合;如果遇到的问题中,数据的元素个数不确定,且需要频繁进行数据的添加、删除,那么链表会比较合适。而如果数据元素大小确定,删除插入的操作并不多,那么数组可能更适合。