前言:本篇仅代表小菜鸟的个人观点,如有错误,还烦请大家指出。
单向链表
链表作为一种简单的线性结构,分为数据域与指针域,顾名思义,链表在逻辑结构上像一条铁链,一个结点连着下一个,仅靠着指针域指向下一个结点,因此链表结点的构建也就十分简单了。
如图所示,最后一个链表的指针域指向None,单向链表十分简单,现在就开始构建吧。
class Node: # 链表结点
def __init__(self, item): # 传入数据域的数据
self.item = item # 数据域
self.next = None # 指针域,指向下一个结点
结点的构建就如上图,单链表的操作也十分简单,插入,删除,定位,遍历等操作。
但不同于栈和队列存在入元素和出元素的固定顺序,链表可随意位置的删除和增加元素,因此插入和删除就分为了头插尾插和头删尾删。
注意以下所有的操作,一旦改变了头结点就需要返回新的头结点。
功能一:尾插元素
def back_push(head, element: int): # head为头节点,element为待插入元素
new_node = Node(element) # 构建新结点
if not head: # 头节点为空
head = new_node
else: # 头节点不为空
curNode = head
while curNode.next:
curNode = curNode.next
curNode.next = new_node
return head # 返回新的头结点
我们定义链表的第一个结点为头节点,尾插只需要找到原链表的最后一个结点在最后一个结点上链接新节点即可,但有一种情况需要注意,即链表为空,这时令新节点为头节点即可。
功能二:头插元素
def front_push(head, element):
new_node = Node(element)
if not head: # head为空
head = new_node
else:
new_node.next = head
head = new_node
return head # 返回新的头结点
头插元素会改变头结点,因此需要返回新的头结点,这也需要分为两种情况,一是链表本身就为空,直接令新节点为头结点即可,第二种就是正常的头插即令新结点的next为原来的头结点,再令newnode为新的头结点即可,最后返回新的头结点(一旦头结点可能发生变化,就需要返回新的头结点)。
功能三:尾删元素
遇到删除部分,就需要考虑链表内是否有元素可以删除,若链表本身就为空了,那就无法删除了。
def back_pop(head):
if head: # 链表不为空,可以删除
curNode = head
while curNode.next and curNode.next.next != None:
curNode = curNode.next # 找到尾巴前的一个结点
curNode.next = None # 将尾结点删除
else: # 链表为空
raise IndexError("link list is empty")
return head # 返回新的头结点
总体的死路就是找到尾巴前的一个结点,将尾巴结点置空即可,此外我们还需要注意一个特殊情况,即只有一个结点,因此我们需要判断curNode.next是否为None,否则取None的next就会报错。
功能四:头删元素
这个删除比较麻烦,需要分为二种情况,一是空链表删除,二是正常情况。
def front_pop(head):
if head: # 不为空
temp = head.next # 保存头结点的下一个结点
head.next = None
head = temp # 更新头结点
else: # 为空
raise IndexError("link list is empty")
return head # 返回新的头结点
功能五:查找功能(返回结点)
def find_node(head, value): # value是待查找的结点数据域的数据
curNode = head
if head:
while curNode.item != value:
curNode = curNode.next
return curNode # 找到了返回结点
else:
return None # 如果找不到就返回空
查找功能即根据需要的数据域的值来确定结点,返回该节点,从而配合后面的指定删除和插入功能,如果找不到的话,就返回None。
功能六:指定位置插入
def insert_(head,insert_pos,element):
# insert_pos是待插入的位置,element是插入结点的值
if insert_pos is head: # 头插
head = front_push(head,element)
else: # 非头插
curNode = head.next
prev = head
while curNode is not insert_pos:
curNode = curNode.next
prev = prev.next # 找到插入结点的前一个结点
new_node = Node(element)
prev.next = new_node # 插入
new_node.next = curNode # 链接
return head # 返回新的头结点
同样分为两种情况,即头插和非头插,为什么要这么分类呢,如果是非头插的话,头结点并不会改变,但一但是头插,就需要返回新的头结点。
总体思路就是定义一个前指针跟着找插入的位置,找到了,令前指针的next为新节点,再将新结点和curNode链接即可,因为头结点可能发生变化,所以需要返沪新的头结点。
功能七:指定位置删除
def pop_(head,pop_pos): # pop_pos为待删除的结点
if pop_pos is head: # 头删
head = front_pop(head)
else: # 非头删
curNode = head.next
prev = head # 前指针
while curNode is not pop_pos:
curNode = curNode.next
prev = prev.next
prev.next = curNode.next # 前指针链接原指针的后指针
curNode.next = None
return head # 返回新的头结点
依旧是分为两种情况,头删和非头删,此处空链表删除不需要讨论,若为空代码也可以跑通,当然读者也可以细分类将空链表单独处理。
功能八:遍历打印链表内容
def print_link_list(head):
if head: # head不为空
curNode = head
while curNode is not None:
print(curNode.item, end='->')
curNode = curNode.next
print(None,end='')
print()
else: # 为空,打印None即可
print(None)
这个很简单,无需多言。
功能九:依据给定列表构建单向链表
def build_list(li:list): # li是传入的列表,依据此构建链表
i = 1
head = Node(li[0])
curNode = head
while i<= len(li)-1:
curNode.next = Node(li[i])
curNode = curNode.next
i += 1
return head # 返回构建好的头结点
双向链表
python库内并没有内置链表,而双向列表顾名思义,就是一个结点链接前一个结点和后一个结点,即有两个指针域,一个为prev,一个为next,如下图所示:
定义也十分简单,只需要在单链表的基础上加上前指针域,如下图代码:
class Doubly_Node:
def __init__(self,item):
self.item = item
self.prev = None
self.next = None
这个双向链表思路与单向链表相同,不过更便捷,相关操作大家可以去试一试!
测试部分:
head = build_list([1,2,3,4,5,6])
back_push(head,99)
back_push(head,99)
head = front_push(head,100)
head = front_push(head,100)
head = front_pop(head)
back_pop(head)
print_link_list(head)
head = insert_(head,find_node(head,2),101)
head = pop_(head,find_node(head,2))
print_link_list(head)
总结:至此链表的相关操作就结束了,要巩固知识还要多刷题,leetcode上有很多,大家可以去练习一下。