定义
链表是一种数据结构, 它由一个个节点组成, 每个节点包含数据和指向下一个节点的指针, 这里的指针指向的其实就是下一个节点的引用。
分类
根据节点的指针数量, 链表可以分为单向链表、双向链表和循环链表。单向链表每个节点只有一个指针, 指向下一个节点; 双向链表每个节点有两个指针, 分别指向前一个节点和后一个节点; 循环链表是一种特殊的链表, 它的最后一个节点的指针指向头节点。
与数组的比较
相同点
- 都是线性存储结构, 可以依次遍历其中所有的元素。
- 元素在内存中的存储顺序都是连续的
区别
- 数组的大小是固定的, 在创建后无法动态扩展, 而链表的大小可以根据需要动态增减。
- 数组的访问速度比较快, 可以通过下标直接访问元素; 而链表的访问速度较慢, 需要遍历整个链表才能访问其中的某个元素。
- 数组的内存分配是连续的, 如果需要插入或删除数组中的元素, 需要移动大量的元素; 而链表只需要改变指针的方向即可。
链表的基本操作
- 单向链表的创建
public class Node {
// 数据
public int value;
// 指针
public Node next;
public Node(int value) {
this.value = value;
}
public static void main(String[] args) {
Node listnode=new Node(1);
}
}
- 遍历链表
迭代方式:
public void traverseLinkedList(Node head) {
Node pointer = head;
while (pointer != null) {
// 指针指向下一个节点
pointer = pointer.next;
}
}
递归方式:
public void traverseLinkedList(Node head) {
if (head == null) {
return;
} else {
// 递归遍历下一个节点
traverseLinkedList(head.next);
}
}
-
链表插入
单链表的插入需要考虑三种情况: 头部、中部和尾部
(1) 头部插入
头部插入比较简单, 只需要将新节点的指针指向头部, 然后将新节点作为头节点。
(2) 在链表中间插入
需要先遍历链表找到要插入的位置, 然后将当前位置接入到前驱节点和后驱节点之间。
需要注意的是, 在遍历链表时, 必须要在目标节点的前一个位置停下来, 不然将无法获取到前驱节点
(3) 在单链表的结尾插入
尾部插入比较简单, 将只需要将尾部节点指向新节点即可。
综上, 我们写出链表插入的方法如下:
/**
* 链表插入
* @param head 头节点
* @param newNode 新节点
* @param position 插入的位置, 从 1 开始
* @return 插入后得到的链表头节点
*/
public Node insertNode(Node head, Node newNode, int position) {
if (head == null) {
// 插入的节点即为头节点
return newNode;
}
// 判断索引是否越界
int size = getLength(head);
if (position < 1 || position > size + 1) {
throw new RuntimeException("索引参数越界");
}
// 表头插入
if (position == 1) {
newNode.next = head;
head = newNode;
return head;
}
// 中间插入 (包括尾部)
Node curNode = head;
int index = 1;
while (index < position - 1) {
curNode = curNode.next;
index++;
}
newNode.next = curNode.next;
curNode.next = newNode;
return head;
}
public int getLength(Node head) {
int length = 0;
Node node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
-
链表删除
删除同样分为在删除头部元素,删除中间元素和删除尾部元素。
(1)删除表头节点
将头节点重新指向头节点的下一个节点即可。
(2)删除最后一个节点将尾节点的前一个节点指向 null 即可。
(3)删除中间节点找到需要删除的节点, 将该节点的前一个节点重新指向该节点的下一个节点即可。
删除节点的完整实现:
/**
* 删除节点
* @param head 头节点
* @param position 删除节点位置, 取值从 1 开始
* @return 删除后的链表头节点
*/
public Node deleteNode(Node head, int position) {
if (head == null) {
return head;
}
// 判断索引是否越界
int size = getLength(head);
if (position < 1 || position > size) {
throw new RuntimeException("索引参数越界");
}
if (position == 1) {
// 删除的为头节点
return head.next;
}else {
// 删除中间节点 (包括尾节点)
Node curNode = head;
int index = 1;
while (index < position - 1) {
curNode = curNode.next;
index++;
}
curNode.next = curNode.next.next;
}
return head;
}