前言
- 有python基础
- 当然,要是学过数据结构自然是最好的
原理
图片来自某度,图中是几种常见的链表,接下来讨论最常用的单链表
-
链表有数据域和指针域(或者说是链接域),数据域item,链接域next。在C语言中,next是一个指针,用来指向下一个节点。同样的,在Python中,next存在的意义也是为了标记下一个节点。
-
链表是由一堆节点组合起来(或者说串起来)的,由于有next,因此链表的数据不需要存在一块连续的存储空间,可以分散存储,毕竟通过next可以找到。
实现
初始化
- 链表涉及的操作有点多,前面栈、队列该有的它基本上都有。
- 初始化:由于链表是由节点串起来的,节点又不是Python里面自带的数据类型,因此还是定义一个节点类,在它的构造函数里面加上item和next属性
class Node(object):
def __init__(self, item):
self.item = item # node的item值为传入的item
self.next = None # node初始化时next为空,即没有后续节点
- 有了节点之后就可以定义链表了,链表的属性写一个头结点即可,初始化的时候依旧是设置为空,建议head设置为私有属性
class Link(object):
def __init__(self):
self.__head = None
遍历
- 虽然和栈、队列这些数据结构一样有某些函数(判空、遍历等),但是写法上有些不同。判空函数在链表中是看头结点是不是为空,而在栈和队列中一般是看items的长度是不是为0或者items这个list是不是为空。
def isEmpty(self):
return self.__head is None
- 在求size的时候,队列是在数元素个数,链表是在数节点的个数(元素个数也对,因为传入的元素item后面生成节点,item的数目等于节点数)
def size(self):
count = 0
cur = self.__head # 当头结点为空的时候,不进行下面的while循环,直接返回0
while cur:
count += 1
# 计数完之后,cur更新为后续的节点,当cur为空的时候就会退出循环
cur = cur.next
return count
- 遍历:从头结点开始,当头结点为空时输出为空,不为空时将节点的item依次打印
def travel(self):
cur = self.__head
while cur:
print(cur.item, end=' ')
cur = cur.next
print('')
插入
- 栈的插入方式是对栈顶元素操作,而队列是队尾插入,双向队列则是队头队尾都可以。(数据结构中的队列不考虑中间插队的情况)而在链表中,可以头部加入,也可以尾部加入,甚至可以中间插入。
- 头部插入:要插入什么元素?因此需要有一个参数接收想插入的元素。接收到参数后,生成一个节点,这个节点生成的时候调用class Node的构造函数,item为传入的元素,next为None,即后续节点为空。要使得这个节点插入到链表头,即使得这个新的节点成为新的头头,就应该使得这个节点的next指向当前的头结点,之后再把头结点更新即可。
- 灵魂画手重出江湖
# 头部插入
def addFront(self, item):
node = Node(item)
node.next = self.__head
self.__head = node
- 尾部插入:尾部插入的时候需要注意的一点是怎样找到尾部
# 尾部插入
def addBack(self, item):
node = Node(item)
if self.__head is None:
self.__head = node
return
cur = self.__head
pre = None
while cur:
pre = cur
cur = cur.next
# 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
# 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
pre.next = node
- 任意位置的插入:需要1个代表位置的参数,和一个item。插入节点, 节点在插入后是第 pos 个节点,当然这个函数也可以实现头部插入和尾部插入的功能。故pos取值1时,即头部插入,取值size+1时是尾部插入。因此取值的合法范围是[1,size + 1]。
- 找到插入位置的前一个节点,这个节点假设为a,a.next 为b,要插入的节点为node。那么,需要将a.next = node ; node.next = b这样即可实现插入。
# 插入(这一块代码自己觉得还是可以改进)
def insert(self, pos, item):
if pos > (self.size() + 1) or pos < 1:
return
if pos == 1:
self.addFront(item)
return
node = Node(item)
cur = self.__head
pre = None
for i in range(pos - 1):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
删除
- 头部删除:将原来的头结点的下一个节点设置为头结点,将原来头结点的next设置为None
# 删除头部节点
def removeFront(self):
cur = self.__head
self.__head = self.__head.next
cur.next = None
- 删除尾部节点:空的时候不用管,只有一个的时候将头结点置于None即可,其余情况如图
# 删除尾部节点
def removeBack(self):
# 空节点时
if self.__head is None:
return
# 只有一个节点
if self.__head and self.__head.next is None:
self.__head = None
return
# 链表节点有两个及以上
cur = self.__head # 当前节点
pre = None # 前一个节点
cn = cur.next # 后一个节点
# 刚开始cur取到的是第一个节点,cn是第二个
while cn:
# 将cur 取值为倒数第二个节点即cn 为最后一个节点带入
pre = cur # (接上面注释)更新为倒数第二个
cur = cur.next # (接上面注释)更新为最后一个
cn = cur.next # (接上面注释)更新为None,下一次就不会进行while循环
pre.next = None # 将倒数第二个节点的next 设置为None就连不上原来的最后一个了
- 查找链表中有没有item,有返回True,没有则返回False
# 查找链表中有没有item,有返回True,没有则返回False
def search(self, item):
cur = self.__head
res = False
while cur:
if cur.item == item:
res = True
break
else:
cur = cur.next
return res
- 删除指定数值的节点:当找到指定数值的节点cur时,将pre节点的next指向cur.next即可
# 删除指定数值的节点
def delete(self, item):
if self.__head is None:
return
if self.__head.item == item:
self.__head = None
return
cur = self.__head.next # 取第二个节点
pre = self.__head # 第一个节点
while cur:
if cur.item == item:
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
完整代码
- 大致方法有这些,文中如若有写错的地方还望批评指出。有些地方或许有更好的实现方式,可以多试试。
- 完整代码
class Node(object):
def __init__(self, item):
self.item = item
self.next = None
class Link(object):
def __init__(self):
self.__head = None
def isEmpty(self):
return self.__head is None
# 头部插入
def addFront(self, item):
node = Node(item)
node.next = self.__head
self.__head = node
# 尾部插入
def addBack(self, item):
node = Node(item)
if self.__head is None:
self.__head = node
return
cur = self.__head
pre = None
while cur:
pre = cur
cur = cur.next
# 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
# 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
pre.next = node
def size(self):
count = 0
cur = self.__head
while cur:
count += 1
cur = cur.next
return count
def travel(self):
cur = self.__head
while cur:
print(cur.item, end=' ')
cur = cur.next
print('')
# 删除头部节点
def removeFront(self):
cur = self.__head
self.__head = self.__head.next
cur.next = None
# 删除尾部节点
def removeBack(self):
# 空节点时
if self.__head is None:
return
# 只有一个节点
if self.__head and self.__head.next is None:
self.__head = None
return
# 链表节点有两个及以上
cur = self.__head # 当前节点
pre = None # 前一个节点
cn = cur.next # 后一个节点
# 刚开始cur取到的是第一个节点,cn是第二个
while cn:
pre = cur
cur = cur.next
cn = cur.next
pre.next = None
# 查找链表中有没有item,有返回True,没有则返回False
def search(self, item):
cur = self.__head
res = False
while cur:
if cur.item == item:
res = True
break
else:
cur = cur.next
return res
# 删除指定数值的节点
def delete(self, item):
if self.__head is None:
return
if self.__head.item == item:
self.__head = None
return
cur = self.__head.next # 取第二个节点
pre = self.__head # 第一个节点
while cur:
if cur.item == item:
pre.next = cur.next
break
else:
pre = cur
cur = cur.next
# 插入节点, 节点在插入后是第 pos 个节点,当然这个函数也可以实现头部插入和尾部插入的功能
# 故pos取值1时,即头部插入,取值size+1时是尾部插入。因此取值的合法范围是[1,size + 1]
def insert(self, pos, item):
if pos > (self.size() + 1) or pos < 1:
return
if pos == 1:
self.addFront(item)
return
node = Node(item)
cur = self.__head
pre = None
for i in range(pos - 1):
pre = cur
cur = cur.next
pre.next = node
node.next = cur
ll = Link()
print(ll.isEmpty()) # 刚开始是空的,应为True
for i in range(5):
ll.addFront(i)
print('size:', ll.size()) # 加入了5个,应为5
ll.travel() # 打印输出,由于是头部添加,因此打印的应该是43210(忽略空格)
for i in range(5):
ll.addBack(i)
ll.travel() # 尾部添加,打印的应是一个对称的序列,4321001234
ll.removeFront() # 删除头部元素,下面打印应是321001234
ll.travel()
print('----')
ll.removeBack()
ll.travel() # 删除尾部元素,下面打印应是32100123
ll.insert(9, 12) # 插入后,下面打印出来的第九个是12
ll.travel()
print(ll.search(13)) # 没有,应该是False
print(ll.search(1)) # 有的,应是True
ll.delete(2) # 删除找到的第一个2
ll.travel()
- 结果