单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
1、构造一个单链表
class Node(object):
"""节点类"""
__slots__ = ['_item', '_next'] # 限定 Node 实例的属性
def __init__(self, item):
self._item = item # Node 的值
self._next = None # Node 的默认指向 None
def getItem(self): # 得到 Node 的值
return self._item
def getNext(self): # 得到下一个 Node
return self._next
def setItem(self, newitem): # 设置 Node 的值
self._item = newitem
def setNext(self, newnext): # 设置下一个 Node
self._next = newnext
class SingleLinkedList(object):
"""单链表类"""
def __init__(self):
self._head = None # 初始化链表为空 始终指向链表的头部
self._size = 0 # 链表长度
def size(self):
"""返回链表的长度"""
current = self._head
count = 0
while current is not None:
count += 1
current = current.getNext()
return count
def travel(self):
"""遍历链表(不能有环)"""
current = self._head
while current is not None:
print(current.getItem(), end='==>')
current = current.getNext()
def isEmpty(self):
"""检查链表是否为空"""
return self._head is None
def add(self, item):
"""在链表头添加元素"""
temp = Node(item) # 创建新的 Node
temp.setNext(self._head) # 新 Node 的 next 指向 _head
self._head = temp # _head 指向新创建的 Node
def append(self, item):
"""在链表尾添加元素"""
temp = Node(item)
if self.isEmpty():
self._head = temp # 若为空表就直接插入
else:
current = self._head
while current.getNext() is not None:
current = current.getNext() # 遍历列表,得到尾 Node
current.setNext(temp) # 此时current为链表最后的元素,在末尾插入
def search(self, item):
"""检索元素是否在链表中"""
current = self._head
founditem = False
while current is not None and not founditem:
if current.getItem() == item:
founditem = True
else:
current = current.getNext()
return founditem
def index(self, item):
"""索引元素在链表中的位置"""
current = self._head
count = 0
found = None
while current is not None and not found:
count += 1
if current.getItem() == item:
found = True
else:
current = current.getNext()
if found:
return count
else:
return -1 # 返回-1表示不存在
def remove(self, item):
"""删除链表中的某节点"""
current = self._head
pre = None
while current is not None:
if current.getItem() == item:
if not pre:
self._head = current.getNext()
else:
pre.setNext(current.getNext())
break
else:
pre = current
current = current.getNext()
def insert(self, pos, item):
"""在链表任意位置插入元素"""
if pos <= 1:
self.add(item)
elif pos > self.size():
self.append(item)
else:
temp = Node(item)
count = 1
pre = None
current = self._head
while count < pos:
count += 1
pre = current
current = current.getNext()
pre.setNext(temp)
temp.setNext(current)
if __name__ == '__main__':
sla = SingleLinkedList()
for i in range(1, 10):
sla.append(i)
print('链表的大小', sla.size())
print(sla.travel())
print(sla.search(6))
print(sla.index(5))
sla.remove(4)
print(sla.travel())
sla.insert(4, 100)
print(sla.travel())
2、在链表中添加环
class SingleLinkedLoop(SingleLinkedList):
"""继承 SingleLinkedList 类"""
def setCycle(self, pos):
"""将链表的第 pos 个节点设为环的入口"""
current = self._head
pre = self._head
for i in range(1, self.size()):
pre = pre.getNext() # 得到尾 Node
if pos < 1 or pos > self.size():
return -1
else:
for i in range(1, pos):
current = current.getNext() # 得到环入口 Node
pre.setNext(current) # 设置环
if __name__ == '__main__':
slb = SingleLinkedLoop()
for i in range(1, 10):
slb.append(i)
print(slb.travel())
slb.setCycle(4)
3、判断链表中是否有环
如何判断一个单链表是否存在循环,链表数目未知。算法不能破坏链表。有三种常用的解决方法。
(1)存储节点
将所有遍历过的节点用某个结构存储起来,然后每遍历一个节点,都在这个结构中查找是否遍历过,如果找到有重复,则说明该链表存在循环;如果直到遍历结束,则说明链表不存在循环。
这个结构我们可以使用hash来做,hash中存储的值为节点的内存地址。
def hasCycle_hash(singlelinked):
"""接收一个链表,判断链表中是否有环
用 set 来存储链表的节点id"""
hash_set = set() # 初始化set
current = singlelinked._head # 得到链表头节点
while current is not None:
if id(current) in hash_set: # 某个节点已经在 hash_set 中,则链表中存在环
return True
else:
hash_set.add(id(current))
current = current.getNext()
return False # 链表中没有环
if __name__ == '__main__':
print(hasCycle_hash(sla))
print(hasCycle_hash(slb))
(2)逆转链表
方法比较的特别,是使用反转指针的方法,每过一个节点就把该节点的指针反向。
当有环的时候,最后指针会定位到链表的头部,如果到最后,没有回到头部,那说明链表不存在循环。
这个方法会破坏掉链表,所以如果要求是不能破坏链表的话,最后还需要反转一下,再将链表恢复。
这个方法其实是使用了3个指针,用于进行反转。
def hasCycle_reverse(singlelinked):
"""接收一个链表,判断链表中是否有环
用一个节点来反转链表"""
dummy = Node(-1) # 定义一个头节点,用来插入摘下的节点
current = singlelinked._head
if current is None:
return False
dummy.setNext(current)
pre, cur = current, current.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='-->')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 反转链表
dummy.setNext(temp)
if dummy.getNext() == current:
pre = dummy.getNext()
cur = pre.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='>>>')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 恢复有环链表
dummy.setNext(temp)
if dummy.getNext() == current:
return True
pre = dummy.getNext()
cur = pre.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='++>')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 恢复无环链表
dummy.setNext(temp)
return False
if __name__ == '__main__':
print(hasCycle_reverse(sla))
print(sla.travel())
print(hasCycle_reverse(slb))
current = slb._head
print(current.getItem())
for i in range(10):
current = current.getNext()
print(current.getItem(), end='==>')
(3)快慢指针
快指针pf(f就是fast的缩写)每次移动2个节点,慢指针ps(s为slow的缩写)每次移动1个节点,如果快指针能够追上慢指针,那就说明其中有一个环,否则不存在环。
def hasCycle_f_s(singlelinked):
"""接收一个链表,判断链表中是否有环
用两个指针来遍历链表"""
current = singlelinked._head
if current is None:
return False
if current.getNext() is None:
return False
slow = current.getNext()
fast = current.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
while (slow.getNext() is not None) and (fast.getNext() is not None):
if slow == fast:
return True
slow = slow.getNext()
fast = fast.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
return False
if __name__ == '__main__':
print(hasCycle_f_s(sla))
print(hasCycle_f_s(slb))
4、找出环的入口点
(1)与存储节点进行对比
def hasCycle_hash(singlelinked):
"""接收一个链表,判断链表中是否有环
用 set 来存储链表的节点id,当某节点 id 在 set 中时,就是环的入口"""
hash_set = set()
current = singlelinked._head
while current is not None:
if id(current) in hash_set:
return current # 返回环的入口节点
else:
hash_set.add(id(current))
current = current.getNext()
return False
if __name__ == '__main__':
in_cycle = hasCycle_hash(slb) # 接收环入口节点
print(in_cycle.getItem()) # 入口节点的 值
print(slb.index(in_cycle.getItem())) # 入口节点的 位置
(2)重置快慢指针
def hasCycle_f_s(singlelinked):
"""接收一个链表,判断链表中是否有环
用两个指针来遍历链表"""
current = singlelinked._head
if current is None:
return False
if current.getNext() is None:
return False
slow = current.getNext()
fast = current.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
while (slow.getNext() is not None) and (fast.getNext() is not None):
if slow == fast:
before = slow # 相遇节点
after = current # 头节点
while True:
if before == after:
return after # 返回环的入口节点
before = before.getNext()
after = after.getNext()
slow = slow.getNext()
fast = fast.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
return False
if __name__ == '__main__':
in_cycle = hasCycle_f_s(slb) # 接收环入口节点
print(in_cycle.getItem()) # 入口节点的 值
print(slb.index(in_cycle.getItem())) # 入口节点的 位置
5、求出环的长度,链表的长度
(1)用存储节点法,跑一圈环,计算长度
def hasCycle_hash(singlelinked):
"""接收一个链表,判断链表中是否有环
用 set 来存储链表的节点id"""
hash_set = set()
current = singlelinked._head
while current is not None:
if id(current) in hash_set:
len_l = len(hash_set) # 链表的长度
temp = current.getNext()
len_c = 1
while True:
if temp == current:
return current, len_c, len_l # 返回环的入口节点, 环的长度,链表的长度
temp = temp.getNext()
len_c += 1
else:
hash_set.add(id(current))
current = current.getNext()
return False
if __name__ == '__main__':
in_cycle, len_c, len_l = hasCycle_hash(slb)
print("链表的长度是:", len_l)
print("环的长度是:", len_c)
print(in_cycle.getItem()) # 入口节点的 值
print(slb.index(in_cycle.getItem())) # 入口节点的 位置
(2)快慢指针法,同时算长度
def hasCycle_f_s(singlelinked):
"""接收一个链表,判断链表中是否有环
用两个指针来遍历链表"""
current = singlelinked._head
if current is None:
return False
if current.getNext() is None:
return False
slow = current.getNext()
fast = current.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
while (slow.getNext() is not None) and (fast.getNext() is not None):
if slow == fast:
before = slow # 相遇节点
after = current # 头节点
len_a = 0 # 头节点到入口节点的长度
while True:
if before == after:
temp = after.getNext()
len_c = 1
while True:
if temp == after:
len_l = len_c + len_a
return after, len_c, len_l # 返回环的入口节点, 环的长度, 链表的长度
temp = temp.getNext()
len_c += 1
before = before.getNext()
after = after.getNext()
len_a += 1
slow = slow.getNext()
fast = fast.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
return False
if __name__ == '__main__':
in_cycle, len_c, len_l = hasCycle_f_s(slb)
print("链表的长度是:", len_l)
print("环的长度是:", len_c)
print(in_cycle.getItem()) # 入口节点的 值
print(slb.index(in_cycle.getItem())) # 入口节点的 位置
6、求环上任意一个节点的对面节点
- 使用快慢指针的方法,让slow和fast都指向环上的节点ptr0,slow每次跳一步,fast每次跳两步,当fast = ptr0 或者 fast = prt0->next的时候slow所指向的节点就是ptr0的”对面节点“。
- 得到环的长度 len_c,对环上的节点ptr0,向前走(len_c//2) 个节点,或者向前走(len_c//2)+1 个节点。所指向的节点就是ptr0的”对面节点“。
7、(扩展)如何判断两个无环链表是否相交,如果相交,求出第一个相交的节点
- 让其中一个链表的尾节点指向头节点,形成闭环。测试另一个链表中是否有环。[转化为 问题3 ]
- 第一个相交的节点,就是环的入口节点。[转化为 问题4]
class Node(object):
"""节点类"""
__slots__ = ['_item', '_next'] # 限定Node实例的属性
def __init__(self, item):
self._item = item
self._next = None # Node的指针部分默认指向None
def getItem(self):
return self._item
def getNext(self):
return self._next
def setItem(self, newitem):
self._item = newitem
def setNext(self, newnext):
self._next = newnext
class SingleLinkedList(object):
"""单链表类"""
def __init__(self):
self._head = None # 初始化链表为空 始终指向链表的头部
self._size = 0 # 链表大小
# 返回链表的大小
def size(self):
current = self._head
count = 0
while current is not None:
count += 1
current = current.getNext()
return count
# 遍历链表
def travel(self):
current = self._head
while current is not None:
print(current.getItem(), end='==>')
current = current.getNext()
# 检查链表是否为空
def isEmpty(self):
return self._head is None
# 在链表前端添加元素
def add(self, item):
temp = Node(item) # 创建新的节点
temp.setNext(self._head) # 新创建的next指针指向_head
self._head = temp # _head指向新创建的指针
# 在链表尾部添加元素
def append(self, item):
temp = Node(item)
if self.isEmpty():
self._head = temp # 若为空表就直接插入
else:
current = self._head
while current.getNext() is not None:
current = current.getNext() # 遍历列表
current.setNext(temp) # 此时current为链表最后的元素,在末尾插入
# 检索元素是否在链表中
def search(self, item):
current = self._head
founditem = False
while current is not None and not founditem:
if current.getItem() == item:
founditem = True
else:
current = current.getNext()
return founditem
# 索引元素在链表中的位置
def index(self, item):
current = self._head
count = 0
found = None
while current is not None and not found:
count += 1
if current.getItem() == item:
found = True
else:
current = current.getNext()
if found:
return count
else:
return -1 # 返回-1表示不存在
# 删除链表中的某节点元素
def remove(self, item):
current = self._head
pre = None
while current is not None:
if current.getItem() == item:
if not pre:
self._head = current.getNext()
else:
pre.setNext(current.getNext())
break
else:
pre = current
current = current.getNext()
# 在链表任意位置插入元素
def insert(self, pos, item):
if pos <= 1:
self.add(item)
elif pos > self.size():
self.append(item)
else:
temp = Node(item)
count = 1
pre = None
current = self._head
while count < pos:
count += 1
pre = current
current = current.getNext()
pre.setNext(temp)
temp.setNext(current)
class SingleLinkedLoop(SingleLinkedList):
"""继承 SingleLinkedList 类"""
def setCycle(self, pos):
"""将链表的第 pos 个节点设为环的入口"""
current = self._head
pre = self._head
for i in range(1, self.size()):
pre = pre.getNext()
if pos < 1 or pos > self.size():
return -1
else:
for i in range(1, pos):
current = current.getNext()
pre.setNext(current)
def hasCycle_hash(singlelinked):
"""接收一个链表,判断链表中是否有环
用 set 来存储链表的节点id"""
hash_set = set()
current = singlelinked._head
while current is not None:
if id(current) in hash_set:
len_l = len(hash_set) # 链表的长度
temp = current.getNext()
len_c = 1
while True:
if temp == current:
return current, len_c, len_l # 返回环的入口节点, 环的长度,链表的长度
temp = temp.getNext()
len_c += 1
else:
hash_set.add(id(current))
current = current.getNext()
return False
def hasCycle_reverse(singlelinked):
"""接收一个链表,判断链表中是否有环
用一个节点来反转链表"""
dummy = Node(-1)
current = singlelinked._head
if current is None:
return False
dummy.setNext(current)
pre, cur = current, current.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='-->')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 反转链表
dummy.setNext(temp)
if dummy.getNext() == current:
pre = dummy.getNext()
cur = pre.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='>>>')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 恢复链表
dummy.setNext(temp)
if dummy.getNext() == current:
return True
pre = dummy.getNext()
cur = pre.getNext()
while cur is not None:
print(dummy.getNext().getItem(), end='++>')
temp = cur
pre.setNext(cur.getNext()) # 把摘链的地方连起来
cur = pre.getNext()
temp.setNext(dummy.getNext()) # 恢复链表
dummy.setNext(temp)
return False
def hasCycle_f_s(singlelinked):
"""接收一个链表,判断链表中是否有环
用两个指针来遍历链表"""
current = singlelinked._head
if current is None:
return False
if current.getNext() is None:
return False
slow = current.getNext()
fast = current.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
while (slow.getNext() is not None) and (fast.getNext() is not None):
if slow == fast:
before = slow # 相遇节点
after = current # 头节点
len_a = 0 # 头节点到入口节点的长度
while True:
if before == after:
temp = after.getNext()
len_c = 1
while True:
if temp == after:
len_l = len_c + len_a
return after, len_c, len_l # 返回环的入口节点, 环的长度, 链表的长度
temp = temp.getNext()
len_c += 1
before = before.getNext()
after = after.getNext()
len_a += 1
slow = slow.getNext()
fast = fast.getNext()
if fast.getNext() is not None:
fast = fast.getNext()
return False
if __name__ == '__main__':
sla = SingleLinkedList()
for i in range(1, 10):
sla.append(i)
print('链表的大小', sla.size())
print(sla.travel())
print(sla.search(6))
print(sla.index(5))
sla.remove(4)
print(sla.travel())
sla.insert(4, 100)
print(sla.travel())
slb = SingleLinkedLoop()
for i in range(1, 10):
slb.append(i)
print(slb.travel())
slb.setCycle(4)
print(hasCycle_hash(sla))
in_cycle, len_c, len_l = hasCycle_hash(slb)
print("链表的长度是:", len_l)
print("环的长度是:", len_c)
print(in_cycle.getItem())
print(slb.index(in_cycle.getItem()))
print(hasCycle_reverse(sla))
print(sla.travel())
print(hasCycle_reverse(slb))
current = slb._head
print(current.getItem())
for i in range(10):
current = current.getNext()
print(current.getItem(), end='==>')
print()
print(hasCycle_f_s(sla))
in_cycle, len_c, len_l = hasCycle_f_s(slb)
print("链表的长度是:", len_l)
print("环的长度是:", len_c)
print(in_cycle.getItem())
print(slb.index(in_cycle.getItem()))