下述是举了一个遍历二叉树的例子
from typing import List
class TreeNode:
#定义树节点
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
#定义前序遍历函数
def preorderTraversal(self, root: TreeNode) -> List[int]:
# self:这是Python中类方法的第一个参数,它指向当前类的实例。当您在类内部定义一个方法时,
# 每个方法都至少有一个参数,通常是self,它代表类的实例本身。
# root: TreeNode:这是第二个参数,root是参数名,而TreeNode是参数的类型注解。这意味着root应该是一个TreeNode类的实例。
# 类型注解不会改变参数的类型,它只是告诉其他开发者或使用静态类型检查工具(如mypy)这个参数期望的类型。
res = []
# dfs(node):这是一个递归函数,它接受一个node参数,该参数代表当前正在访问的树节点。
# if node is None: :这是一个基本情况检查。如果当前节点为None(即没有子节点或已经到达叶节点的子节点),则函数返回,结束当前的递归调用。
# res.append(node.val):如果当前节点不为None,则将当前节点的值添加到结果列表res中。
# dfs(node.left):递归调用dfs函数,以当前节点的左子节点作为参数,继续遍历左子树。
# dfs(node.right):递归调用dfs函数,以当前节点的右子节点作为参数,继续遍历右子树。
def dfs(node):
if node is None:
return
res.append(node.val)
dfs(node.left)
dfs(node.right)
# dfs(root):这是对dfs函数的第一次调用,它启动了整个递归过程。参数root是二叉树的根节点,它是遍历的起始点。
# 之所以要调用dfs(root),是因为递归过程需要一个起始点,即从树的根节点开始遍历。
# 根节点是整个树的入口点,通过递归调用dfs函数,可以遍历整棵树的所有节点。
dfs(root)
return res
#定义中序遍历函数
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
dfs(node.left)
res.append(node.val)
dfs(node.right)
dfs(root)
return res
#定义后序遍历函数
def postorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
dfs(node.left)
dfs(node.right)
res.append(node.val)
dfs(root)
return res
#测试代码
if __name__ == "__main__":
# 构建一个二叉树
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
root.right.left = TreeNode(6)
root.right.right = TreeNode(7)
#创建一个Solution实例(对象)
sol = Solution()
result = sol.preorderTraversal(root)
result1 = sol.inorderTraversal(root)
result2 = sol.postorderTraversal(root)
print(result2)
from typing import List
# 定义二叉树节点类
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 定义Solution类
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
res.append(node.val)
dfs(node.left)
dfs(node.right)
dfs(root)
return res
# 测试代码
if __name__ == "__main__":
# 构建一个二叉树
# 1
# / \
# 2 3
# / \
# 4 5
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
# 创建Solution类的实例
sol = Solution()
# 调用preorderTraversal方法并打印结果
print(sol.preorderTraversal(root)) # 应该输出 [1, 2, 4, 5, 3]
from typing import List
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def preorder_traversal(root: TreeNode) -> List[int]:
res = []
def dfs(node):
if node is None:
return
res.append(node.val)
dfs(node.left)
dfs(node.right)
dfs(root)
return res
# 测试代码
if __name__ == "__main__":
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(preorder_traversal(root)) # 输出: [1, 2, 4, 5, 3]
为什么要创建一个类来实现遍历?为什么又要创建一个类的实例?只用函数实现调用和用创建实例类来调用有什么区别?
为什么创建一个类来实现遍历?
封装:类允许我们将数据(属性)和行为(方法)封装在一起。在二叉树遍历的情况下,
Solution
类将遍历算法封装起来,使其更加模块化和可重用。可维护性:通过类,我们可以更容易地管理和维护代码。如果将来需要修改遍历算法,我们只需要修改
Solution
类中的preorderTraversal
方法,而不需要触及使用该方法的代码。可扩展性:如果我们想要为二叉树添加更多的操作(例如中序遍历、后序遍历),可以在
Solution
类中添加更多的方法,而不需要定义多个独立的函数。命名空间:类为方法提供了一个命名空间,有助于避免全局命名空间的污染,并且可以清晰地表达方法与数据之间的关系。
为什么创建类的实例?
实例化:要使用类定义的方法,我们需要创建类的实例(对象)。实例化过程分配内存来存储对象的属性,并使方法可供调用。
状态:类的实例可以保存状态。虽然在这个特定的例子中,
Solution
类没有使用实例属性来保存状态,但如果需要,可以在实例中保存与遍历相关的状态。
函数调用与类实例调用的区别:
函数调用:
- 通常用于执行单一的操作,不与特定的数据结构或状态相关联。
- 直接通过函数名调用,不需要实例化。
- 不保存状态,所有数据都是通过参数传递。
类实例调用:
- 适用于需要封装多个操作和数据的情况。
- 需要先创建类的实例,然后通过实例调用方法。
- 可以保存状态,即使方法调用结束后,实例属性仍然存在。
为什么说要使用类定义的方法,我们需要创建类的实例(对象) ?
在面向对象编程(OOP)中,类(Class)定义了一个数据类型的蓝图,它包含了数据成员(属性)和方法。而对象(Object)是类的实例,是实际存在的实体。以下是为什么需要创建类的实例来使用类定义的方法的几个原因:
1. 实例化过程:
- 分配内存:当我们创建一个类的实例时,Python 会为这个实例分配内存,用于存储其属性和方法的引用。
- 初始化:实例化过程中会自动调用类的构造函数(
__init__
方法),允许我们初始化实例的属性。2. 封装:
- 数据和行为:类将数据和操作数据的方法封装在一起。创建实例是为了将这些数据和操作应用于具体的对象。
- 状态:每个实例都有自己的状态(即属性值),即使是同一个类的不同实例,它们的状态也可能是不同的。
3. 多态性:
- 方法重写:子类可以重写(覆盖)父类的方法。通过创建实例,我们可以调用特定子类的实现,而不是仅仅调用父类的方法。
4. 方法的作用域:
- 实例方法:类中定义的方法默认是实例方法,它们操作的是类的实例(即对象)。这些方法必须通过实例来调用,因为它们需要访问实例的数据。
- 类方法:虽然有些方法可以使用类方法(通过装饰器
@classmethod
定义),它们操作的是类本身而不是实例,但大多数方法都是实例方法。5. 访问控制:
- 私有属性和方法:类中可以定义私有属性和方法,它们不能从类外部直接访问,只能通过类的实例方法来访问。创建实例是为了能够在类的内部逻辑控制下访问这些私有成员。
6. 代码组织:
- 模块化:通过类,我们可以将代码组织成模块化的单元,每个类负责一部分功能。创建实例是为了使用这些功能单元。