1. 概述
链表是一种数据结构,在内存中通过节点记录内存地址而相互链接形成一条链的储存方式。链表的插入和删除都比较快,缺点是查找比较慢。除非需要频繁的通过下标来随机访问数据,否则在很多使用数组的地方都可以用链表代替。相比数组而言,链表在内存中不需要连续的区域,只需要每一个节点都能够记录下一个节点的内存地址,通过引用进行查找,因此链表增删操作时间消耗很小,而查找遍历时间消耗很大。
2. 链表的分类
链表常用的有 3 类: 单链表、双向链表、循环链表。
单链表:
单链表是链表中结构最简单的,是一种线性表,实际上是由节点(Node)组成的。一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。
单向链表的实现:
package cn.zzw.singlelink;
/**
* @author 鹭岛猥琐男
* @create 2019/8/13 5:48
*/
public class SingleLink<T> {
private Node first;
private Node last;
private int size;
private class Node<T> {
private Node next;
private T data;
Node(T data, Node next) {
this.data = data;
this.next = next;
}
}
/*
* 向链表头部添加元素
*/
public void push(T t) {
Node f = first;
Node newNode = new Node(t, f);
//因为是向链表头部添加元素,所以第一个节点变成新的节点
first = newNode;
//如果原来的第一个节点是空,则原链表为空,添加后,链表仅有一个节点,最后一个节点也是新添加的节点
if (f == null) {
last = newNode;
}
size++;
}
/*
* 向链表的尾部添加元素
*/
public void add(T t) {
Node l = last;
Node newNode = new Node(t, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
/**
* 打印结点
*/
public void printLink() {
Node curNode = first;
while (curNode != null) {
System.out.print(curNode.data + " ");
curNode = curNode.next;
}
System.out.println();
}
/*
* 返回链表的长度
*/
public int getSize() {
return size;
}
/*
* 删除指定位置的节点
*/
public void remove(int index) {
//先判断index是否合法
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
//当需要删除的是头节点,直接将头节点指向下一个节点
if (index == 0) {
Node removeNode = first;
first = removeNode.next;
} else {
Node prev = first;
for (int i = 0; i < index - 1; i++) {
//prev此时指向的是被删除节点的前一个位置
prev = prev.next;
}
//需要被删除的节点
Node removeNode = prev.next;
prev.next = removeNode.next;
removeNode.next = null;
}
size--;
}
/*
* 获取指定位置的节点的值
*/
public T get(int index) {
//先判断index是否合法
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
Node<T> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x.data;
}
}
测试类:
package cn.zzw.singlelink;
/**
* @author 鹭岛猥琐男
* @create 2019/8/13 5:48
*/
public class TestLink
{
public static void main(String[] args)
{
SingleLink<Integer> singleLink =new SingleLink();
singleLink.add(8);
singleLink.add(9);
singleLink.push(1);
singleLink.push(2);
System.out.println("链表的长度:"+singleLink.getSize());
//按照上面的添加顺序,链表的排序应该是:2,1,8,9
singleLink.printLink();
//获取指定节点的值
Integer index0 = singleLink.get(0);
Integer index1 = singleLink.get(1);
System.out.println("获取头节点的值:"+index0);
System.out.println("获取节点1的值:"+index1);
//删除指定位置的节点
singleLink.remove(3);
System.out.println("删除节点后的列表:");
singleLink.printLink();
System.out.println("删除后链表的长度:"+singleLink.getSize());
}
}
测试结果:
链表的长度:4
2 1 8 9
获取头节点的值:2
获取节点1的值:1
删除节点后的列表:
2 1 8
删除后链表的长度:3
双向链表:
双向链表即是这样一个有序的结点序列,每个节点分为三个部分,一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址,还有一部分存储上一个节点的地址。第一个节点的存储的上一个节点的地址为空值,最后一个节点存储的下一个节点的地址为空值。双向链表是可以从两个方向进行遍历的。
双向链表的实现:
package cn.zzw.doublelink;
public class DoubleLink<E> {
private Node first;
private Node last;
private int size;
/*
* 与单向链表相比,多了指向前一个节点的prev
*/
private class Node<E> {
private E data;
private Node<E> prev;
private Node next;
Node(E data, Node prev, Node next) {
this.data = data;
this.prev = prev;
this.next = next;
}
}
/*
* 向链表的尾部添加元素
*/
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<E>(e, l, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
}
/*
* 向链表头部添加元素
*/
public void push(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(e, null, f);
//因为是向链表头部添加元素,所以第一个节点变成新的节点
first = newNode;
//如果原来的第一个节点是空,则原链表为空,添加后,链表仅有一个节点,最后一个节点也是新添加的节点
if (f == null) {
last = newNode;
} else {
f.prev = newNode;
}
size++;
}
/**
* 打印结点
*/
public void printLink() {
Node curNode = first;
while (curNode != null) {
System.out.print(curNode.data + " ");
curNode = curNode.next;
}
System.out.println();
}
/*
* 返回链表的长度
*/
public int getSize() {
return size;
}
/*
* 删除指定位置的节点,并返回删除节点的值
*/
public E remove(int index) {
//先判断index是否合法
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
return unlink(node(index));
}
/*
* 查找指定位置的节点
*/
Node<E> node(int index) {
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
E unlink(Node<E> x) {
final E element = x.data;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.data = null;
size--;
return element;
}
/*
* 获取指定位置的节点的值
*/
public E get(int index) {
//先判断index是否合法
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
return node(index).data;
}
}
测试类:
package cn.zzw.doublelink;
import cn.zzw.singlelink.SingleLink;
/**
* @author 鹭岛猥琐男
* @create 2019/8/13 5:48
*/
public class TestDoubleLink
{
public static void main(String[] args)
{
DoubleLink<Integer> doubleLink =new DoubleLink();
doubleLink.add(8);
doubleLink.add(9);
doubleLink.push(1);
doubleLink.push(2);
System.out.println("链表的长度:"+doubleLink.getSize());
//按照上面的添加顺序,链表的排序应该是:2,1,8,9
doubleLink.printLink();
//获取指定节点的值
Integer index0 = doubleLink.get(0);
Integer index1 = doubleLink.get(1);
System.out.println("获取头节点的值:"+index0);
System.out.println("获取节点1的值:"+index1);
//删除指定位置的节点
doubleLink.remove(3);
System.out.println("删除节点后的列表:");
doubleLink.printLink();
System.out.println("删除后链表的长度:"+doubleLink.getSize());
}
}
测试结果:
链表的长度:4
2 1 8 9
获取头节点的值:2
获取节点1的值:1
删除节点后的列表:
2 1 8
删除后链表的长度:3
Java自带的集合包中有实现双向链表,路径是:java.util.LinkedList,以上双向链表的实现,就是参考它的源码来实现的。
循环链表:
循环链表指的是在单向链表和双向链表的基础上,将两种链表的最后一个结点指向第一个结点从而实现循环。
循环列表分为单向循环链表和双向循环链表,循环链表和单向列表以及双向链表的区别在于,最后一个结点是否指向第一个结点,这里就不再展开了。