1.概念
在前面的博客数据结构-数组中,可以看到数组虽然查询比较快,但是插入和删除元素的效率并不高,而且创建一个数组之后不能修改其长度,有什么数据结构可以解决数组的这些缺点吗?有,这个就是本篇需要介绍的数据结构-链表
,不过它的查询效率并不高,在后面我们会介绍。
链表也是一种常见的数据结构,它不是线性存储数据,而是通过前一个元素保存后一元素的引用从而形成一条链。不过链表可以分为多种类型链表,常见的有:单向链表,双向链表等
2. 单向链表
代码实现:
public class SingleLinkedList {
private Node head; // 头部节点
private Node tail; // 尾部节点(非链表最后的null)
private int size; // 链表长度
/**
* 节点
*/
public static class Node {
private Object data;
private Node next;
Node(Object data) {
this.data = data;
}
@Override
public String toString() {
return data == null ? null : data.toString();
}
}
public void add(Object o) {
// 没有元素
if (head == null) {
head = new Node(o);
tail = head;
head.next = null;
} else {
Node cur = new Node(o);
// 原来的尾节点next指向新节点
tail.next = cur;
// 新节点变成现在的尾节点
tail = cur;
// 将新尾节点的next置为null
tail.next = null;
}
size++;
}
public Node find(Object o) {
if (head == null) {
return null;
}
Node cur = head;
while (cur != null) {
// 判断null
if (cur.data == null) {
if (o == null) {
return cur;
}
} else if (cur.data.equals(o)) {
return cur;
}
cur = cur.next;
}
return null;
}
public boolean delete(Object o) {
if (head == null) {
return false;
}
// 如果长度为1
if (size == 1) {
if (head.data.equals(o)) {
head = tail = null;
size--;
return true;
} else {
return false;
}
}
Node cur = head;
Node pre = head;
boolean deleted = false;
while (cur != null) {
// 判断null
if (cur.data == null) {
if (o == null) {
pre.next = cur.next;
deleted = true;
break;
}
} else if (cur.data.equals(o)) {
pre.next = cur.next;
deleted = true;
break;
}
// 修改指向
pre = cur;
cur = cur.next;
}
// 如果删除成功,需要判断当前操作的节点是不是特殊节点
if (deleted) {
if (cur == head) {
head = head.next;
} else if (cur == tail) {
tail = pre;
}
size--;
}
return deleted;
}
public int size() {
return size;
}
public boolean empty() {
return size == 0;
}
@Override
public String toString() {
if (head == null) {
return null;
}
Node cur = head;
StringBuffer stringBuffer = new StringBuffer("(");
while (cur != null) {
stringBuffer.append(cur.data);
stringBuffer.append(",");
cur = cur.next;
}
return stringBuffer.substring(0, stringBuffer.length() - 1) + ")";
}
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(null);
singleLinkedList.add(12);
singleLinkedList.add("string");
// (null,12,string)
System.out.println(singleLinkedList);
// string
Node node = singleLinkedList.find("string");
System.out.println(node);
boolean deleted = singleLinkedList.delete("string");
System.out.println("deleted = " + deleted);
// (null,12)
System.out.println(singleLinkedList);
}
}
- 属性介绍:
- head: 链表的首节点,链表中的最核心的字段,tail和size都是可以通过这个字段计算出来。不过为了快速实现常用方法,这两个而外的字段最好加上。
- tail: 链表的尾节点,方便直接往最后一个节点添加元素,缺点就是需要多维护一个字段,在元素添加和删除特殊节点时要记得修改这个值。
- size: 链表的长度
- 方法介绍
add
: 往链表最后再添加一个元素,需要主要tail的指向需要改变,关键代码:
// 原来的尾节点next指向新节点
tail.next = cur;
// 新节点变成现在的尾节点
tail = cur;
// 将新尾节点的next置为null
tail.next = null;
find
: 查找指定数据所对应的节点,就是通过head.next不断遍历链表节点。不过需要注意处理节点数据为null的情况,不然会出现空指针的问题。delete
:删除指定数据所对应的节点,利用find相同的原理查找到指定的节点,然后修改该节点前一个节点的指向。需要注意如果删除的节点是head/tail这两个特殊节点时,需要同时修改head/tail的指向。
3.双向链表
可以看出双向链表只是在node结构上增加了一个字段pre:表示前一个节点。
public static class Node {
private Object data;
private Node next;
private Node pre;
Node(Object data) {
this.data = data;
}
}
双向链表需要在add和delete方法增加一段维护pre指向的代码,由于逻辑几乎一样,就不做赘述。