今天我们要研究的单链表的逻辑结构就和他们如出一辙
是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
链表按照其结构可以分为
链表中最简单的一种是单向链表,它包含两个域,一个信息域(data field)和一个指针域(‘next’ field)。指针域指向链表中的下一个节点。最后一个节点则指向一个空值(None)。可以在单链表上执行的操作包括插入,删除和遍历。
从引用单向链表的首节点的变量可以找到这个表的首节点(图中的data field == 12),从表中任一结点可以找到保存着该表下一个元素的结点(表中下一结点)。这样,从首节点出发就能找到这个表里的任一个结点。结点之间通过结点链接建立起单向的顺序联系。
我们把保存着首节点链接(引用或标识)的变量成为表头指针或表头变量。
如果一个表头指针的值是空链接,就说明‘它所引用的链接已经结束’,这是没有元素就已结束,说明该表为空表。
我们照例还是用代码实现一遍方便理解
链是由结点构成的, 为了方便操作, 我们先定义一个简单的表结点类:
class LNode(object): # 定义一个节点类
def __init__(self, value, next_n=None):
self.value = value
self.next = next_n
前面已经讲过了, 一个结点包含两个域, data和next.
下面考虑单链表的操作,这里主要关注加入元素和删除元素:
有顺序的东西, 当你想插入的时候, 自然要考虑插入的位置, 可以是首端插入、尾端插入或者定位插入。注意, 在插入时, 我们并不需要移动已有的数据, 只需要给新元素一个结点, 改变next的值, 连在正确的位置即可。
第一步, 创建我们要插入的结点, 并输入数据;
第二步, 将新结点的next域更新为原来旧表头的链接, 这一操作将原表的一串结点链接在刚创建的新结点之后;
第三部, 将新结点更新为表头指针。
new_node = LNode(new_value)
new_node.next = head
head = new_node
一般位置插入就要更新位置前后两个结点的next值, 前值指向新结点, 新结点指向后值
new_node = LNode(new_value)
new_node.next = pre.next #前结点原本保存的是后结点的链接,所以直接赋给新结点
pre.next = new_node #新结点的链接赋值给前结点的next
如果是尾部就不需要更新新结点的next, 默认为None即可
插入的逻辑明白之后, 删除自然也就明白了, 就是换next呗
删除首元素
直接将表头后一个元素的链接更新为表头即可, Python解释器会自动回收丢弃的结点
head = head.next
删除其他位置
将要删除的结点的前一个结点的next域修改为要删除的结点的后一个结点的链接即可
pre.next = pre.next.next
#如果pre是0号结点,那么pre.next对应的是1号结点,pre.next.next就是2号
Python解释器同样会自动回收丢弃的结点
总结一下链表操作的复杂度:
1)创建空表:O(1)
2)删除表:在python中是O(1)。当然,Python解释器做存储管理也需要时间
3)判断空表:O(1)
4)加入元素:
首端加入元素:O(1)
尾端加入元素:O(n),因为需要找到表的最后结点
定位加入元素:O(n),平均情况和最坏情况
5)删除元素:
首端删除元素:O(1)
尾端删除元素:O(n)
定位删除元素:O(n),平均情况和最坏情况\
6)扫描、定位和遍历操作都需要检查一批表结点,其复杂度受到表结点数的约束,都是O(n)操作。
转载一个单链表的实现程序, 将上边的思路完整的捋顺一下
作者:Vico_Men 来源:CSDN
原文:https://blog.csdn.net/qq_28031525/article/details/53583857
class LNode(object): # 定义一个节点类
def __init__(self, elem, next_=None):
self.elem = elem
self.next = next_
class LinkListUnderflow(ValueError): # 自定义一个异常
pass
class Llist(object): # 定义一个链表
def __init__(self):
self._head = None
def is_empty(self): # 判断链表是否为空,通过self._head来判断
return self._head is None
def prepend(self, elem): # 在链表的头部加上元素
self._head = LNode(elem, self._head)
def pop(self): # 删除表头节点并返回其中的值
if self._head is None: # 无结点
raise LinkListUnderflow('in pop')
e = self._head.elem
self._head = self._head.next
print e
def append(self, elem):
if self._head is None: # 在链表的结尾处添加元素
self._head = LNode(elem)
return
p = self._head
while p.next is not None:
p = p.next
p.next = LNode(elem)
def pop_last(self): # 删除链表最后的一个元素
if self._head is None:
raise LinkListUnderflow('in pop_last')
p = self._head
if p.next is None:
e = p.elem
self._head = None
return e
while p.next.next is not None: # 找到链表的倒数第二个节点
p = p.next
e = p.next.elem
p.next = None
return e
def printall(self): # 自定义打印出来的结果
p = self._head
while p is not None:
print p.elem
p = p.next
def rev(self): # 列表翻转的操作
p = None
while self._head is not None:
q = self._head
self._head = q.next # 摘下原来的首节点
q.next = p # 将摘下来的节点放到p引用的节点序列中
p = q
self._head = p # 反转的结点序列做好后,重置表头链接
def sort1(self): # 通过移动表中的元素进行排序
if self._head is None:
return
cart = self._head.next # 从首节点之后的结点开始处理
while cart is not None:
p = self._head
x = cart.elem
while p is not cart and p.elem <= x: # 跳过小的元素
p = p.next
while p is not cart: # 置换大的元素和现在的值
y = p.elem
p.elem = x
x = y
p = p.next
cart.elem = x # 回填最后的一个元素
cart = cart.next
#142 Linked List Cycle II
https://leetcode.com/problems/linked-list-cycle-ii/
拿到题以后, 就没明白传入的变量是什么, 让人一头雾水
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
看到example里的input, 本以为iuput是一个列表!?? 但是!!! 函数传入的类型是ListNode!!! 我一下就没明白这是怎样的一个对应关系, 本以为需要自己拿列表数据做链, 直到我验证了head的内容。
print(head.val) #得到的结果竟然是3
***再测试一下next***
print(head.next.val) #得到的结果是2
这也就是说明, 提供给我们的是串好的链!!! 直接干就完了
昨天我们做了#202 Happy Number, 这和今天这题不就是一回事儿吗
昨天我们把平方和后得到的结果放在字典里, 如果 in dict 为 True 就return False
我们把结点放在字典里, 如果 in dict 为 True, 那这个交叉点不就找到了么, 是不是一回事儿
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
dict = {}
while head and head.next:
if head in dict:
return head
else:
dict[head] = 0
head = head.next
return None
判断单链表中是否有环,找到环的入口节点, 我们可以使用双指针追逐法: 快指针每次移动2, 慢指针每次移动1
如图所示,链表头是X,环的第一个节点是Y,slow和fast第一次的交点是Z。各段的长度分别是a,b,c,如图所示。环的长度是L。
fast先走, 与slow在Z会和是已经走了 a+b+nL (#可能已经循环了n圈)
slow走了 a + b, 因为fast的速度是slow的二倍所以 fast走了 2a+2b
我们可以得到等式 a+b+nL = 2a+2b
所以a = nL -b = c
所以, 当快慢指针相遇时, 再有一指针从表头走, 因为路径长度相同, 所以再次会和时, 所在结点就是交点
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
slow = head
while fast != slow:
fast = fast.next
slow = slow.next
return fast
return None
#206 Reverse Linked List
https://leetcode.com/problems/reverse-linked-list/
反转一个单链表, 迭代法和递归法都行
递归我们会在后面讨论
迭代解一下:
class Solution:
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if head is None:
return None
this = None
pre = None
while head:
this = head.next
head.next = pre
pre = head
head = this
return pre
今天的学习到这里就要结束了
感谢那些为我们提供资料的大佬们:
下次见