文章目录
- 🌰导语
- 🌰链表的定义及基本结构
- 🌰单链表
- 🥕单链表特点
- 🌰双向链表
- 🥕双链表特点
- 🌰循环链表
- 🥕循环链表特点
- 🌰链表的操作
- 🍆链表的插入
- 🫘链头插入
- 🫘链间插入
- 🍆链表的删除
- 🫘链头删除
- 🫘链间删除
- 🍆链表的查询
- 🌰链表的应用场景
- 🌰链表与数组的比较
- 🌳存储方式
- 🌳插入和删除操作
- 🌳访问效率
- 🌳空间效率
- 🌰结语
🌰导语
链表是一种常用的数据结构,通过节点之间的指针连接而成,具有动态性和高效的插入删除操作。本文将深入介绍链表的定义、类型、操作以及应用场景,帮助读者全面理解链表的原理和使用。
🌰链表的定义及基本结构
链表是一种由节点组成的数据结构,每个节点包含存储数据的部分以及指向下一个节点的指针。通过节点之间的指针连接,形成了链表的结构。链表可以分为单链表、双向链表和循环链表等不同类型,它们各自具有特定的特点和应用场景。
- 数据元素:节点存储的实际数据。数据可以是任意类型,例如整数、字符、字符串、对象等。
- 指针(或引用):指向下一个节点的指针。它存储了下一个节点在内存中的地址,通过这个指针可以找到链表的下一个节点。
🌰单链表
单链表是最简单的链表类型,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表的尾节点指针为空,表示链表的结束。单链表的插入、删除操作相对简单高效,但随机访问的效率较低。
结构图:
🥕单链表特点
-
非连续存储:单链表中的节点在内存中可以是任意位置,不要求连续存储。每个节点通过指针指向下一个节点,从而将它们连在一起。
-
动态性:相较于数组,单链表的长度可以动态地增减,不需要预先分配内存空间。这使得单链表在需要频繁插入和删除节点的场景中更加灵活。
-
搜索效率相对较低:由于单链表的节点只能通过指针一个一个地遍历,因此搜索某个特定的节点需要遍历整个链表,时间复杂度为O(n),其中n是链表的长度。相比之下,数组可以使用索引直接访问元素,搜索效率更高。
-
内存空间的额外开销:单链表中的每个节点除了存储数据外,还需要存储下一个节点的指针,这导致了额外的内存开销。
-
插入和删除效率较高:相对于数组,单链表在插入和删除节点时效率较高。插入一个节点只需要改变相邻节点的指针,而删除一个节点只需要改变前一个节点的指针指向下一个节点,不需要移动其他元素。
-
灵活性:单链表可以方便地进行节点的插入和删除操作,可以根据实际需要进行自由调整。
🌰双向链表
双向链表在单链表的基础上增加了一个指向前一个节点的指针,使得节点既能够往后访问,也能够往前访问。这样的设计使得插入和删除操作更加灵活便捷,但相应地占用了更多的存储空间。
结构图:
🥕双链表特点
-
支持双向遍历:相对于单向链表,双向链表支持双向遍历,可以从前往后、从后往前遍历链表。
-
插入、删除节点效率更高:相较于单向链表,双向链表在插入和删除某个节点时,只需要改变相邻两个节点的指向,效率更高,尤其是在删除链表中某个元素时更加便捷。
-
需要更多的内存空间:相比单向链表,双向链表需要更多的内存空间来存储额外的指针,增加了额外的空间开销。
-
内存存储不连续:相对于数组,链表中节点的内存存储位置不连续,需要使用指针进行串联,这可以更加灵活地进行插入、删除和移动节点的操作。
-
操作复杂度较高:插入、删除、移动等操作,需要修改前后节点的指针信息,操作比较繁琐。
🌰循环链表
循环链表是一种特殊的链表类型,链表的尾节点指针指向头节点,形成一个循环的结构。循环链表可以通过尾节点快速访问链表的头部,常用于某些特定场景,如循环队列的实现。
结构图:
🥕循环链表特点
-
首位相连:循环链表的最后一个节点指向链表的第一个节点,使得链表成为一个环形结构。这样,链表的结束节点与开始节点相连,可以实现无限循环,更加灵活和方便。
-
操作始终成立:由于循环链表始终是一个环形的结构,因此操作(例如插入、删除、查找等)始终处于链表中,这也保证了操作始终能够完成。
-
遍历循环:与单向链表相比,在遍历时需要注意指针不要陷入死循环。否则会导致遍历永远无法结束。
-
内存空间的额外开销:与单向链表相比,循环链表需要多一个指向头部节点的指针,增加了额外的空间开销。
-
插入和删除效率较高:相对于数组,循环链表在插入和删除节点时效率比较高。插入一个节点时只需要修改相邻节点的指针即可,而删除一个节点时只需要改变前一个节点的指针指向下一个节点即可。
🌰链表的操作
链表的常见操作包括插入、删除和查询。链表的插入操作可以在指定位置或者链表尾部插入一个节点,只需要调整相应节点的指针即可。链表的删除操作类似,只需要改变相应节点的指针即可删除指定节点。链表的访问操作需从头节点开始遍历,直到找到目标节点或遍历到链表尾部。同时,需要注意链表操作时需要处理特殊情况,如链表为空或插入删除节点是头节点或尾节点的情况。
🍆链表的插入
🫘链头插入
代码示例
class Node:
def __init__(self, data):
self.data = data
self.next = None
def insert_at_head(head, value):
# 创建新节点
new_node = Node(value)
# 将新节点的下一个指针指向当前头节点
new_node.next = head
# 更新头节点为新节点
head = new_node
return head
def display_linked_list(head):
current = head
while current:
print(current.data, "-> ", end="")
current = current.next
print("NULL")
# 创建一个空链表
head = None
# 在头部插入节点
head = insert_at_head(head, 3)
head = insert_at_head(head, 2)
head = insert_at_head(head, 1)
# 打印链表
display_linked_list(head)
打印结果:
1 -> 2 -> 3 -> NULL
🫘链间插入
程序示例:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def insert_in_between(node, value):
if not node:
return None
new_node = Node(value)
new_node.next = node.next
node.next = new_node
def display_linked_list(head):
current = head
while current:
print(current.data, "-> ", end="")
current = current.next
print("NULL")
# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
head.next = node2
node2.next = node3
node3.next = node4
# 在节点2和节点3之间插入节点
insert_in_between(node2, 2.5)
# 打印链表
display_linked_list(head)
打印结果:
1 -> 2 -> 2.5 -> 3 -> 4 -> NULL
🍆链表的删除
🫘链头删除
代码示例:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def delete_at_head(head):
if not head:
return None
new_head = head.next
head.next = None
return new_head
def display_linked_list(head):
current = head
while current:
print(current.data, "-> ", end="")
current = current.next
print("NULL")
# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
head.next = node2
node2.next = node3
# 删除头节点
head = delete_at_head(head)
# 打印链表
display_linked_list(head)
运行结果:
2 -> 3 -> NULL
🫘链间删除
示例代码:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def delete_in_between(head, value):
if not head:
return None
current = head
prev = None
while current and current.data != value:
prev = current
current = current.next
if not current:
return head
if not prev:
head = head.next
else:
prev.next = current.next
current.next = None
return head
def display_linked_list(head):
current = head
while current:
print(current.data, "-> ", end="")
current = current.next
print("NULL")
# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
head.next = node2
node2.next = node3
node3.next = node4
# 删除节点3
head = delete_in_between(head, 3)
# 打印链表
display_linked_list(head)
运行结果:
1 -> 2 -> 4 -> NULL
🍆链表的查询
代码示例:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def search_node(head, value):
current = head
while current:
if current.data == value:
return True
current = current.next
return False
# 创建一个链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
head.next = node2
node2.next = node3
node3.next = node4
# 查询节点3
found = search_node(head, 3)
if found:
print("节点找到")
else:
print("节点未找到")
🌰链表的应用场景
链表在实际应用中有许多重要的应用场景。其中,LRU Cache(最近最少使用缓存)获取和替换缓存中的数据时,可以使用链表实现高效的插入和删除操作。链表反转可以使用链表的插入和删除操作实现,将原链表的节点反向连接,获得反转后的链表。另外,链表排序也是链表的经典应用之一,通过比较和交换链表节点的值,实现链表的排序操作。
🌰链表与数组的比较
链表和数组是两种不同的数据结构,它们在以下方面有所区别:
🌳存储方式
数组是一块连续的内存空间,可以通过下标访问任意位置的元素;而链表是由节点组成,每个节点包含数据和指向下一个节点的指针。
🌳插入和删除操作
在数组中,插入和删除操作可能需要移动其他元素以保持连续性,时间复杂度为 O(n);而链表中插入和删除操作只需要更新节点的指针,时间复杂度通常为 O(1)。
🌳访问效率
在数组中,通过下标可以直接访问元素,时间复杂度为 O(1);而链表需要从头节点开始遍历,平均情况下需要 O(n) 时间。
🌳空间效率
对于相同的元素数量,链表通常需要更多的空间,因为每个节点都需要额外的指针来指向下一个节点;而数组只需要连续的内存空间。
🌰结语
链表作为一种重要的数据结构,在算法和数据结构中扮演着重要的角色。通过深入理解链表的原理和操作,我们可以更好地应用它来解决实际问题。
链表的灵活性使得它在许多领域有着广泛的应用。其中,LRU Cache就是一个很好的例子。LRU Cache是一种常见的缓存策略,它会保留最近最常使用的数据,而淘汰最近最少使用的数据。这可以通过链表来实现。链表的头部表示最近最常使用的数据,而尾部表示最近最少使用的数据。当有新数据加入时,我们可以将其添加到链表的头部。而当缓存满时,我们可以淘汰链表尾部的数据。这就是链表的高效插入和删除操作在实际应用中的体现。
链表的反转也是一个重要的应用场景。通过链表的插入和删除操作,我们可以将原始链表的节点逐个取出,并依次插入到新链表的头部,从而实现链表的反转。这个过程只需要简单地修改指针的指向,时间复杂度为O(n),其中n为链表的长度。链表的反转在实际开发中经常遇到,比如反转字符串、反转链表以及翻转二叉树等。
此外,链表的排序也是另一个常见的应用场景。链表的排序可以采用排序算法中的一些经典方法,比如插入排序、归并排序和快速排序等。其中,归并排序在链表中有着很好的适用性。归并排序是一种分治的排序算法,通过递归地将链表划分为更小的子链表,然后将它们按顺序合并。这样,在每一层递归中,我们可以通过比较两个已排序的子链表的头部节点,选择较小的节点,进行合并。通过这种方式,我们可以实现链表的排序操作。
在选择使用链表还是数组时,我们需要根据实际情况权衡它们之间的优缺点。链表的插入和删除操作相对高效,而数组的随机访问效率较高。因此,在需要频繁插入和删除的场景下,链表是一个更好的选择。而在需要快速根据索引查找元素的场景下,数组更具优势。此外,在空间方面,链表需要额外的指针来连接节点,而数组则需要连续的存储空间。因此,如果内存空间有限,我们可能需要考虑使用链表。
总结起来,链表是一种重要的数据结构,具有许多广泛应用的场景。通过深入理解链表的原理和操作,我们可以更好地应用它来解决实际问题。无论是LRU Cache的实现、链表的反转还是链表的排序,都需要我们熟练掌握链表的特性和操作方法。
🏫博客主页:魔王-T
🏯系列专栏:结构算法
🥝大鹏一日同风起 扶摇直上九万里
❤️感谢大家点赞👍收藏⭐评论✍️