单链表的定义、表示与基本操作实现
。上一节我们讨论了顺序表,它是一种连续存储的数据结构,但插入和删除操作效率较低。今天,我们将探索单链表——一种动态、非连续的线性表结构,它能高效地处理插入和删除。我会用通俗易懂的语言,结合代码示例,详细介绍单链表的定义、表示和基本操作。最后,我会自然引出双向链表的概念,为后续学习铺垫。
一、单链表的定义和表示
单链表是一种线性数据结构,由一系列节点组成,每个节点存储数据元素和一个指向下一个节点的指针(称为“后继指针”)。这就像一串珠子,每个珠子(节点)包含自己的内容(数据)和一根线(指针)连接到下一个珠子。整个链表通过一个头指针(head)指向第一个节点,如果链表为空,则头指针为None。
- 节点结构:每个节点有两部分:
- 数据域(data):存储实际数据,可以是整数、字符串等。
- 指针域(next):指向下一个节点的地址。如果当前节点是最后一个,则指针为None(表示链表的结尾)。
- 表示方式:在代码中,我们用一个类来表示节点。例如,在Python中:
整个链表通过头指针管理:头指针指向第一个节点,后续节点通过指针逐个连接。class Node: def __init__(self, data): self.data = data # 存储数据 self.next = None # 指向下一个节点的指针,初始为None
单链表的优势在于:
- 动态分配内存:节点在运行时创建和删除,无需预先分配固定大小。
- 高效插入/删除:只需修改指针,无需移动大量元素(像顺序表那样)。
- 内存利用率高:节点可以分散存储。
但缺点也很明显:
- 只能单向遍历:从头部开始,只能向后访问,不能向前。
- 查找效率较低:需要从头开始逐个遍历。
现在,我们进入核心部分——单链表的基本操作实现。我会覆盖创建、插入、删除、查找、遍历等所有常见操作,确保全面详细。所有代码用Python实现,我会逐行解释。
二、单链表基本操作的实现
为了便于理解,我定义一个完整的单链表类 LinkedList,包含所有基本操作。每个操作我都会用通俗语言解释原理、步骤和代码。
1. 创建链表(初始化)
- 原理:创建一个空链表,头指针指向None。
- 代码实现:
class LinkedList: def __init__(self): self.head = None # 头指针初始为None,表示空链表- 解释:
__init__是构造函数,self.head是头指针。当创建链表对象时,如my_list = LinkedList(),头指针指向None,表示没有节点。
- 解释:
2. 插入操作
插入操作有三种常见方式:头插法(在链表头部插入)、尾插法(在尾部插入)和在指定位置插入。我会详细说明每种方式。
-
头插法(插入到头部)
- 原理:新节点成为链表的第一个节点。步骤:创建新节点 → 新节点的指针指向原头节点 → 更新头指针指向新节点。
- 代码实现:
def insert_at_head(self, data): new_node = Node(data) # 创建新节点 new_node.next = self.head # 新节点的指针指向原头节点 self.head = new_node # 更新头指针指向新节点- 解释:例如,链表原为
1 -> 2,插入数据3:新节点3的指针指向1,然后头指针指向3,结果3 -> 1 -> 2。时间复杂度是 $O(1)$。
- 解释:例如,链表原为
-
尾插法(插入到尾部)
- 原理:新节点成为链表的最后一个节点。步骤:创建新节点 → 如果链表为空,直接设为头节点 → 否则,遍历到最后一个节点 → 将最后一个节点的指针指向新节点。
- 代码实现:
def insert_at_tail(self, data): new_node = Node(data) # 创建新节点 if self.head is None: # 如果链表为空 self.head = new_node # 直接设为头节点 else: current = self.head while current.next: # 遍历到最后一个节点(current.next 为 None) current = current.next current.next = new_node # 最后一个节点的指针指向新节点- 解释:例如,链表
1 -> 2,插入3:遍历到节点2(其 next 为 None),然后将2.next指向3,结果1 -> 2 -> 3。时间复杂度是 $O(n)$,因为需要遍历整个链表。
- 解释:例如,链表
-
在指定位置插入
- 原理:在链表的特定索引处插入节点。索引从0开始(头节点索引为0)。步骤:创建新节点 → 找到指定位置的前一个节点 → 修改指针连接新节点。
- 代码实现:
def insert_at_position(self, data, position): if position < 0: raise ValueError("位置不能为负") new_node = Node(data) if position == 0: # 如果插入位置是0(头部) self.insert_at_head(data) else: current = self.head index = 0 # 找到位置 position-1 的节点(新节点的前一个节点) while current and index < position - 1: current = current.next index += 1 if current is None: # 如果位置超出链表长度 raise IndexError("位置超出链表范围") new_node.next = current.next # 新节点指针指向原位置节点 current.next = new_node # 前一个节点指针指向新节点- 解释:例如,链表
1 -> 2 -> 4,在索引1处插入3:找到索引0的节点(数据为1),其 next 原指向2,现在新节点3的 next 指向2,然后1.next指向3,结果1 -> 3 -> 2 -> 4。时间复杂度是 $O(n)$。
- 解释:例如,链表
3. 删除操作
删除操作也有三种:删除头节点、删除尾节点和删除指定节点。
-
删除头节点
- 原理:移除链表的第一个节点。步骤:如果链表不空,更新头指针指向第二个节点。
- 代码实现:
def delete_at_head(self): if self.head is None: # 链表为空 raise Exception("链表为空,无法删除") self.head = self.head.next # 头指针指向第二个节点(原头节点被丢弃)- 解释:例如,链表
3 -> 1 -> 2,删除头部:头指针从3改为指向1,结果1 -> 2。时间复杂度 $O(1)$。
- 解释:例如,链表
-
删除尾节点
- 原理:移除最后一个节点。步骤:遍历到倒数第二个节点 → 将其指针设为None。
- 代码实现:
def delete_at_tail(self): if self.head is None: # 链表为空 raise Exception("链表为空,无法删除") if self.head.next is None: # 如果只有一个节点 self.head = None else: current = self.head while current.next.next: # 遍历到倒数第二个节点(current.next.next 为 None) current = current.next current.next = None # 倒数第二个节点的指针设为None- 解释:例如,链表
1 -> 2 -> 3,删除尾部:遍历到节点2(其 next 指向3),将2.next设为 None,结果1 -> 2。时间复杂度 $O(n)$。
- 解释:例如,链表
-
删除指定节点(按值或位置)
- 原理:删除包含特定数据的节点或指定位置的节点。这里以按值删除为例:找到该节点的前一个节点 → 修改指针跳过该节点。
- 代码实现:
def delete_by_value(self, value): if self.head is None: raise Exception("链表为空,无法删除") if self.head.data == value: # 如果要删除头节点 self.delete_at_head() return current = self.head while current.next: # 遍历,直到找到值匹配节点的前一个节点 if current.next.data == value: current.next = current.next.next # 跳过匹配节点 return current = current.next raise ValueError(f"值 {value} 不在链表中")- 解释:例如,链表
1 -> 2 -> 3,删除值2:找到节点1(其 next 指向2),将1.next指向3(跳过2),结果1 -> 3。时间复杂度 $O(n)$。
- 解释:例如,链表
4. 查找操作
查找操作包括按值查找和按位置查找。
-
按值查找
- 原理:遍历链表,检查每个节点的数据是否匹配目标值。
- 代码实现:
def search_by_value(self, value): current = self.head position = 0 while current: if current.data == value: return position # 返回匹配的索引 current = current.next position += 1 return -1 # 没找到- 解释:例如,链表
1 -> 2 -> 3,查找值2:从头部开始,索引0(数据1)不匹配,索引1(数据2)匹配,返回1。时间复杂度 $O(n)$。
- 解释:例如,链表
-
按位置查找
- 原理:获取指定索引处的节点数据。
- 代码实现:
def get_at_position(self, position): if position < 0: raise ValueError("位置不能为负") current = self.head index = 0 while current and index < position: current = current.next index += 1 if current is None: raise IndexError("位置超出链表范围") return current.data- 解释:例如,链表
1 -> 2 -> 3,获取位置1:遍历到索引1(节点数据为2),返回2。时间复杂度 $O(n)$。
- 解释:例如,链表
5. 遍历操作
- 原理:从头节点开始,逐个访问每个节点并打印数据。
- 代码实现:
def traverse(self): current = self.head while current: print(current.data, end=" -> ") # 打印当前节点数据 current = current.next print("None") # 表示链表结束- 解释:例如,链表
1 -> 2 -> 3,调用traverse()输出1 -> 2 -> 3 -> None。时间复杂度 $O(n)$。
- 解释:例如,链表
6. 其他常用操作
为了全面,我还添加了获取链表长度和判断链表是否为空的辅助操作。
-
获取链表长度
- 原理:遍历链表计数节点数。
- 代码实现:
def get_length(self): count = 0 current = self.head while current: count += 1 current = current.next return count- 解释:时间复杂度 $O(n)$。
-
判断链表是否为空
- 原理:检查头指针是否为None。
- 代码实现:
def is_empty(self): return self.head is None- 解释:时间复杂度 $O(1)$。
完整代码示例
为了方便测试,这里是完整的单链表类定义:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def insert_at_head(self, data):
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def insert_at_tail(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node
def insert_at_position(self, data, position):
if position < 0:
raise ValueError("位置不能为负")
new_node = Node(data)
if position == 0:
self.insert_at_head(data)
else:
current = self.head
index = 0
while current and index < position - 1:
current = current.next
index += 1
if current is None:
raise IndexError("位置超出链表范围")
new_node.next = current.next
current.next = new_node
def delete_at_head(self):
if self.head is None:
raise Exception("链表为空,无法删除")
self.head = self.head.next
def delete_at_tail(self):
if self.head is None:
raise Exception("链表为空,无法删除")
if self.head.next is None:
self.head = None
else:
current = self.head
while current.next.next:
current = current.next
current.next = None
def delete_by_value(self, value):
if self.head is None:
raise Exception("链表为空,无法删除")
if self.head.data == value:
self.delete_at_head()
return
current = self.head
while current.next:
if current.next.data == value:
current.next = current.next.next
return
current = current.next
raise ValueError(f"值 {value} 不在链表中")
def search_by_value(self, value):
current = self.head
position = 0
while current:
if current.data == value:
return position
current = current.next
position += 1
return -1
def get_at_position(self, position):
if position < 0:
raise ValueError("位置不能为负")
current = self.head
index = 0
while current and index < position:
current = current.next
index += 1
if current is None:
raise IndexError("位置超出链表范围")
return current.data
def traverse(self):
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
def get_length(self):
count = 0
current = self.head
while current:
count += 1
current = current.next
return count
def is_empty(self):
return self.head is None
三、引出双向链表
单链表在插入和删除操作上高效,但它只能单向遍历(从头到尾)。这在某些场景下效率不高,例如:
- 需要反向遍历链表时(如查找前一个节点),单链表必须从头开始,耗时 $O(n)$。
- 删除尾节点时,需要遍历整个链表,效率低。
为了解决这些问题,我们引入了双向链表。在双向链表中,每个节点不仅有一个指向下一个节点的指针(next),还有一个指向前一个节点的指针(prev)。这就像珠子之间有两根线,一根向前,一根向后,允许双向遍历。
双向链表的优势:
- 双向遍历:可以从头到尾或从尾到头遍历。
- 高效删除尾节点:直接通过尾指针操作。
- 更灵活的插入/删除:尤其在中间位置。
在下一节课,我们将详细讲解双向链表的定义、表示和实现。现在,你可以尝试基于单链表代码,思考如何修改节点类来支持双向链表(提示:在 Node 类中添加一个 prev 指针)。
3198

被折叠的 条评论
为什么被折叠?



