单链表
指针表示一个数据元素逻辑上的存储位置。在计算机中,不同的高级语言实现指针的方法不同。Java语言用对象引用来表示指针。通过把新创建对象赋值给一个对象引用,即是让该对象引用表示(或指向)了所创建的对象。也可以说,该对象引用是所创建的实际对象的一个别名。我们用术语“指针”表示逻辑概念上的数据元素存储位置,用“对象引用”表示Java语言实现的指针。
链式存储结构是基于指针实现的。我们把一个数据元素和一个指针称为一个结点。链式存储结构是用指针把相互直接关联的结点(即直接前驱结点或直接后继结点)链接起来。链式存储结构的特点是数据元素间的逻辑关系表现在结点的链接关系上。链式存储结构的线性表称为链表。根据结点构造链的方法不同,链表主要有单链表、单循环链表和循环双向链表三种。
单链表的结构
在单链表中,构成链表的每个结点只有一个指向直接后继结点的指针。
单链表有带头结点结构和不带头结点结构两种。我们把指向单链表的指针称为单链表的头指针。头指针所指的不存放数据元素的第一个结点称为头结点。存放第一个数据元素的结点称作第一个数据元素结点,或称首元结点。
带头结点单链表和不带头结点单链表的比较
当选用带头结点的单链表时,插入和删除的实现方法比不带头结点单链表的实现方法简单。
在单链表中非第一个数据元素结点前插入一个新结点时,首先把插入位置定位在要插入结点的前一个结点位置,然后把新结点插入单链表中。
要在第一个数据元素结点前插入一个新结点,若采用不带头结点的单链表结构,则结点插入后,头指针就要等于新插入结点,这和在非一个数据元素结点前插入结点的情况不同。另外,还有一些不同情况需要考虑。因此,算法对这两种情况就要分别设计实现方法。
类似地,删除操作实现时,带头结点的单链表和不带头结点的单链表也有类似情况。因此,对于单链表,带头结点比不带头结点的设计方法简单。
结点类
public class Node {
// 数据元素
private Object element;
// 下一个结点
private Node next;
// 用于头结点的构造函数
Node(Node next) {
this.next = next;
}
// 用于其他结点的构造函数
Node(Object element, Node next) {
this.element = element;
this.next = next;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Object getElement() {
return element;
}
public void setElement(Object element) {
this.element = element;
}
}
单链表类
public class LinList implements List {
// 头指针
private Node head;
// 当前结点位置
private Node current;
// 数据元素个数
private int size;
LinList() {
head = current = new Node(null);
size = 0;
}
private void index(int i) throws Exception {
if (i < -1 || i > size - 1) {
throw new Exception("参数错误");
}
if (i == -1) {
current = head;
return;
}
current = head.getNext();
int j = 0;
while (current != null && j < i) {
current = current.getNext();
j++;
}
}
public void insert(int i, Object obj) throws Exception {
if (i < 0 || i > size) {
throw new Exception("参数错误");
}
index(i - 1);
current.setNext(new Node(obj, current.getNext()));
size++;
}
public Object delete(int i) throws Exception {
if (size == 0) {
throw new Exception("链表已空,无数据可删!");
}
if (i < 0 || i > size - 1) {
throw new Exception("参数错误");
}
index(i - 1);
Object obj = current.getNext().getElement();
current.setNext(current.getNext().getNext());
size--;
return obj;
}
public Object getData(int i) throws Exception {
if (i < 0 || i > size - 1) {
throw new Exception("参数错误");
}
index(i);
return current.getElement();
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
}
单链表的效率分析
当在单链表的任何位置上插入数据元素的概率相等时,在单链表中插入一个数据元素时比较数据元素的平均次数为:
删除单链表的一个数据元素时比较数据元素的平均次数为:
因此,单链表插入和删除操作的时间复杂度均为O(n)。
另外,单链表取数据元素操作的时间复杂度也为O(n)。
顺序表和单链表的比较
顺序表的优点:支持随机读取,内存空间利用率高。
顺序表的缺点:需要预先给出数组的最大数据元素个数,而这通常很难准确做到。当实际的数据元素个数超出了预先给出的个数时,会发生异常。另外,顺序表插入和删除操作时需要移动较多的数据元素。
单链表的优点:不需要预先给出数据元素的最大个数。另外,单链表插入和删除操作时不需要移动数据元素。
单链表的缺点:每个结点中要有一个指针,因此单链表的空间利用率略低于顺序表。另外,单链表不支持随机读取,单链表取数据元素操作的时间复杂度为 O(n) ;而顺序表支持随机读取,顺序表读取数据元素的时间复杂度为 O(1) 。