简介
同数组一样,链表
也是一种线性结构。但是同数组不一样的是链表并不需要一组连续的内存空间来存储数据。取而代之的是,它通过“指针”将一组零散的内存串联起来,用一张图来体现链表
和数组
的区别如下:
这样做的好处就是:
- 可以动态的为链表申请所需要的内存
举个例子,假设我们要申请一个100MB大小的数组,如果内存中没有连续的、足够大的存储空间时,即使内存的剩余总空间大于100MB,仍然会申请失败;而链表就恰恰相反,因为它并不需要一块连续的内存空间,所以只要剩余总空间大于100MB就能够成功申请。
我们知道在进行数组的插入、删除操作时,为了保持内存的连续性,需要做大量的数据移动操作,所以时间复杂度为O(n)。而在链表中插入或删除一个数据,并不需要做这些数据移动操作,因为链表的存储空间不需要连续性。只需要将"指针"重定向即可。如下图所示:
- 但是有利就有弊
对于访问第K个元素的操作,链表的表现就远不如数组那么高效了。因为链表中的数据并非连续性的,所以无法向数组那样根据首地址和下标,通过寻址公式即可计算出对应的内存地址。链表需要根据指针一个节点一个节点的遍历,直到找到相应的节点位置。
常用链表
链表的种类五花八门。但是一般情况下,我们在实际开发中会用到的链表包含:单链表
和 双向链表
单链表
单链表只有一个方向,单链表中的每一个节点只有一个后继指针指向后面的节点。如下所示:
从上图中可以看出,单链表中有两个节点比较特殊:分别是首尾节点。首节点也称为head(头节点),它可以用来记录链表的首地址,并通过它来遍历整条链表。尾节点(tail)的特殊之处在于它的next指针并不指向下一个节点,而是指向一个空地址NULL。
双向链表
顾名思义,双向链表支持两个方向。每个节点不止有一个后继指针next指向后面的节点,还有一个前驱指针pre指向前面的节点。如下所示:
链表的操作
每一个链表都包含以下几种基本操作
- addLast: 在链表的末尾添加一个元素
- removeLast: 删除链表末尾的元素
- addHead: 添加一个元素到链表的头部
- removeHead: 删除链表头部的元素
- delete(Object value) 删除指定value的元素
- find(Object value) 查找指定value的元素
每一种操作,都需要考虑到两种情况
- 如果链表中不存在任何元素则直接将被插入元素设置为链表的头部元素(head)
- 相反,如果链表此已经有数据,则将链表中最后一个元素的指针指向被插入元素,然后将被插入元素置为末尾元素(tail)
代码实现
以双向链表
的实现为例,如下是一个双向链表的节点(Node)实现。并在此基础上实现各个基本操作
class Node {
private T value;
private Node prev;
private Node next;
Node(T value, Node prev, Node next) {
this.value = value;
this.prev = prev;
this.next = next;
}
}
addLast和removeLast
// 向链表中末尾添加元素
public void addLast(Object obj){
Node newTail = new Node(obj);
if(size == 0){
// 如果链表中没有数据,则将被插入元素设置为head和tail
head = tail = newHead;
}else{
tail.next = newTail;
tial = newTail;
}
size++;
}
//删除链表尾部元素
public Object removeLast(){
if(size == 0) {
return null;
} else {
Node current = tail;
Node prevNode = tail.prev;
Object obj = tail.value;
prevNode.next = null;
tail = prevNode;
size--;
return obj;
}
}
addHead和removeHead
//在链表头添加元素
public Object addHead(Object obj){
Node newHead = new Node(obj);
if(size == 0){
head = newHead;
}else{
newHead.next = head;
head = newHead;
}
size++;
return obj;
}
//在链表头删除元素
public Object removeHead(){
Object obj = head.data;
head = head.next;
size--;
return obj;
}
delete(Object value)
//删除指定的元素,删除成功返回true
public boolean delete(Object value){
if(size == 0){
return false;
}
Node current = head;
Node previous = head;
while(current.data != value){
if(current.next == null){
return false;
}else{
previous = current;
current = current.next;
}
}
//如果删除的节点是第一个节点
if(current == head){
head = current.next;
size--;
}else{//删除的节点不是第一个节点
previous.next = current.next;
size--;
}
return true;
}
find(Object obj)
//查找指定元素,找到了返回节点Node,找不到返回null
public Node find(Object obj){
Node current = head;
int tempSize = size;
while(tempSize > 0){
if(obj.equals(current.data)){
return current;
}else{
current = current.next;
}
tempSize--;
}
return null;
}
双向链表操作的时间复杂度
操作 | 时间复杂度 | 备注 |
---|---|---|
addHead | O(1) | 向链表头部插入元素 |
addLast | O(1) | 插入元素到链表末尾 |
add | O(n) | 在链表的任意位置插入元素 |
removeHead | O(1) | 删除链表头部元素 |
removeLast | O(1) | 删除链表尾部元素 |
remove | O(n) | 删除链表中任意位置的元素 |
find | O(n) | 在链表中查找某元素 |