数据结构 - 链表
链表(linked list):由一组被称为结点(也叫节点)的数据元素组成的数据结构,每个结点包含2部分:1)结点本身的数据信息(data),称为信息域;2)指向下一个结点的地址信息(next),称为指针域。
head结点:是一个特殊的结节,用于存储链表第一个结点的地址信息,永远指向链表的第一个结点;
tail结点:是一个特殊的结点,永远指向链表的最后一个节点。链表最后一个结点的地址信息(指针域)指向None值,因也叫接地点。
由于链表的每个结点都包含了可以链接起来的地址信息,所以用一个变量就能够访问整个结点序列。
链表是计算机科学领域应用最广泛的高阶数据结构之一,它实现了数据之间保持逻辑顺序,但存储空间不必按顺序的方法。
-
根据结构的不同,链表可以分为单向链表、单向循环链表、双向链表、双向循环链表等。其中,单向链表和单向循环链表的结构如下图所示:
链表常用方法
LinkedList()
:创建空链表,不需要参数,返回值是空链表;
is_empty()
:测试链表是否为空,不需要参数,返回值是布尔值;
append(data)
:追加元素到链表尾部。参数是要追加的元素,无返回值;
iter()
:生成器,遍历链表,无参数,无返回值;
insert(idx, value)
:插入一个元素,参数为插入元素的索引和值;
remove(idx)
:移除1个元素,参数为要移除的元素或索引,并修改链表;
size()
:返回链表的元素数,不需要参数,返回值是个整数;
search(item)
:查找链表某元素,参数为要查找的元素或索引,返回是布尔值。
节点类
python用类(class)来实现链表的数据结构,节点(Node)是实现链表的基本模块,每个节点至少包括两个重要部分:值和指针(引用)。示例:
class Node(object):
def __init__(self, data):
self.data = data
self.next = None
此节点类只有一个构建函数,接收一个数据参数,其中next表示指针域的指针,实例化后得到一个节点对象,如下示例,节点对象数据为4,指针指向None。
node = Node(4)
print(node)
<__main__.Node object at 0x10998b3c8>
这样一个节点对象,可以用一个图例来更形象的说明,如下:链表类
先来看LinkedList类的构建函数:
class LinkedList(object):
def __init__(self):
self.head = None
self.tail = None
此类实例后会生成一个链表对象,初始化了head和tail节点,且两节点都指向None,实例化代码如下:
link_list = LinkedList()
print(link_list)
<__main__.LinkedList object at 0x10b521710>
也可以用图形象的表示这个链表对象,如下:is_empty()函数
is_empty()函数,用于检查链表是否是一个空链表,这个方法只需要检查head节点是否指向None即可,代码如下:
def is_empty(self):
return self.head is None
如果是空列表返回True,否则返回False。
append()函数
append()函数,用于追加元素到链表。如果链表为空,则添加为第一个节点,如果链表非空,则追加为最后一个元素。代码如下:
def append(self, data):
node = Node(data) # 把 Node类实例化得到一个node对象
if self.head is None:
# 如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点
self.head = node
self.tail = node
else:
# 如果链表非空,则将tail节点和上一个节点的next均指向当前node,即将元素追加为最后一个节点
self.tail.next = node
self.tail = node
首先把Node类实例化得到一个node对象。如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点;如果链表非空,则将tail节点和上一个节点的next均指向当前node,即将元素追加为最后一个节点。链表非空时追加元素的过程示意图:iter()函数
iter()函数:生成器,可以遍历链表。遍历链表时,从head开始,直到一个节点的next指向None结束,特别地,需要考虑空链表的情况。代码如下:
def iter(self):
# 如果链表为空,则返回None
if not self.head:
return
cur = self.head
yield cur.data # 当前节点data数据的生成器
while cur.next:
cur = cur.next # 当前节点指向下一个节点并遍历
yield cur.data # 当前节点data数据的生成器
如果链表为空,遍历时直接返回None;如果链表不为空,遍历时,先将当前节点的data返回生成器,再将当前节点指向下一个节点,进行while循环,同时将对应的data返回生成器,直到next指向None,就可以实现非空链表的遍历。
insert()函数
- insert()函数,用于将数据插入到链表的指定索引的位置。
-
通过实例,来说明insert()函数插入数据到链表的过程。例如,需要在链表数据域为6的节点处插入一个新节点,链表如下:
- 操作过程
将新节点的next指针指向数据域为6的这个节点;
将数据域为5的节点的next指针指向新加的节点。 -
过程示意图
- 注意事项
操作过程的两个步骤不能颠倒,如果颠倒,数据域为6的节点会被丢失,数据域为7的节点不再是链表的节点;
链表为空时,返回None或者抛出异常;
插入位置超出链表节点的长度时,抛出异常;
插入位置是链表的最后一个节点时,还需要移动tail结点指向新结点。 - 示例代码
def insert(self, idx, value):
cur = self.head
cur_idx = 0
if cur is None:
raise Exception('The linked_list is empty')
while cur_idx < idx - 1:
cur = cur.next
if cur is None:
raise Exception('list length less than index')
cur_idx += 1
node = Node(value)
node.next = cur.next
cur.next = node
if node.next is None:
self.tail = node
- 在指定的索引位置插入一个节点,前提是需要找到这个位置,在链表中只有采用遍历的方式,具有O(n)的速度,最糟糕时会遍历链表的所有节点,当找到插入点时,其实并不需要当前节点的信息,而是需要前一个节点的信息,所以,代码中巧妙的使用了
while cur_idx < idx-1:
的方式,这样能使用cur这个变量能指向插入位置的上一个节点对象。
remove()函数
remove()函数接收一个idx参数,表示要删除节点的索引。
使用remove()函数时需要考虑如下情况:
空链表,直接抛出异常;
删除第一个节点时,移动head到删除节点的next指针指向的对象;
链表只有一个节点时,把head与tail都指向None即可;
删除最后一个节点时,需要移动tail到上一个节点;
遍历链表时要判断给定的索引是否大于链表的长度,如果大于则抛出异常信息。-
示意图
示例代码:
def remove(self, idx):
cur = self.head
cur_idx = 0
if self.head is None: # 空链表时
raise Exception('The linked_list is empty')
while cur_idx < idx - 1:
cur = cur.next
if cur is None:
raise Exception('list length less than index')
cur_idx += 1
if idx == 0: # 当删除第一个节点时
self.head = cur.next
cur = cur.next
return
if self.head is self.tail: # 当只有一个节点的链表时
self.head = None
self.tail = None
return
cur.next = cur.next.next
if cur.next is None: # 当删除的节点是链表最后一个节点时
self.tail = cur
size()函数
size()函数不接收参数,返回链表中节点的个数,要获得链表的节点个数,必定会遍历链表,直到最后一个节点的next指针指向None时链表遍历完成,遍历时可以用一个累加器来计算节点的个数,如下代码:
def size(self):
cur = self.head
count = 0
if cur is None:
raise Exception('The linked_list is empty')
while cur is not None:
count += 1
cur = cur.next
return count
search()函数
search()函数接收一个参数,表示查找节点中数据域的值。查找时会遍历链表,每到一个节点把当前节点的data值与参数值作比较,最糟糕的情况下会全遍历链表。如果查找到有些数据则返回True,否则返回False,代码如下:
def search(self, item):
cur = self.head
found = False
while cur is not None and not found:
if cur.data == item:
found = True
else:
cur = cur.next
return found
Node类与LinkedList类完整代码
class Node():
'创建节点'
def __init__(self,data):
self.data = data
self.next = None
class LinkedList(object):
'创建列表'
def __init__(self):
'初始化列表'
self.head = None
self.tail = None
def is_empty(self):
'判断链表是否为空'
return self.head is None
def append(self, data):
'追加元素'
node = Node(data) # 把Node类实例化得到一个node对象
if self.head is None:
# 如果链表为空,则将head和tail都指向node,即将元素设置为第一个节点
self.head = node
self.tail = node
else:
# 如果链表非空,则将tail节点和上一个节点的next均指向当前node
self.tail.next = node
self.tail = node
def iter(self):
'遍历链表'
# 如果链表为空,则返回None
if not self.head:
return
cur = self.head
yield cur.data # 当前节点data数据的生成器
while cur.next:
cur = cur.next # 当前节点指向下一个节点并遍历
yield cur.data # 当前节点data数据的生成器
def insert(self, idx, value):
'插入元素'
cur = self.head
cur_idx = 0
if cur is None:
raise Exception('The linked_list is empty')
while cur_idx < idx - 1:
cur = cur.next
if cur is None:
raise Exception('list length less than index')
cur_idx += 1
node = Node(value)
node.next = cur.next
cur.next = node
if node.next is None:
self.tail = node
def remove(self, idx):
'删除元素'
cur = self.head
cur_idx = 0
if self.head is None: # 空链表时
raise Exception('The linked_list is empty')
while cur_idx < idx - 1:
cur = cur.next
if cur is None:
raise Exception('list length less than index')
cur_idx += 1
if idx == 0: # 当删除第一个节点时
self.head = cur.next
cur = cur.next
return
if self.head is self.tail: # 当只有一个节点的链表时
self.head = None
self.tail = None
return
cur.next = cur.next.next
if cur.next is None: # 当删除的节点是链表最后一个节点时
self.tail = cur
def size(self):
'统计元素个数'
cur = self.head
count = 0
if cur is None:
raise Exception('The linked_list is empty')
while cur is not None:
count += 1
cur = cur.next
return count
def search(self, item):
'查找指定元素'
cur = self.head
found = False
while cur is not None and not found:
if cur.data == item:
found = True
else:
cur = cur.next
return found
if __name__ == '__main__':
link_list = LinkedList() # 实例化
print(link_list.is_empty()) # 判断是否为空
for i in range(15):
link_list.append(i) # 追加15个元素
print(link_list.size()) # 元素个数
print(link_list.is_empty()) # 判断是否为空
link_list.insert(5, 1000) # 插入一个元素:1000
print(link_list.size()) # 元素个数
link_list.remove(0) # 删除第一个元素
print(link_list.size()) # 元素个数
print(link_list.search(10)) # 查找是否存在元素10
for node in link_list.iter():
print(f'node is {node}') # 遍历元素
True
15
False
16
15
True
node is 1
node is 2
node is 3
node is 4
node is 1000
node is 5
node is 6
node is 7
node is 8
node is 9
node is 10
node is 11
node is 12
node is 13
node is 14