Skip to content

Commit 4c9cc17

Browse files
committed
链表
1 parent a76cfa3 commit 4c9cc17

File tree

3 files changed

+338
-0
lines changed

3 files changed

+338
-0
lines changed

3_链表/double_link_list.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
class Node(object):
5+
6+
def __init__(self, value=None, prev=None, next=None):
7+
self.value, self.prev, self.next = value, prev, next
8+
9+
10+
class CircularDoubleLinkedList(object):
11+
"""循环双端链表 ADT
12+
多了个循环其实就是把 root 的 prev 指向 tail 节点,串起来
13+
"""
14+
15+
def __init__(self, maxsize=None):
16+
self.maxsize = maxsize
17+
node = Node()
18+
node.next, node.prev = node, node
19+
self.root = node
20+
self.length = 0
21+
22+
def __len__(self):
23+
return self.length
24+
25+
def headnode(self):
26+
return self.root.next
27+
28+
def tailnode(self):
29+
return self.root.prev
30+
31+
def append(self, value): # O(1), 你发现一般不用 for 循环的就是 O(1),有限个步骤
32+
if self.maxsize is not None and len(self) > self.maxsize:
33+
raise Exception('LinkedList is Full')
34+
node = Node(value=value)
35+
tailnode = self.tailnode() or self.root
36+
37+
tailnode.next = node
38+
node.prev = tailnode
39+
node.next = self.root
40+
self.root.prev = node
41+
self.length += 1
42+
43+
def appendleft(self, value):
44+
if self.maxsize is not None and len(self) > self.maxsize:
45+
raise Exception('LinkedList is Full')
46+
node = Node(value=value)
47+
if self.root.next is self.root: # empty
48+
node.next = self.root
49+
node.prev = self.root
50+
self.root.next = node
51+
self.root.prev = node
52+
else:
53+
node.prev = self.root
54+
headnode = self.root.next
55+
node.next = headnode
56+
headnode.prev = node
57+
self.root.next = node
58+
self.length += 1
59+
60+
def remove(self, node): # O(1),传入node 而不是 value 我们就能实现 O(1) 删除
61+
"""remove
62+
:param node # 在 lru_cache 里实际上根据key 保存了整个node:
63+
"""
64+
if node is self.root:
65+
return
66+
else: #
67+
node.prev.next = node.next
68+
node.next.prev = node.prev
69+
self.length -= 1
70+
return node
71+
72+
def iter_node(self):
73+
if self.root.next is self.root:
74+
return
75+
curnode = self.root.next
76+
while curnode.next is not self.root:
77+
yield curnode
78+
curnode = curnode.next
79+
yield curnode
80+
81+
def __iter__(self):
82+
for node in self.iter_node():
83+
yield node.value
84+
85+
def iter_node_reverse(self):
86+
"""相比单链表独有的反序遍历"""
87+
if self.root.prev is self.root:
88+
return
89+
curnode = self.root.prev
90+
while curnode.prev is not self.root:
91+
yield curnode
92+
curnode = curnode.prev
93+
yield curnode
94+
95+
96+
def test_double_link_list():
97+
dll = CircularDoubleLinkedList()
98+
assert len(dll) == 0
99+
100+
dll.append(0)
101+
dll.append(1)
102+
dll.append(2)
103+
104+
assert list(dll) == [0, 1, 2]
105+
106+
assert [node.value for node in dll.iter_node()] == [0, 1, 2]
107+
assert [node.value for node in dll.iter_node_reverse()] == [2, 1, 0]
108+
109+
headnode = dll.headnode()
110+
assert headnode.value == 0
111+
dll.remove(headnode)
112+
assert len(dll) == 2
113+
assert [node.value for node in dll.iter_node()] == [1, 2]
114+
115+
dll.appendleft(0)
116+
assert [node.value for node in dll.iter_node()] == [0, 1, 2]
117+
118+
119+
if __name__ == '__main__':
120+
test_double_link_list()

