我们知道线性表的顺序存储结构,最大的缺点就是插入和删除时需要移动大量的元素,这样显然很消耗时间。我们可以想一想,怎么样可以不用移动元素而进行删除和插入呢?为了解决这个问题,我们提出了链式存储结构,它是用一组任意的存储单元去存储线性表的数据元素,这组存储单元可以连续,也可以不连续,也就是说这些数据元素可以在内存中未被占用任意位置,这样也节省了大量的空间。如图所示。
链式存储结构
1.结点
为了表示元素与元素之间的逻辑关系,我们将元素分为数据域和指针域,我们称这样的元素为一个结点。如图所示:每个节点用来存储实际数据中的一个数据项,每个节点的指针域指向下一个节点,最后一个指向一个空值。即一个单项链表的一个节点分为两个部分,第一部分保存或者显示关于节点的信息,第二部分存储下一个节点的地址,单链表只能向一个方向进行遍历。
2.头结点与头指针
1)头结点
什么是头结点呢?在单链表的第一个结点前设置一个结点,该结点数据域不存储任何信息,而指针域存储指向第一个结点的地址,这个结点被称为头结点。那我们为什么要设置一个这样的头结点呢?当然是因为这样处理更简单,也更加容易理解,在单链表中如果我们要访问一个元素,必须先找到它的前驱,通过前驱结点中指针域的地址去访问,有了头结点,对在第一个结点前插入结点或删除结点就跟其它结点方式一样,更加方便操作。
2)头指针
头指针,顾名思义就是指向第一个结点的指针。如果链表中有头结点,头指针则指向头结点,如图所示,head为头指针。
尾指针,当然是指向最后一个结点的,也就是图中a4的位置。(画图的时候忘了标,请见谅。)
3.单链表的插入与删除
我们可以先来想一想链表是怎么样实现插入和删除元素的?
①插入元素
插入元素有三种情况,分别是头插、尾插和一般插入。
我们先来看头插法:头插法也有特殊情况,如图所示,当链表为空时,插入一个元素,我们可以先将头结点的下一跳给A的下一跳,再将A的地址给头结点的下一跳,再将尾指针指向A。
当链表不为空时,前面的步骤也是一样的,唯一不同点就是,尾指针不用移动。
尾插法:当链表为空时,我们发现尾插和头插的步骤是一样的。当链表不为空时,把B的地址给A的下一跳,然后rear后移
一般插入:当我们要在A、B之间插入D时,可以将A看作头,这样就会发现与头插法是一样的。将A的下一跳给D的下一跳,然后让A的下一跳指向D的地址。
②删除元素
同样,删除元素也分为三种情况:头删、尾删和一般删除。
头删:当表中只有一个元素时,删头时,被删元素的下一跳给head的下一跳,最后要让尾指针前移。正常删头时,也一样,唯一不同的是尾指针不需要移动。
尾删:删尾时,一定是要去找尾的前驱(因为这是链表,当需要删尾时,我们不知道尾在哪,所以需要从头开始找),然后将前驱的下一跳置为null,然后再让尾指针指向前驱。
一般删除:当要删哪个元素时,就去找这个元素的前驱。如图,要删B,先找到B的前驱A,然后将B的下一跳给A的下一跳,最后将B的下一跳置null,
4.单链表代码实现(LinkedList)
1)首先我们先定义List接口
package com.openlab.list;
/**
* List是线性表的最终父接口
* @author ABC
* @param <E>
*/
public interface List<E> {
/**
* 获取线性表中元素的个数(线性表的长度)
* @return 线性表中有效元素的个数
*/
public int getSize();
/**
* 判断线性表是否为空
* @return 是否为空的布尔类型值
*/
public boolean isEmpty();
/**
* 在线性表中指定的index角标处添加元素e
* @param index 指定的角标 0<=index<=size
* @param e 要插入的元素
*/
public void add(int index,E e);
/**
* 在线性表的表头位置插入一个元素
* @param e 要插入的元素 指定在角标0处
*/
public void addFirst(E e);
/**
* 在线性表的表尾位置插入一个元素
* @param e 要插入的元素 指定在角标size处
*/
public void addLast(E e);
/**
* 在线性表中获取指定index角标处的元素
* @param index 指定的角标0<=index<size
* @return 该角标所对应的元素
*/
public E get(int index);
/**
* 获取线性表中表头的元素
* @return 表头元素 index=0
*/
public E getFirst();
/**
* 获取线性表中表尾的元素
* @return 表尾的元素 index=size-1
*/
public E getLast();
/**
* 修改线性表中指定的index处元素为新元素e
* @param index 指定的角标
* @param e 新元素
*/
public void set(int index,E e);
/**
* 判断线性表中是否包含指定元素e 默认从前往后找
* @param e 要判断是否存在的元素
* @return 元素的存在性布尔类型值
*/
public boolean contains(E e);
/**
* 在线性表中获取指定元素e的角标,默认从前往后
* @param e 要查询的数据
* @return 数据在线性表中的角标
*/
public int find(E e);
/**
* 在线性表中删除指定角标处的元素,并返回
* @param index 指定的角标 0<=index<size
* @return 删除掉的老元素
*/
public E remove(int index);
/**
* 删除线性表中的表头元素
* @return 表头元素
*/
public E removeFirst();
/**
* 删除线性表中的表尾元素
* @return 表尾元素
*/
public E removeLast();
/**
* 在线性表中删除指定元素
* @param e 指定元素
*/
public void removeElement(E e);
/**
* 清空线性表
*/
public void clear();
}
2)LinkedList实现List接口
链表是由多个结点组成的,我们可以先定义一个结点类(Node),用于保存数据项的各项信息:
由于我们所定义的结点类是不能让外界进行操控,所以将Node类私有化,只允许在本类中被调用。
package com.openlab.链表;
import com.openlab.list.List;
public class LinkedList<E> implements List<E> {
private Node head; // 指向虚拟头节点的头指针
private Node rear; // 指向尾结点的尾指针
private int size; // 记录元素的个数
//无参构造函数
public LinkedList() {
head = new Node();
rear = head;
size = 0;
}
//有参构造函数(传入的参数是数组)
//将数组拆分开,将元素一个一个加入链表中,组成单链表
public LinkedList(E[] arr) {
this();
for (E e : arr) {
addLast(e);
}
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0 && head.next == null;
}
@Override
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("插入角标非法!");
}
Node n = new Node(e, null);
if (index == 0) { // 头插
n.next = head.next;
head.next = n;
if (size == 0) { //当表中没有元素时,插入元素
rear = n;
}
} else if (index == size) { // 尾插
rear.next = n;
rear = rear.next;
} else { // 一般插入
Node p = head;
for (int i = 0; i < index; i++) {
p = p.next;
}
n.next = p.next;
p.next = n;
}
size++;
}
@Override
public void addFirst(E e) {//获取表头元素
add(0, e);
}
@Override
public void addLast(E e) {//获取表尾元素
add(size, e);
}
@Override
public E get(int index) {//获取指定index的元素
if (index < 0 || index >= size) {
throw new IllegalArgumentException("查找角标非法!");
}
if (index == 0) {//表头元素
return head.next.data;
} else if (index == size - 1) {//表尾元素
return rear.data;
} else {
Node p = head;
//找指定index的元素,p就移动index次
for (int i = 0; i <= index; i++) {
p = p.next;
}
return p.data;
}
}
@Override
public E getFirst() { //获取表头元素
return get(0);
}
@Override
public E getLast() { //获取表尾元素
return get(size - 1);
}
@Override
public void set(int index, E e) { //修改指定的index处元素为新元素e
// TODO Auto-generated method stub
if (index < 0 || index >= size) {
throw new IllegalArgumentException("查找角标非法!");
}
if (index == 0) { //修改表头元素
head.next.data = e;
} else if (index == size - 1) { //修改表尾元素
rear.data = e;
} else {
Node p = head;
for (int i = 0; i <= index; i++) {
p = p.next;
}
p.data = e;
}
}
@Override
public boolean contains(E e) {
// TODO Auto-generated method stub
return find(e) != -1;
}
@Override
public int find(E e) {
int index = -1;
if (isEmpty()) {//判空
return index;
}
Node p = head;
while (p.next != null) {
p = p.next;
index++;
if (p.data == e) {
return index;
}
}
return -1;
}
@Override
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("删除角标非法!");
}
E res = null;
if (index == 0) { // 头删
Node p = head.next;
res = p.data;
head.next = p.next;
p.next = null;
p = null;
if (size == 1) { //当删除表中最后一个元素时
rear = head;
}
} else if (index == size - 1) { // 尾删
Node p = head;
res = p.data;
while (p.next != rear) {
p = p.next;
}
p.next = null;
rear = p;
} else {//一般删除
Node p = head;
for (int i = 0; i < index; i++) {
p = p.next;
}
Node del = p.next;
res = p.next.data;
p.next = del.next;
del.next = null;
}
size--;
return res;
}
@Override
public E removeFirst() {//移除表头元素
return remove(0);
}
@Override
public E removeLast() {//移除表尾元素
return remove(size - 1);
}
@Override
public void removeElement(E e) {
//先用find方法找个该结点,返回结点的下标
int index = find(e);
if (index == -1) {
throw new IllegalArgumentException("元素不存在!");
}
remove(index);
}
@Override
public void clear() {//清空表
head.next = null;
rear = head;
size = 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedList:size=" + getSize() + "\n");
if (isEmpty()) {
sb.append("[]");
} else {
sb.append('[');
Node p = head;
while (p.next != null) {
p = p.next;
if (p == rear) {
sb.append(p.data + "]");
} else {
sb.append(p.data + ",");
}
}
}
return sb.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof LinkedList) {
LinkedList list = (LinkedList) obj;
if (getSize() == list.getSize()) {
for (int i = 0; i < getSize(); i++) {
if (get(i) != list.get(i)) {
return false;
}
}
return true;
}
}
return false;
}
/**
* 单项列表的节点
*/
private class Node {
E data; // 数据域
Node next; // 指针域
public Node() {
this(null, null);
}
public Node(E data, Node next) {
this.data = data;
this.next = next;
}
@Override
public String toString() {
return data.toString();
}
}
}