帮你把链表操作学个通透!
前言
链表是大家在大学数据结构课程中首先接触到的结构。对于许多人而言,它是打开数据结构这扇神秘大门的钥匙。我到现在都记忆深刻那个场景,老师在台上画链表,当时虽然没听太懂,但是也算是我数据结构的启蒙课了!
在这篇文章中,我们将一起回顾和学习链表的基本概念、操作方法和应用场景。希望通过本文,你能够对链表有一个全面而深入的理解,并能够在实际编程中熟练运用。
什么是链表?
链表是一种线性数据结构,但与数组不同的是,链表的元素在内存中不是连续存储的。每个元素(称为节点)包含两部分:数据和指向下一个节点的指针。这种结构使得链表在插入和删除操作上具有很高的灵活性。
链表的基本结构
链表的基本结构由节点组成,每个节点包含两个部分:
- 数据域:存储数据。
- 指针域:存储指向下一个节点的地址。
一个简单的链表节点可以用以下结构表示(以 JavaScript 为例):
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
单链表 双链表 循环链表
单链表:
它是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。。
双链表:
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
循环链表
循环链表,顾名思义,就是链表首尾相连。循环链表可以用来解决约瑟夫环问题。
链表的定义
许多同学在面试时遇到的一个常见问题是无法正确地定义链表节点。这主要是因为他们在平时刷题时,LeetCode等平台通常已经为他们提供了现成的链表节点定义,他们只需要直接使用即可。因此,当面试官要求他们从头开始定义链表节点时,他们往往会出现各种错误。下面是JS语言版本
class ListNode {
public val: number;
public next: ListNode|null = null;
constructor(value: number) {
this.val = value;
this.next = null;
}
}
基本操作
1. 创建链表
创建一个链表可以通过连接多个节点来实现。例如:
const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);
node1.next = node2;
node2.next = node3;
2. 遍历链表
遍历链表是指从头节点开始,依次访问每个节点,直到到达链表的末尾。
function printList(head) {
let current = head;
while (current !== null) {
console.log(current.val);
current = current.next;
}
}
3. 插入节点
在链表中插入节点可以分为三种情况:
- 在头部插入:将新节点插入到链表的头部。
- 在中间插入:将新节点插入到链表的某个位置。
- 在尾部插入:将新节点插入到链表的尾部。
function insertAtHead(head, val) {
const newNode = new ListNode(val);
newNode.next = head;
return newNode;
}
function insertAtPosition(head, val, position) {
const newNode = new ListNode(val);
if (position === 0) {
newNode.next = head;
return newNode;
}
let current = head;
for (let i = 0; i < position - 1 && current !== null; i++) {
current = current.next;
}
if (current !== null) {
newNode.next = current.next;
current.next = newNode;
}
return head;
}
function insertAtTail(head, val) {
const newNode = new ListNode(val);
if (head === null) {
return newNode;
}
let current = head;
while (current.next !== null) {
current = current.next;
}
current.next = newNode;
return head;
}
4. 删除节点
在链表中删除节点也可以分为三种情况:
- 删除头部节点:将链表的头部节点删除。
- 删除中间节点:将链表的某个位置的节点删除。
- 删除尾部节点:将链表的尾部节点删除。
function deleteAtHead(head) {
if (head === null) {
return null;
}
return head.next;
}
function deleteAtPosition(head, position) {
if (head === null) {
return null;
}
if (position === 0) {
return head.next;
}
let current = head;
for (let i = 0; i < position - 1 && current !== null; i++) {
current = current.next;
}
if (current !== null && current.next !== null) {
current.next = current.next.next;
}
return head;
}
function deleteAtTail(head) {
if (head === null) {
return null;
}
if (head.next === null) {
return null;
}
let current = head;
while (current.next.next !== null) {
current = current.next;
}
current.next = null;
return head;
}
应用场景
链表在许多实际应用中都非常有用,例如:
- 内存管理:操作系统中的内存管理使用链表来管理空闲内存块。
- 动态数据结构:链表可以动态地增加或减少节点,适用于需要频繁插入和删除的场景。
- 队列和栈:可以使用链表实现队列和栈数据结构。
总结
通过本文的学习,我们一起解了链表的类型、基本概念、节点结构、基本操作以及应用场景。链表作为一种灵活的数据结构,在许多实际问题中都有着广泛的应用。希望这篇文章能够帮助你对链表有一个全面而深入的理解,并能够在实际编程中熟练运用。
如果你有任何疑问或需要进一步的解释,请随时留言交流。祝你学习愉快!