3_链表/linked_list.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# 链式结构
2+
3+
上一节讲到了支持随机访问的线性结构,这次我们开始讲链式结构。最常见的就是单链表和双链表。
4+
之前在专栏文章[那些年,我们一起跪过的算法题[视频]](https://zhuanlan.zhihu.com/p/35175401)里实现过一个 lru_cache,
5+
使用到的就是循环双端链表,如果感觉这篇文章有点难理解,我们这里将会循序渐进地来实现。
6+
后边讲到哈希表的冲突解决方式的时候,我们会再次使用链接表。
7+
8+
上一节我们分析了 list 的各种操作是如何实现的,如果你还有印象的话,list
9+
在头部进行插入是个相当耗时的操作(需要把后边的元素一个一个挪个位置)。假如你需要频繁在数组两头增删,list 就不太合适。
10+
今天我们介绍的链式结构将摆脱这个缺陷,当然了链式结构本身也有缺陷,比如你不能像数组一样随机根据下标访问。
11+
所以嘛,学习和了解数据结构的原理和实现你才能准确地选择到底什么时候该用什么数据结构,而不是瞎选导致代码性能很差。
12+
13+
14+
# 单链表
15+
和线性结构不同,链式结构内存不连续的,而是一个个串起来的,这个时候就需要每个链接表的节点保存一个指向下一个节点的指针。
16+
看到指针你也不用怕,这里我们用的 python,你只需要一个简单赋值操作就能实现,不用担心 c 语言里复杂的指针。
17+
18+
先来定义一个链接表的节点,刚才说到有一个指针保存下一个节点的位置,我们叫它 next, 当然还需要一个 value 属性保存值
19+
20+
```py
21+
class Node(object):
22+
def __init__(self, value, next=None):
23+
self.value = value
24+
self.next = next
25+
```
26+
然后就是我们的单链表 LinkedList ADT:
27+
28+
```py
29+
class LinkedList(object):
30+
""" 链接表 ADT
31+
[root] -> [node0] -> [node1] -> [node2]
32+
"""
33+
```
34+
实现我们会在视频中用画图来模拟并且手动代码实现。
35+
36+
来看下时间复杂度:
37+
38+
操作 | 平均时间复杂度 |
39+
--------------------------|----------------|
40+
linked_list.append(value) | O(1) |
41+
linked_list.appendleft(value) | O(1) |
42+
linked_list.find(value) | O(n) |
43+
linked_list.remove(value) | O(n) |
44+
45+
46+
# 双链表
47+
上边我们亲自实现了一个单链表,但是能看到很明显的问题,单链表虽然 append 是 O(1),但是它的 find 和 remove 都是 O(n)的,
48+
因为删除你也需要先查找,而单链表查找只有一个方式就是从头找到尾,中间找到才退出。
49+
这里我之前提到过如果要实现一个 lru
50+
缓存(访问时间最久的踢出),我们需要在一个链表里能高效的删除元素,并把它追加到访问表的最后一个位置,这个时候单链表就满足不了了,
51+
因为缓存在dict 里查找的时间是 O(1),你更新访问顺序就 O(n)了,缓存就没了优势。
52+
53+
这里就要使用到双链表了,相比单链表来说,每个节点既保存了指向下一个节点的指针,同时还保存了上一个节点的指针。
54+
55+
```py
56+
class Node(object):
57+
58+
def __init__(self, value=None, prev=None, next=None):
59+
self.value, self.prev, self.next = value, prev, next
60+
```
61+
62+
对, 就多了 prev,有啥优势嘛?
63+
64+
- 看似我们反过来遍历双链表了。反过来从哪里开始呢?我们只要让 root 的 prev 指向 tail 节点,不就串起来了吗?
65+
- 直接删除节点,当然如果给的是一个值,我们还是需要查找这个值在哪个节点? - 但是如果给了一个节点,我们把它拿掉,直接让它的前后节点互相指过去不就行了?哇欧,删除就是 O(1) 了,两步操作就行啦
66+
- 是不是可以很方便地
67+
68+
好,废话不多说,我们在视频里介绍怎么实现一个双链表 ADT。
69+
70+
71+
# 小问题:
72+
- 这里单链表我没有实现 insert 方法,你能自己尝试实现吗? insert(value, new_value),我想在某个值之前插入一个值。你同样需要先查找,所以这个步骤也不够高效。
73+
- 你能尝试自己实现个 lru cache 吗?
74+
- python 内置库的哪些数据结构使用到了本章讲的链式结构?
75+
76+
# 相关阅读
77+
78+
[那些年,我们一起跪过的算法题- Lru cache[视频]](https://zhuanlan.zhihu.com/p/35175401)

3_链表/linked_list.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
class Node(object):
5+
def __init__(self, value=None, next=None): # 这里我们 root 节点默认都是 None,所以都给了默认值
6+
self.value = value
7+
self.next = next
8+
9+
def __str__(self):
10+
"""方便你打出来调试,复杂的代码可能需要断点调试"""
11+
return '<Node: value: {}, next={}>'.format(self.value, self.next)
12+
13+
__repr__ = __str__
14+
15+
16+
class LinkedList(object):
17+
""" 链接表 ADT
18+
[root] -> [node0] -> [node1] -> [node2]
19+
"""
20+
21+
def __init__(self, maxsize=None):
22+
"""
23+
:param maxsize: int or None, 如果是 None,无限扩充
24+
"""
25+
self.maxsize = maxsize
26+
self.root = Node() # 默认 root 节点指向 None
27+
self.tailnode = None
28+
self.length = 0
29+
30+
def __len__(self):
31+
return self.length
32+
33+
def append(self, value): # O(1)
34+
if self.maxsize is not None and len(self) > self.maxsize:
35+
raise Exception('LinkedList is Full')
36+
node = Node(value) # 构造节点
37+
tailnode = self.tailnode
38+
if tailnode is None: # 还没有 append 过,length = 0, 追加到 root 后
39+
self.root.next = node
40+
else: # 否则追加到最后一个节点的后边,并更新最后一个节点是 append 的节点
41+
tailnode.next = node
42+
self.tailnode = node
43+
self.length += 1
44+
45+
def appendleft(self, value):
46+
headnode = self.root.next
47+
node = Node(value)
48+
self.root.next = node
49+
node.next = headnode
50+
self.length += 1
51+
52+
def __iter__(self):
53+
for node in self.iter_node():
54+
yield node.value
55+
56+
def iter_node(self):
57+
"""遍历 从 head 节点到 tail 节点"""
58+
curnode = self.root.next
59+
while curnode is not self.tailnode: # 从第一个节点开始遍历
60+
yield curnode
61+
curnode = curnode.next # 移动到下一个节点
62+
yield curnode
63+
64+
def remove(self, value): # O(n)
65+
""" 删除包含值的一个节点,将其前一个节点的 next 指向被查询节点的下一个即可
66+
67+
:param value:
68+
"""
69+
prevnode = self.root #
70+
curnode = self.root.next
71+
while curnode.next is not None:
72+
if curnode.value == value:
73+
prevnode.next = curnode.next
74+
del curnode
75+
self.length -= 1
76+
return
77+
78+
def find(self, value): # O(n)
79+
""" 查找一个节点,返回序号,从 0 开始
80+
81+
:param value:
82+
"""
83+
index = 0
84+
for node in self.iter_node(): # 我们定义了 __iter__,这里就可以用 for 遍历它了
85+
if node.value == value:
86+
return index
87+
index += 1
88+
return -1 # 没找到
89+
90+
def popleft(self): # O(1)
91+
""" 删除第一个链表节点
92+
"""
93+
if self.root.next is None:
94+
raise Exception('pop from empty LinkedList')
95+
headnode = self.root.next
96+
self.root.next = headnode.next
97+
self.length -= 1
98+
value = headnode.value
99+
del headnode
100+
return value
101+
102+
def clear(self):
103+
for node in self.iter_node():
104+
del node
105+
self.root.next = None
106+
self.length = 0
107+
108+
109+
def test_linked_list():
110+
ll = LinkedList()
111+
112+
ll.append(0)
113+
ll.append(1)
114+
ll.append(2)
115+
116+
assert len(ll) == 3
117+
assert ll.find(2) == 2
118+
assert ll.find(3) == -1
119+
120+
ll.remove(0)
121+
assert len(ll) == 2
122+
assert ll.find(0) == -1
123+
124+
assert list(ll) == [1, 2]
125+
126+
ll.appendleft(0)
127+
assert list(ll) == [0, 1, 2]
128+
assert len(ll) == 3
129+
130+
headvalue = ll.popleft()
131+
assert headvalue == 0
132+
assert len(ll) == 2
133+
assert list(ll) == [1, 2]
134+
135+
ll.clear()
136+
assert len(ll) == 0
137+
138+
139+
if __name__ == '__main__':
140+
test_linked_list()

0 commit comments

Comments
 (0)