- 定义
链表是一种物理单元上非连续、非顺序的存储结构,链表实际上由一系列节点组成,每个节点包括两个部分:用来存储数据的数据域和指向下一个节点的指针。
链表结构
使用链表可以克服数组长度不可变的缺点,链表可以充分利用计算机的内存空间,实现灵活的内存动态管理,但是链表失去了数组随机读取元素的优点,每次读取元素都必须从头结点往后遍历,同时由于增加了指针,空间开销会大于数组。
- 使用
根据链表的结构,可以设计出对应的链表类,链表类中包含两个属性,链表的头结点和链表中的元素个数。
public class LinkedList<E> {
private class Node{
//数据域
public E e;
//指针域
public Node next;
public Node(E e, Node next){
this.e = e;
this.next = next;
}
......
}
private Node head;
private int size;
}
(1)添加元素,当向链表添加元素时,需要考虑要添加的位置是否是头结点,具体流程如下:
添加元素
具体的实现如下:
//向索引为 index 的位置添加元素 E
public void add(int index, E e){
if(index < 0 || index > size) throw new IllegalArgumentException("参数无效!!");
size ++;
// 当为头结点时
if(index == 0){
head = new Node(e, head);
return ;
}
Node pre = head;
while(-- index > 0) pre = pre.next;
pre.next = new Node(e, pre.next);
}
(2)删除元素时,同样要考虑要删除的元素是否是头结点,具体流程如下:
删除节点
具体实现如下:
//移除索引 index 位置的元素
public E remove(int index){
if(index < 0 || index >= size) throw new IllegalArgumentException("参数无效!!");
size --;
E e;
// 当为头结点时
if(index == 0){
e = head.e;
head = head.next;
// 移除非头结点的元素
}else{
Node pre = head;
while( -- index > 0) pre = pre.next;
e = pre.e;
pre.next = pre.next.next;
}
return e;
}
无论是删除还是添加操作,都必须判断当前要操作的节点是否是头结点,因为头结点是没有前节点的,所以操作和其他的节点不同,我们可以实现一个虚拟头节点,在链表的初始化时,就让头指针指向虚拟节点,让虚拟节点指向链表的真实头节点,这样的话链表头结点的添加和删除操作就和其他节点相同了,因为链表的头结点的前节点就是虚拟节点,链表的结构如下:
头结点
具体实现如下:
public class VirtualHeadLinkedList<E> {
private Node head;
private int size;
public VirtualHeadLinkedList(){
head = new Node();
}
//向索引为 index 的位置添加元素 E
public void add(int index, E e){
if(index < 0 || index > size) throw new IllegalArgumentException("参数无效!!");
size ++;
Node pre = head;
while( index -- > 0) pre = pre.next;
pre.next = new Node(e, pre.next);
}
//移除索引 index 位置的元素
public E remove(int index){
if(index < 0 || index >= size) throw new IllegalArgumentException("参数无效!!");
size --;
Node pre = head;
while( index -- > 0) pre = pre.next;
E e = pre.e;
pre.next = pre.next.next;
return e;
}
}
- 扩展
使用链表实现栈、队列(提示:可以增加一个tail指针)