浅探Python链表构造方式
1 链表是什么
最基础的单向链表是一个像一条珠链一样的线性结构,由多个结点(珠)组成;而每个节点有数据及一个指向下一节点的指针(链)。因此,单向链表具有三种特殊的节点:头节点、尾节点及虚拟节点。
1.1 头节点
由于单向链表是由一个个节点组成,而每个节点仅指向下一个节点,无法追溯上一个节点,因此我们只能从头节点开始,逐个读取节点并获得下一个节点。由此可见,给出头节点,就能够得到整个链表。
1.2 尾节点
尾节点是链表的最后一个节点,因此可以作为链表结束的标志。由于其是最后一个节点,没有指向别的任何节点,因此其指针置空。我们也可以根据这一标志判断链表是否遍历完全。
1.3 虚拟节点
虚拟节点是一个指向头节点的节点、不存放任何数据的节点(通常将其数据初始化为0/-1/None),它最大的意义就是作为链表的“0”号位,指向头节点。当我们需要获取头节点,直接通过虚拟节点获取即可。使用虚拟节点一方面便于我们获取头节点,另一方面可以避免一些索引难处理的问题。尤其是在删除节点的相关问题上,我们通常需要在待删除节点的前一个节点处停下,此时引入虚拟节点就相当于提前一个节点。
2 构建一个链表
2.1 构造链表的节点类
单向链表的节点只涉及两个东西,一是该节点的数据,二是指向下一节点的指针。因此我们使用面向对象的方法,为其构造一个节点类:
class ListNode:
def __init__(self,data):
self.value = data
self.next = None
当我们使用node = ListNode(xxx)
创建了一个节点类的实例后,可以使用node.value
及node.next
访问其数据和下一节点。
2.2 连接两个节点
以上文定义的节点类为基础,先实例化两个节点类:
node1 = ListNode(1)
node2 = ListNode(2)
使node1节点指向node2节点:
node1.next = node2
事实上,我们已经完成了一个简单的链表的构建。这个链表以node1节点为头节点,以node2节点为尾节点。
3 链表的修改
链表的每个节点都需要存储一份数据和一个指针,它的优势在于对其增、删非常方便,劣势就是占用更多空间。
3.1 插入元素
向链表中插入元素,实际上就是让一个节点成为链表的一部分,也就是处理这个节点与链表原有节点的“指与被指”问题。
3.1.1 链表头部
当我们向链表头部插入元素时,我们只需要先构建一个新节点,让它指向原本的头部元素即可,此时头部元素就变成了新节点:
newnode = ListNode(data)
newnode.next = firstnode
3.1.2 链表中部
当我们向链表中部插入元素时,我们需要构建一个新节点,然后将插入处的“链”断开,使前一个节点指向新节点,新节点指向后一个节点,即可完成链表中部的数据插入:
newnode = ListNode(data)
# 首先使新节点指向原节点所指向的节点
newnode.next = listnode.next
# 再使原节点指向新节点
listnode.next = newnode
3.1.3 链表尾部
当我们向链表尾部插入元素,只需要构建一个新节点,然后让尾部节点指向它即可:
newnode = ListNode(data)
endnode.next = newnode
3.2 删除元素
删除元素的操作与插入元素正好相反,实际上就是使被删除节点的前一个节点从指向被删除节点变为指向被删除节点所指向的节点,也即“绕过”被删除节点,从而实现删除功能:
lastnode.next = last.next.next
4 链表常用算法:
遍历取链表长度:
p, cnt = head, 0
while p:
p, cnt = p.next, cnt + 1
print(cnt)
遍历取得尾节点(用于延长链表等):
vhead = ListNode(-1)
vhead.next = head
p = vhead
while p.next:
p = p.next
# 此时的p即为末节点