树
"""
树相关的概述:
概述:
它属于数据结构的一种, 属于 非线性结构, 即: 每个节点可以有1个父节点(前驱) 和 n个子节点(后继)
特点:
1. 有且只能有1个根节点.
2. 每个节点都只有1个父节点 及 若干个子节点, 根节点除外(它没有父节点)
3. 没有子节点的节点称之为: 叶子节点.
名词解释:
树的度: 树结构中, 最大的 节点的度 称之为: 树的度
树的层: 根节点为第1层, 以此类推, 最大的层数就是 树的层.
森系结构: 由多棵树组成的结构.
分类:
完全二叉树: 除了最后1层, 其它层都是满的.
满二叉树: 树的所有叶子节点, 都在最后一层.
平衡二叉树: 任意节点的两个子树的高度差不超过1, 目的: 防止树退化成链表.
排序二叉树: 按照 中序(左根右) 的顺序获取数据, 是一个有序的序列.
存储:
顺序表:
只存储节点的数据, 相对节省空间. 有索引, 方便查找数据, 但是不存储节点之间的关系.
链表存储:
不仅存储节点的数据, 还存储节点的关系, 相对更占用空间.
但是方便我们维护和管理节点(的关系). 推荐.
把树转成二叉树, 每个二叉树都有: data(存数据的) + next(左子树, left child) + next(右子树 right child) 组成.
案例:
自定义代码 模拟 二叉树.
分析流程:
定义节点类Node,
属性: item(元素域), lchild(左子树), rchild(右子树)
定义二叉树类BinaryTree:
属性: root -> 充当根节点.
行为:
add() -> 往二叉树中, 添加元素的.
breadth_travel() -> 广度优先, 逐层遍历.
"""
python
# 1. 定义节点类Node
class Node:
# 初始化属性
def __init__(self, item):
self.item = item # 元素域
self.lchild = None # 左子树
self.rchild = None # 右子树
# 2. 定义二叉树类BinaryTree
class BinaryTree:
# 初始化属性.
def __init__(self, root=None):
self.root = root # 二叉树的根节点.
# 定义函数, 实现往二叉树中, 添加元素.
def add(self, item):
# 1. 判断根节点是否为空.
if self.root is None:
# 走这里, 根节点为空, 把 item封装成节点, 作为根节点即可.
self.root = Node(item)
return # 添加完毕后, 记得结束 添加动作即可.
# 2. 定义列表 -> 充当队列(先进先出), 用于记录: 二叉树中已经存在的节点.
queue = []
# 3. 把根节点添加到 队列 中.
queue.append(self.root)
# 4. 循环查找队列中的元素, 直至找到: 某个节点的左子树, 或者右子树为空的情况.
while True:
# 5. 获取(弹出) 队列中的第1个元素, 即: 从 根节点开始.
node = queue.pop(0)
# 6. 判断节点的左子树是否为空.
if node.lchild is None:
# 6.1 走到这里, 说明当前节点的左子树为空, 把新元素添加到这里即可, 并记得返回.
node.lchild = Node(item)
return
else:
# 6.2 走到这里, 说明当前节点的左子树不为空, 就把左子树添加到 队列中.
queue.append(node.lchild)
# 7. 走到这里, 说明当前节点的左子树不为空, 继续判断右子树是否为空.
if node.rchild is None:
# 7.1 走到这里, 说明当前节点的右子树为空, 把新元素添加到这里即可, 并记得返回.
node.rchild = Node(item)
return
else:
# 7.2 走到这里, 说明当前节点的右子树不为空, 就把右子树添加到 队列中.
queue.append(node.rchild)
# 定义函数, 实现广度优先, 逐层遍历.
def breadth_travel(self):
# 1. 判断根节点是否为空, 如果为空, 直接返回即可.
if self.root is None:
return
# 2. 走这里, 说明根节点不为空, 我们逐个获取节点, 打印信息即可.
queue = [] # 定义队列, 用于记录: 二叉树中已经存在的节点.
queue.append(self.root) # 把根节点添加到队列中.
# 3. 开始循环, 直至队列为空.
while len(queue) > 0:
# 4. 获取(弹出) 队列中的第1个元素, 即: 从 根节点开始.
node = queue.pop(0)
# 5. 打印当前节点的信息(元素域)
print(node.item)
# 6. 判断当前节点的左子树是否为空, 不为空就添加到队列中.
# if node.lchild != None:
if node.lchild is not None:
queue.append(node.lchild)
# 7. 判断当前节点的右子树是否为空, 不为空就添加到队列中.
if node.rchild is not None:
queue.append(node.rchild)
# 定义函数, 实现深度优先 -> 先序(前序), 根左右
def preorder_travel(self, root): # root是传入的节点
# 1. 判断传入的节点是否不为空.
if root is not None:
# 2. 走这里, 说明有元素, 根左右顺序拿即可.
print(root.item, end=' ')
# 3. 递归拿左子树.
self.preorder_travel(root.lchild)
# 4. 递归拿右子树.
self.preorder_travel(root.rchild)
# 定义函数, 实现深度优先 -> 中序, 左根右
def inorder_travel(self, root): # root是传入的节点
# 1. 判断传入的节点是否不为空.
if root is not None:
# 2. 递归拿左子树.
self.inorder_travel(root.lchild)
# 3. 走这里, 说明有元素, 左根右顺序拿即可.
print(root.item, end=' ')
# 4. 递归拿右子树.
self.inorder_travel(root.rchild)
# 定义函数, 实现深度优先 -> 后序, 左右根
def postorder_travel(self, root): # root是传入的节点
# 1. 判断传入的节点是否不为空.
if root is not None:
# 2. 递归拿左子树.
self.postorder_travel(root.lchild)
# 3. 递归拿右子树.
self.postorder_travel(root.rchild)
# 4. 走这里, 说明有元素, 左右根顺序拿即可.
print(root.item, end=' ')
# 3. 定义测试方法.
def dm01_测试节点类和二叉树类():
# 1. 创建节点对象.
n1 = Node('天龙八部')
# 2. 打印节点的: 元素域, 左子树, 右子树.
print(n1.item) # 天龙八部
print(n1.lchild) # None
print(n1.rchild) # None
print('-' * 22)
# 3. 创建二叉树对象.
# bt = BinaryTree()
bt = BinaryTree(n1)
print(bt) # 打印二叉树对象.
print(bt.root) # 打印二叉树的根节点
print(bt.root.item) # 打印二叉树的根节点的元素域
def dm02_测试队列添加和弹出队头元素():
# 1. 创建队列(先进先出), 其实: 还是列表模拟的.
queue = []
# 2. 演示队列添加元素.
queue.append('a')
queue.append('b')
queue.append('c')
# 3. 弹出队头元素.
print(queue.pop(0)) # ['a', 'b', 'c'] -> a
print(queue.pop(0)) # ['b', 'c'] -> b
print(queue.pop(0)) # ['c'] -> c
# 4. 打印队列.
# print(queue) # ['a', 'b', 'c']
def dm03_广度优先遍历():
# 1. 定义二叉树.
bt = BinaryTree()
# 2. 往二叉树中添加元素.
bt.add('a')
bt.add('b')
bt.add('c')
bt.add('d')
bt.add('e')
bt.add('f')
bt.add('g')
bt.add('h')
bt.add('i')
bt.add('j')
# 3. 遍历二叉树 -> 广度优先.
bt.breadth_travel()
def dm04_深度优先遍历():
# 1. 定义二叉树
bt = BinaryTree()
# 2. 添加元素.
bt.add(0)
bt.add(1)
bt.add(2)
bt.add(3)
bt.add(4)
bt.add(5)
bt.add(6)
bt.add(7)
bt.add(8)
bt.add(9)
# 3. 打印遍历结果.
print('先序遍历(根左右): ')
bt.preorder_travel(bt.root) # 传入根节点, 即: 从根节点开始遍历.
print('\n中序遍历(左根右): ')
bt.inorder_travel(bt.root)
print('\n后序遍历(左右根): ')
bt.postorder_travel(bt.root)
# 4. 在main函数中测试
if __name__ == '__main__':
# dm01_测试节点类和二叉树类()
# dm02_测试队列添加和弹出队头元素()
# dm03_广度优先遍历()
dm04_深度优先遍历()
模拟迭代器
"""
回顾:
生成器 -> 基于一定的规则来生成数据, 不会一下子生成所有, 而是用一个, 生成1个, 目的: 节约内存空间.
上下文管理器 -> 1个类只要实现了 enter (), exit(), 该类就是上下文管理器.
迭代器:
概述:
1个类只要实现了 iter (), next ()这两个函数, 它就是: 迭代器类.
该类的对象就是 迭代器对象.
目的:
用的时候再迭代, 迭代1个, 获取1个, 节约内存空间.
实现方式:
方式1: iter()函数 -> 可以把容器类型转成迭代器类型
方式2: 自定义类实现 iter (), next ()这两个函数, 即: 自定义代码实现迭代器.
"""
python
import sys
# 自定义代码, 模拟迭代器.
class MyIterator:
# 初始化属性
def __init__(self, max_value): # 假设: max_value = 100
self.max_value = max_value # 定义变量, 记录: 最大的值
self.current_value = 0 # 定义变量, 记录: 当前的(迭代到的)数据
# 实现 __iter__()函数, 返回: 迭代器对象.
def __iter__(self):
return self # MyIterator是迭代器类, 所以它的对象是迭代器对象, 直接返回即可.
# 实现 __next__()函数, 返回: 下一个值.
def __next__(self):
# 非法值校验.
if self.current_value >= self.max_value:
raise StopIteration # 抛出异常, 停止迭代.
# 走到这里, 说明还在合法区间, 定义变量value基础当前值, 返回即可.
value = self.current_value # 假设: value = 0 1
self.current_value += 1 # self.current_value = 1 2
return value # return 0 1
if __name__ == '__main__':
# 1. 对比 生成器 和 普通列表的内存占用.
list1 = [i for i in range(1000000) ]
my_generator = (i for i in range(1000000))
# 查看内存占用.
print(sys.getsizeof(list1)) # 8448728
print(sys.getsizeof(my_generator)) # 192
# 2. 上述生成器是用一个生成1个, 迭代器就是迭代一次, 才会获取下一个.
print(next(my_generator)) # 0
# print(next(list1)) # 报错, 列表不是迭代器.
# 3. 具体的迭代器演示.
# 方式1: iter()函数, 可以把容器类型转成迭代器类型.
list1 = [i for i in range(1000000)]
my_itr = iter(list1)
print(type(list1)) # <class 'list'>
print(type(my_itr)) # <class 'list_iterator'>
print('-' * 22)
print(next(my_itr)) # 0
print(next(my_itr)) # 1
print(next(my_itr)) # 2
print(sys.getsizeof(list1)) # 8448728
print(sys.getsizeof(my_itr)) # 48
print('-' * 22)
# 方式2: 自定义代码, 模拟上述的操作.
# 1. 创建迭代器对象.
my_itr2 = MyIterator(10)
# 2. 从迭代器中获取元素.
print(next(my_itr2)) # 0
print(next(my_itr2)) # 1
print(next(my_itr2)) # 2
print('-' * 22)
for i in my_itr2:
print(i)
print('-' * 22)
# 总结: 迭代器 和 普通列表对比.
list1 = [i for i in range(100000)] # 列表
myItr1 = iter(list1) # 把列表 -> 转成迭代器
myItr2 = MyIterator(100000) # 自定义代码实现迭代器.
# 查看空间占用.
print(sys.getsizeof(list1)) # 800984
print(sys.getsizeof(myItr1)) # 48
print(sys.getsizeof(myItr2)) # 48
约瑟夫环
"""
需求:
有一天你穿越到了古代, 碰巧约到古代的皇帝在选状元郎, 有两个好消息
1.你在此列.
- 你可以选择自己占的位置.
规则: 让所有的人从1开始不断数数, 每次累加1, 只要数到3的倍数, 就去掉这个人, 直至剩下最后1个人, 他站的位置就是: 幸运数字.
参考答案: 10个人玩游戏 -> 幸运数字: 4
"""
python
# 思路1: 传统编程思路, 业务逻辑实现.
# 1. 定义变量, 记录参与游戏的人数, 并接收.
n = int(input('请录入参与游戏的人数: '))
# 2. 生成列表.
num_list = [i for i in range(1, n + 1)]
# 3. 定义变量, 分别记录: 当前数到的数字, 当前元素的索引.
current_num = 0 # 当前数到的数字
i = 0 # 当前元素的索引
# 4. 循环, 直到列表中只剩下1个元素.
while len(num_list) != 1:
# 5. 先数 数字.
current_num += 1
# 6. 判断, 当前数字是3的倍数, 就删掉这个元素.
if current_num % 3 == 0:
num_list.pop(i)
# 细节1: 因为删除元素后, 后续元素的索引会-1, 所以: i也要同步-1
i -= 1
# 7. 走到这里, 说明上个人已经报过数字了, 我们继续游戏即可.
i += 1
# 8. 做越界处理.
if i >= len(num_list):
# 细节2: 走到这里, 说明本轮结束, (开启下轮报数)从头开始报数即可.
i = 0
# 9. 走到这里, 说明幸运数字已经找到了, 返回即可.
print(f'{n}人参与游戏, 幸运数字是: {num_list[0]}')
print('-' * 22)
# 思路2: 数学思维 -> 找规律.
# 1. 定义变量, 记录参与游戏的人数, 并接收.
# n = int(input('请录入参与游戏的人数: '))
# 2. 生成列表.
num_list = [i for i in range(1, n + 1)]
# 3. 定义变量, 分别记录: 当前元素的索引.
i = 0 # 当前元素的索引
# 4. 循环, 直到列表中只剩下1个元素.
while len(num_list) != 1:
# 5. 通过公式, 直接找到要删除的那个元素的索引.
i = (i + 2) % len(num_list)
# 6. 删除该元素.
num_list.pop(i)
# 7. 走到这里, 说明幸运数字已经找到了, 返回即可.
print(f'{n}人参与游戏, 幸运数字是: {num_list[0]}')
坚持分享 共同进步 如有错误 欢迎指出