保证一周更两篇吧,以此来督促自己好好的学习!代码的很多地方我都给予了详细的解释,帮助理解。好了,干就完了~加油!
声明:本python数据结构与算法是imooc上liuyubobobo老师java数据结构的python改写,并添加了一些自己的理解和新的东西,liuyubobobo老师真的是一位很棒的老师!超级喜欢他~
如有错误,还请小伙伴们不吝指出,一起学习~
No fears, No distractions.
# 这里为了更清楚的展示Node类,用C++来写的。
class Node{
K key;
V value;
Node* left;
Node* right
}
用链表实现时:
class Node{
K key;
V value;
Node* next;
}
# -*- coding: utf-8 -*-
# Author: Annihilation7
# Data: 2018-10-20 10:30am
# Python version: 3.6
from queue.loopqueue import LoopQueue # 会用到我们以前实现的循环队列
class Node:
def __init__(self, key=None, value=None):
"""
Description: 节点类的构造函数
"""
self.key = key # 键
self.value = value # value
self.left = None # 指向左孩子的标签
self.right = None # 指向右孩子的标签
class BstDict:
def __init__(self):
"""
Description: 基于二分搜索树的字典类的构造函数
"""
self._root = None # 初始化根节点为None
self._size = 0 # 初始化self._size
def getSize(self):
"""
Description: 获取字典内有效元素的个数
Returns:
有效元素的个数
"""
return self._size
def isEmpty(self):
"""
Description: 判断字典是否为空
Returns:
bool值,空为True
"""
return self._size == 0
def add(self, key, value):
"""
Description: 向字典中添加键-值对,若键已经存在字典中,那就更新这个键所对应的value
时间复杂度:O(logn)
Params:
- key: 待添加的键
-value: 待添加的键所对应的value
"""
self._root = self._add(self._root, key, value) # 调用self._add方法,常规套路
def contains(self, key):
"""
Description: 查看字典的键中是否包含key
时间复杂度:O(logn)
Params:
- key: 待查询的键值
Returns:
bool值,存在为True
"""
return self._getNode(self._root, key) is not None # 调用self._getNode私有函数,看返回值是否是None
def get(self, key):
"""
Description: 得到字典中键为key的value值
时间复杂度:O(logn)
Params:
- key: 待查询的键值
Returns:
相应的value值。若key不存在,就返回None
"""
node = self._getNode(self._root, key) # 拿到键为key的Node
if node: # 如果该Node不是None
return node.value # 返回对应的value
else:
return None # 否则(此时不存在携带key的Node)返回None
def set(self, key, new_value):
"""
Description: 将字典中键为key的Node的value设为new_value。注意,为防止与add函数发生混淆,
此函数默认用户已经确信key在字典中,否则报错。并不会有什么新建Node的操作,因为这么做为与add函数有相同的功能,就没有意义了。
时间复杂度:O(logn)
Params:
- key: 将要被设定的Node的键
- new_value: 新的value值
"""
node = self._getNode(self._root, key)
if node is None:
raise Exception('%s doesn\'t exist!' % key) #报错就完事了
node.value = new_value
def remove(self, key):
"""
Description: 将字典中键为key的Node删除。注:若不存在携带key的Node,返回Node就好。这个remove函数和前面的二分搜索树的remove函数极为相似,不理解的可以去前面看一下
时间复杂度:O(logn)
Params:
- key: 待删除的键
Returns:
被删除节点的value
"""
ret = self._minimum(self._root) # 调用self._minimum函数保存携带最小值的节点,便于返回
self._root = self._remove(self._root, key) # 调用self._remove函数
return ret.value # 返回携带最小值节点的value
def printBstDict(self):
"""对字典进行打印操作,这里使用广度优先遍历,因为比较直观"""
if self._root is None:
return
queue = LoopQueue()
queue.enqueue(self._root)
while not queue.isEmpty():
node = queue.dequeue()
print('[Key: %s, Value: %s]' % (node.key, node.value))
if node.left:
queue.enqueue(node.left)
if node.right:
queue.enqueue(node.right)
# private method
def _add(self, node, key, value):
"""
Description: 向以node为根的二叉搜索树中添加键-值对
Params:
- node: 根节点
- key: 待添加的键
-value: 待添加的键所对应的value
Returns:
添加后新的根节点
"""
getNode = self._getNode(self._root, key) # 先判断字典中是否存在携带这个键的Node
if getNode is not None: # 如果已经存在
getNode.value = value # 将这个节点的value设为新传入的value就好
return node # 返回根节点,不需要维护self._size哦
if node is None: # 递归到底的情况,该添加节点啦
self._size += 1 # 维护self._size
return Node(key, value) # 将新节点返回
if key < node.key: # 待添加key小于node的key,向左子树继续前进。这里的操作在二叉搜索树中详细讲过,就不BB了
node.left = self._add(node.left, key, value)
elif key > node.key: # 待添加key大于node的key,向右子树继续前进。
node.right = self._add(node.right, key, value)
return node # 将node返回,这样在递归的回归过程中逐级返回,最终返回的是根节点
def _remove(self, node, key):
"""
Description: 将以node为根节点的字典中键为key的Node删除。注:若不存在携带key的Node,返回Node就好。
时间复杂度:O(logn)
Params:
- node: 以node为根节点的字典
- key: 待删除的节点所携带的键
Returns:
删除操作后的根节点
"""
if node is None: # 到头了都没找到key
return None # 直接返回None
if key < node.key: # 待删除key小于当前Node的key
node.left = self._remove(node.left, key) # 往左子树递归
return node
elif node.key < key: # 待删除key大于当前Node的key
node.right = self._remove(node.right, key) # 网右子树递归
return node
else: # 此时待删除key和当前Node的key相等,别忘了有三种情况
if node.left is None: # 左子树为空的情况
right_node = node.right # 记录当前Node的右孩子
node.right = None # 当前Node的右孩子设为None,便于垃圾回收
self._size -= 1 # 维护self._size
return right_node # 将被删除Node的右孩子返回,即用右孩子来代替被删除节点的位置
elif node.right is None: # 右子树为空的情况,大同小异,不再赘述
left_node = node.left
node.left = None
self._size -= 1
return left_node
else: # 这里不再赘述了,不清楚的话回去看看二分搜索树那章,一样的操作
successor = self._minimum(node.right)
successor.right = self._removeMin(node.right)
self._size += 1
successor.left = node.left
node.left = node.right = None
self._size -= 1
return successor
def _minimum(self, node):
"""
Description: 返回以node为根的二分搜索树的携带最小值的节点,下面的这些操作在二分搜索树中均有涉及,如果看不懂回到那一章再理解下就好了
Params:
- node: 根节点
Returns:
携带最小值的节点
"""
if node.left is None: # 递归到底的情况,node的左孩子已经为空了
return node # 返回node
return self._minimum(node.left) # 否则往左子树继续撸
def _removeMin(self, node):
"""
Description: 删除以node为根的二分搜索树的携带最小值的节点,同理,返回删除后的根节点
Params:
- node: 根节点
Returns:
返回删除操作后的树的根节点
"""
if node.left is None: # 递归到底的情况,node的左孩子已经为空
right_node = node.right # 记录node的右孩子,为None也无所谓的
node.right = None # 将node的右孩子设为None,使其与树断绝联系,从而被垃圾回收
self._size -= 1 # 维护self._size
return right_node # 返回node的右孩子,即用右孩子来代替当前的node
node.left = self._removeMin(node.left) # 否则往node的左子树继续撸
return node # 返回node,从而最终返回根节点
def _getNode(self, node, key):
"""
Description: 设置一个根据key来找到以node为根的二叉搜索树的相对应的Node的私有函数,便于方便的实现其他函数
Params:
- node: 传入的根节点
- key: 带查找的key
Returns:
携带key的节点Node, 若不存在携带key的节点,返回None
"""
if node is None: # 都到底了还没有找到,直接返回None
return None
# 注意我们的二分搜索树依旧不包含重复的键哦~这也是字典的基本特点
if key < node.key: # 待查找的key小于当前节点的key,向左子树递归就完事了
return self._getNode(node.left, key)
elif node.key < key: # 待查找的key大于当前节点的key,向右子树递归就完事了
return self._getNode(node.right, key)
else: # 此时带查找的key和node.key是相等的,直接返回这个Node就好
return node
from DICT.bstdict import BstDict # 我们的基于二分搜索树的字典类写在bstdict.py文件中
test_bst = BstDict()
print('初始Size: ', test_bst.getSize())
print('是否为空?', test_bst.isEmpty())
add_list = [15, 4, 25, 22, 3, 19, 23, 7, 28, 24]
print('待添加元素:', add_list)
for add_elem in add_list:
test_bst.add(add_elem, str(add_elem) + 'x')
##################################################
# 15(15x) #
# / \ #
# 4(4x) 25(x) #
# / \ / \ #
# 3(3x) 7(7x) 22(22x) 28(28x) #
# / \ #
# 19(19x) 23(23x) #
# \ #
# 24(24x) #
##################################################
print('添加元素后打印集合:')
test_bst.printBstDict()
print('此时的Size: ', test_bst.getSize())
print('字典中是否包含键23?', test_bst.contains(23))
print('获取键23的value:', test_bst.get(23))
print('将键为7的value设为"努力学习"并打印:')
test_bst.set(7, '努力学习')
test_bst.printBstDict()
print('删除键22并打印:')
test_bst.remove(22)
test_bst.printBstDict()
print('此时的Size:', test_bst.getSize())
初始Size: 0
是否为空? True
待添加元素: [15, 4, 25, 22, 3, 19, 23, 7, 28, 24]
添加元素后打印集合:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 7x]
[Key: 22, Value: 22x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 23, Value: 23x]
[Key: 24, Value: 24x]
此时的Size: 10
字典中是否包含键23? True
获取键23的value: 23x
将键为7的value设为"努力e学习"并打印:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 努力学习]
[Key: 22, Value: 22x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 23, Value: 23x]
[Key: 24, Value: 24x]
删除键22并打印:
[Key: 15, Value: 15x]
[Key: 4, Value: 4x]
[Key: 25, Value: 25x]
[Key: 3, Value: 3x]
[Key: 7, Value: 努力学习]
[Key: 23, Value: 23x]
[Key: 28, Value: 28x]
[Key: 19, Value: 19x]
[Key: 24, Value: 24x]
此时的Size: 9
# -*- coding: utf-8 -*-
# Author: Annihilation7
# Data: 2018-10-19 7:29 pm
# Python version: 3.6
class Node:
def __init__(self, key=None, value=None, next=None):
"""
Description: 节点的构造函数
Params:
- key: 传入的键值,默认为None
- value: 传入的键所对应的value值,默认为None
- next: 指向下一个Node的标签,默认为None
"""
self.key = key
self.value = value
self.next = next # 下一个节点为None
class LinkedListDict:
"""以链表作为底层数据结构的字典类"""
def __init__(self):
"""
Description: 字典的构造函数
"""
self._dummyhead = Node() # 建立一个虚拟头结点,前面讲过,不再赘述
self._size = 0 # 字典中有效元素的个数
def getSize(self):
"""
Description: 获取字典中有效元素的个数
Returns:
有效元素的个数
"""
return self._size
def isEmpty(self):
"""
Description: 判断字典是否为空
Returns:
bool值,空为True
"""
return self._size == 0
def contains(self, key):
"""
Description: 查看字典的键中是否包含key
时间复杂度:O(n)
Params:
- key: 待查询的键值
Returns:
bool值,存在为True
"""
return self._getNode(key) is not None # 调用self._getNode私有函数,看返回值是否是None
def get(self, key):
"""
Description: 得到字典中键为key的value值
时间复杂度:O(n)
Params:
- key: 待查询的键值
Returns:
相应的value值。若key不存在,就返回None
"""
node = self._getNode(key) # 拿到键为key的Node
if node: # 如果该Node不是None
return node.value # 返回对应的value
else:
return None # 否则(此时不存在携带key的Node)返回None
def add(self, key, value):
"""
Description: 向字典中添加key,value键值对。若字典中已经存在相同的key,更新其value,否咋在头部添加Node,因为时间复杂度为O(1)
时间复杂度:O(n)
Params:
- key: 待添加的键值
- value: 待添加的键值的value
"""
node = self._getNode(key) # 先判断字典中是否存在这个键
if node != None: # 已经存在
node.value = value # 更新这个Node的value
else:
self._dummyhead.next = Node(key, value, self._dummyhead.next) # 否则在头部添加,添加操作链表那一章有讲,这里不再赘述
self._size += 1 # 维护self._size
def set(self, key, new_value):
"""
Description: 将字典中键为key的Node的value设为new_value。注意,为防止与add函数发生混淆,
此函数默认用户已经确信key在字典中,否则报错。并不会有什么新建Node的操作,因为这么做为与add函数有相同的功能,就没有意义了。
时间复杂度:O(n)
Params:
- key: 将要被设定的Node的键
- new_value: 新的value值
"""
node = self._getNode(key) # 找到携带这个key的Node
if node is None: # 没找到
raise Exception('%s doesn\'t exist!' % key) #报错就完事了
node.value = new_value # 找到了就直接将返回节点的value设为new_value
def remove(self, key):
"""
Description: 将字典中键为key的Node删除。注:若不存在携带key的Node,返回Node就好。
时间复杂度:O(n)
Params:
- key: 待删除的键
Returns:
被删除节点的value
"""
pre = self._dummyhead # 找到要被删除节点的前一个节点(惯用手法,不再赘述)
while pre.next is not None: # pre的next只要不为空
if pre.next.key == key: # 如果找到了
break # 直接break,此时pre停留在要被删除节点的前一个节点
pre = pre.next # 否则往后撸
if pre.next is not None: # 此时找到了
delNode = pre.next # 记录一下要被删除的节点,方便返回其value
pre.next = delNode.next # 不再赘述,如果不懂就去看看链表那节吧。O(∩_∩)O
delNode.next = None # delNode的下一个节点设为None,使delNode完全与字典脱离,便于垃圾回收器回收
self._size -= 1 # 维护self._size
return delNode.value # 返回被删除节点的value
return None # 此时pre的next是None!说明并没有找到这个key,返回None就好了。
def printLinkedListDict(self):
"""打印字典元素"""
cur = self._dummyhead.next
while cur != None:
print('[Key: %s, Value: %s]' % (cur.key, cur.value), end='-->')
cur = cur.next
print('None')
# private functions
def _getNode(self, key):
"""
Description: 一个辅助函数,是私有函数。功能就是返回要查找的键的Node,若key不存在就返回None
时间复杂度:O(n)
Params:
- key: 要查找的键值
Returns:
返回带查找key的节点,若不存在返回None
"""
cur = self._dummyhead.next # 记录当前节点
while cur != None: # cur没到头
if cur.key == key: # 找到了
return cur # 返回当前的Node
cur = cur.next # 没找到就往后撸
return None # cur此时为None了。。说明已经到头还没找到,返回None
from DICT.linkedlistdict import LinkedListDict # 我们的字典类写在了linkedlistmap.py文件中
test_map = LinkedListDict()
print('初始字典Size:', test_map.getSize())
print('初始字典是否为空:', test_map.isEmpty())
for i in range(10):
test_map.add(i, str(i) + '_index')
print('10次添加操作后:')
print('Size: ', test_map.getSize())
print('是否包含7?', test_map.contains(7))
test_map.printLinkedListDict() # 由于在头部插入,所以打印是反向的哦~
print('键为6的value:', test_map.get(6))
print('将键值为4的value设为 "你好呀",并打印:')
test_map.set(4, '你好呀')
test_map.printLinkedListDict()
# test_map.set(12, 'debug')
print('删除键为7的元素,并打印:')
test_map.remove(7)
test_map.printLinkedListDict()
print('此时的Size为:', test_map.getSize())
初始字典Size: 0
初始字典是否为空: True
10次添加操作后:
Size: 10
是否包含7? True
[Key: 9, Value: 9_index]-->[Key: 8, Value: 8_index]-->[Key: 7, Value: 7_index]-->[Key: 6, Value: 6_index]-->[Key: 5, Value: 5_index]-->[Key: 4, Value: 4_index]-->[Key: 3, Value: 3_index]-->[Key: 2, Value: 2_index]-->[Key: 1, Value: 1_index]-->[Key: 0, Value: 0_index]-->None
键为6的value: 6_index
将键值为4的value设为 "你好呀",并打印:
[Key: 9, Value: 9_index]-->[Key: 8, Value: 8_index]-->[Key: 7, Value: 7_index]-->[Key: 6, Value: 6_index]-->[Key: 5, Value: 5_index]-->[Key: 4, Value: 你好呀]-->[Key: 3, Value: 3_index]-->[Key: 2, Value: 2_index]-->[Key: 1, Value: 1_index]-->[Key: 0, Value: 0_index]-->None
删除键为7的元素,并打印:
[Key: 9, Value: 9_index]-->[Key: 8, Value: 8_index]-->[Key: 6, Value: 6_index]-->[Key: 5, Value: 5_index]-->[Key: 4, Value: 你好呀]-->[Key: 3, Value: 3_index]-->[Key: 2, Value: 2_index]-->[Key: 1, Value: 1_index]-->[Key: 0, Value: 0_index]-->None
此时的Size为: 9
若有还可以改进、优化的地方,还请小伙伴们批评指正!