在 Python 中,类是面向对象编程的核心工具。它们用于封装数据和相关的操作。通过类,我们可以创建自己的数据结构,并且通过定义方法来操作这些数据。
一、类的基本用法
1. 类的定义和创建
类是通过 class 关键字定义的,类的实例是通过调用类的构造方法(__init__)创建的。
class Person:
# 类的构造方法,用于初始化实例的属性
def __init__(self, name, age):
self.name = name # 实例变量
self.age = age # 实例变量
# 类的方法,用于执行操作
def greet(self):
print(f"Hello, my name is {self.name}, and I'm {self.age} years old.")
# 创建类的实例
person = Person("Alice", 30)
person.greet() # 输出:Hello, my name is Alice, and I'm 30 years old.
2. 实例变量和类变量
- 实例变量:每个实例(对象)拥有独立的变量。
- 类变量:所有实例共享的变量。
class Counter:
total = 0 # 类变量
def __init__(self):
self.count = 0 # 实例变量
def increment(self):
Counter.total += 1
self.count += 1
# 创建多个实例
counter1 = Counter()
counter2 = Counter()
counter1.increment()
counter2.increment()
print(counter1.count, counter2.count, Counter.total) # 输出:1 1 2
3. 继承和多态
继承可以让一个类从另一个类派生,获取父类的属性和方法。子类可以重写父类的方法,或者添加新的方法。
class Animal:
def speak(self):
print("Animal is making a sound.")
class Dog(Animal): # Dog 类继承自 Animal
def speak(self): # 重写父类的方法
print("Woof!")
class Cat(Animal):
def speak(self):
print("Meow!")
# 多态的例子
animals = [Dog(), Cat()]
for animal in animals:
animal.speak() # 输出:Woof! Meow!
二、类在科研中的应用
在科研中,类常用于封装数据结构和算法。特别是在机器学习和数据分析中,类能帮助我们组织模型、数据预处理、训练过程等内容。
1. 定义机器学习模型类
例如,假设我们在实现一个简单的神经网络类:
import numpy as np
class SimpleNN:
def __init__(self, input_size, hidden_size, output_size):
# 初始化神经网络的层
self.weights1 = np.random.rand(input_size, hidden_size)
self.weights2 = np.random.rand(hidden_size, output_size)
self.bias1 = np.zeros((1, hidden_size))
self.bias2 = np.zeros((1, output_size))
def forward(self, X):
# 前向传播:输入 X 经过层1和层2的计算
self.z1 = np.dot(X, self.weights1) + self.bias1
self.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.weights2) + self.bias2
output = self.sigmoid(self.z2)
return output
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def train(self, X, y, epochs=1000, lr=0.01):
# 简单的训练过程
for epoch in range(epochs):
output = self.forward(X)
loss = np.mean((output - y) ** 2) # 均方误差损失
# 反向传播和梯度更新过程略,实际中会计算梯度并更新权重
print(f"Epoch {epoch}, Loss: {loss}")
这个类 SimpleNN 代表了一个简单的神经网络,包含了前向传播、损失计算和训练的方法。
2. 数据结构封装
我们可以用类来封装研究中的数据结构,比如时间序列、图像数据或实验结果:
class Experiment:
def __init__(self, name, data):
self.name = name
self.data = data # 存储实验数据
def analyze(self):
# 执行数据分析,如计算均值、标准差等
return np.mean(self.data), np.std(self.data)
# 创建实验对象
exp = Experiment("Test Experiment", np.random.randn(100))
mean, std = exp.analyze()
print(f"Mean: {mean}, Std: {std}")
三、类在 LeetCode 中的应用
在 LeetCode 的算法题中,类主要用于组织状态、递归或动态规划算法,特别是当问题涉及到多层数据结构时(如树、图、链表等)。类能帮助我们将状态和逻辑分开,使得代码更加模块化和清晰。
1. 使用类来解决链表问题
假设要解决一个反转链表的问题,可以使用类来封装链表节点,并实现反转操作。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_linked_list(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
# 示例链表: 1 -> 2 -> 3 -> 4
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4))))
new_head = reverse_linked_list(head)
while new_head:
print(new_head.val, end=" -> ")
new_head = new_head.next
2. 使用类来解决树的问题
比如,在二叉树问题中,定义一个类来表示树节点,然后用递归方法进行操作。
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def preorder_traversal(root):
if root is None:
return []
return [root.val] + preorder_traversal(root.left) + preorder_traversal(root.right)
# 示例树: 1
# / \
# 2 3
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(preorder_traversal(root)) # 输出: [1, 2, 3]
3. 动态规划问题中的类应用
在某些动态规划问题中,我们可以使用类来封装状态。比如在解决背包问题时:
class Knapsack:
def __init__(self, capacity):
self.capacity = capacity
self.dp = [0] * (capacity + 1)
def add_item(self, weight, value):
for w in range(self.capacity, weight - 1, -1):
self.dp[w] = max(self.dp[w], self.dp[w - weight] + value)
def get_max_value(self):
return self.dp[self.capacity]
# 使用例子
knapsack = Knapsack(10)
knapsack.add_item(3, 4) # 重量3,价值4
knapsack.add_item(4, 5) # 重量4,价值5
knapsack.add_item(5, 7) # 重量5,价值7
print(knapsack.get_max_value()) # 输出最大价值
总结
- 类 在 Python 中是封装数据和操作的工具,可以用来创建复杂的数据结构和管理状态。
- 在 科研 中,类可以用来封装模型、数据、算法等内容,帮助我们组织实验、训练模型、实现数据分析。
- 在 LeetCode 中,类帮助我们解决问题时,可以组织状态、封装递归算法,或者在动态规划、树、链表等问题中提供清晰的结构。
在实际应用中,类帮助我们将相关的数据和功能聚合起来,使得代码更加清晰、可维护和可扩展。
108. Convert Sorted Array to Binary Search Tree
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution(object):
def sortedArrayToBST(self, nums):
def build(nums,left,right):
if left>right:
return None
mid=(left+right)//2
root=TreeNode(nums[mid])
root.left=build(nums,left,mid-1)
root.right=build(nums,mid+1,right)
return root
return build(nums,0,len(nums)-1)
- root = TreeNode(nums[mid_index]) 为什么这样就可以创建一个值为 nums[mid]的根节点?
它触及了面向对象编程 (Object-Oriented Programming, OOP) 的核心概念,而不仅仅是这个特定算法。
代码 root = TreeNode(nums[mid_index]) 之所以能“创建”一个节点,是因为 TreeNode 是一个类 (Class)。
让我们来分解这个过程:
1. 什么是类 (Class)?
你可以把 类(Class) 想象成一个蓝图或模板。
在你的代码示例中,TreeNode 这个类就是一张“二叉树节点”的蓝图。这张蓝图规定了:
- 一个“二叉树节点”必须有一个值(我们叫它
val)。 - 一个“二叉树节点”可以有一个指向左边子节点的指针(我们叫它
left)。 - 一个“二叉树节点”可以有一个指向右边子节点的指针(我们叫它
right)。
这个蓝图在 Python 中是这样定义的:
class TreeNode:
# 这个 __init__ 函数就是蓝图的 "构造说明"
# 它会在你"建造"一个新节点时自动运行
def __init__(self, val=0, left=None, right=None):
self.val = val # 把传入的 val 存为自己的 .val 属性
self.left = left # 把传入的 left 存为自己的 .left 属性
self.right = right # 把传入的 right 存为自己的 .right 属性
2. 什么是对象 (Object)?
如果说 TreeNode 类是蓝图,那么 对象 (Object) 就是根据这张蓝图实际建造出来的东西。
在你的代码中,root 就是这个实际被建造出来的“二叉树节点”对象。
3. TreeNode(nums[mid_index]) 这一步在做什么?
这一步叫做实例化 (Instantiation),也就是“根据蓝图建造一个实例(对象)”的过程。
当你调用 TreeNode(...) 时,你就是在对 Python 说:
“嘿,请按照 TreeNode 这张蓝图,帮我造一个新的二叉树节点对象!”
TreeNode:指定要用的蓝图。( ... ):这是“开始建造”的命令。nums[mid_index]:这是你递给建造工人的材料。
这个材料(nums[mid_index] 的值,比如 0)会被传递给蓝图的“构造说明”,也就是 __init__ 函数:
# 你的调用:
TreeNode(nums[mid_index])
# 对应到 __init__ 函数:
def __init__(self, val=0, left=None, right=None):
# self: (Python 自动处理,它代表"正在被建造的这个新对象")
# val: 接收了 nums[mid_index] 这个值
# left: (你没提供,所以使用默认值 None)
# right: (你没提供,所以使用默认值 None)
# "构造说明" 开始执行:
self.val = nums[mid_index] # 新对象的 .val 属性被设为 nums[mid_index]
self.left = None # 新对象的 .left 属性被设为 None
self.right = None # 新对象的 .right 属性被设为 None
4. root = ... 这一步在做什么?
这一步是赋值 (Assignment)。
TreeNode(nums[mid_index]) 成功地在内存中建造了一个新的“二叉树节点”对象(它有 val、left、right 三个属性)。
root = 这部分代码就是给这个刚刚诞生的新对象贴上一个名叫 root 的标签,这样你后续才能通过这个名字找到它、使用它(比如,去设置 root.left = ...)。
总结
所以,root = TreeNode(nums[mid_index]) 这行代码的完整含义是:
- 计算
nums[mid_index]:从数组中取出中间的那个值(例如0)。 - 调用
TreeNode类的构造函数:请求“建造”一个TreeNode的新实例(对象)。 - 传递参数:把
0这个值作为val参数传给构造函数。 - 执行
__init__:构造函数运行,创建了一个新对象,并设置self.val = 0,self.left = None,self.right = None。 - 赋值:将这个新创建的对象(它现在是一个功能齐全的、值为 0 的根节点)赋值给
root这个变量。
这就是为什么这行代码能够创建—个值为 nums[mid] 的根节点。这是面向对象编程的一个最基本、最常见的操作。
- 但是这个代码里值是0啊,nums[mid]到底是怎么传进去的
val=0 是一个默认参数(Default Argument),而不是一个固定的值。
这行代码:
def __init__(self, val=0, left=None, right=None):
它的意思是:
“当我(__init__)被调用来创建一个新节点时:
- 请你(调用者) 给我一个值,我会把它存为
val。 - 如果你不给我
val,那我就用0作为备用的默认值。”
它是如何工作的
情况 A:你不提供值(使用默认值)
如果你这样写代码:
my_node = TreeNode()
Python 就会去看 __init__ 的定义,发现你没有提供 val,于是它就使用了默认值。
my_node.val会是0my_node.left会是Nonemy_node.right会是None
情况 B:你提供了值(你的代码就是这种情况)
在你的代码中,你写的是:
root = TreeNode(nums[mid])
假设 nums = [-10, -3, 0, 5, 9] 并且 mid = 3。
那么 nums[mid] 的值就是 5。
所以,你的代码实际上是在调用:
root = TreeNode(5)
当 Python 看到这个,它会把 5 作为第一个参数传递给 __init__:
# 你的调用: TreeNode(5)
# Python 这样匹配参数:
def __init__(self, val=0, left=None, right=None):
# val 接收了你传进来的 5 (默认值 0 被忽略)
# left 你没传,所以使用默认值 None
# right 你没传,所以使用默认值 None
self.val = val # 也就是 self.val = 5
self.left = left # 也就是 self.left = None
self.right = right# 也就是 self.right = None
因此,root.val 就被设置成了 5。
- 是默认nums[mid]作为第一个参数被传进去吗?为什么不会被默认成left或者right?
是的,你完全说对了!nums[mid] 默认就是作为第一个参数被传进去的。
这涉及到 Python 中函数参数的两种类型:位置参数(Positional Arguments) 和 关键字参数(Keyword Arguments)。
1. Python 如何匹配参数
当你调用一个函数时,Python 会严格按照顺序来匹配参数。
我们来看 TreeNode 的“蓝图” ( __init__ 函数):
def __init__(self, val=0, left=None, right=None):
这个函数定义了它能接收的参数顺序:
self:这个是 Python 自动处理的(代表对象自己),你不用管它。- 第 1 个你能传的参数是:
val - 第 2 个你能传的参数是:
left - 第 3 个你能传的参数是:
right
2. 你的调用:root = TreeNode(nums[mid])
假设 nums[mid] 的值是 5。你的调用实际上是 TreeNode(5)。
Python 会这样匹配:
- “我收到了1 个参数(值是
5)。” - “我应该把它赋给谁?”
- “按照顺序,第 1 个参数是
val。” - “好的,
val = 5。” - “(
val的默认值0被覆盖了,不再使用)。” - “调用者没有提供第 2 个参数(
left),所以我使用left的默认值None。” - “调用者没有提供第 3 个参数(
right),所以我使用right的默认值None。”
这就是为什么它不会被误认为是 left 或 right。它严格遵守从左到右的顺序。
3. 如何才能把它传给 left?
如果你不想按顺序,你必须使用关键字参数(即明确“指定名字”)来告诉 Python 你的意图。
情况 A(你的代码):位置参数
root = TreeNode(5)
- 结果:
root.val是5,root.left是None,root.right是None
情况 B:关键字参数
如果你想跳过 val,直接给 left 赋值,你必须这样写:
root = TreeNode(left=5)
- 结果:
root.val是0(因为你没传,所以用了默认值),root.left是5,root.right是None
情况 C:混合使用
root = TreeNode(5, left=some_node, right=another_node)
- 结果:
root.val是5,root.left是some_node,root.right是another_node
总结:
因为代码 TreeNode(nums[mid]) 只提供了一个参数,Python 会默认把它分配给参数列表中的第一个可用位置,也就是 val。

2万+

被折叠的 条评论
为什么被折叠?



