当前位置: 首页 > article >正文

【Python进阶】Python中的数据库交互:ORM技术与SQLAlchemy

1、数据库与Python编程环境

1.1 数据库管理系统概述

数据库管理系统(DBMS)是现代信息技术中的基石之一,它们扮演着组织、存储和管理大量数据的角色。想象一下,数据库就像一个巨大的图书馆,其中书架上的书籍代表着不同的数据表,每本书则是表中的行记录,而书页上的内容则对应着每个字段的信息。关系型数据库,例如MySQL、PostgreSQL等,采用表格的形式组织数据,并遵循严格的数学理论——关系代数,通过SQL(Structured Query Language)这一通用语言进行交互。

1.1.1 关系型数据库与非关系型数据库简介

关系型数据库(Relational Database)基于关系模型,其数据以表格形式存储,各表之间通过主键和外键关联。以银行账户为例,用户信息可能存储在一个用户表中,账户信息存储在另一个表中,两表通过用户的唯一标识相互关联。关系型数据库具有高度结构化、事务支持和ACID属性(原子性、一致性、隔离性和持久性)等特点。

非关系型数据库(NoSQL Database),又称非关系型数据存储,种类多样,包括文档型(如MongoDB)、键值型(如Redis)、列族型(如Cassandra)和图形数据库(如Neo4j)等。它们通常为了应对大规模分布式存储、高并发访问以及灵活的数据模型而设计。例如,在社交网络中,用户产生的动态数据可以通过文档数据库轻松存储,无需预定义严格的表格结构。

1.1.2 SQL语言基础及其在数据库管理中的作用

SQL语言是用于与关系型数据库进行交互的标准语言,能够执行诸如创建和修改表、插入和查询数据、更新记录及删除信息等多种操作。下面是一些简单的SQL命令示例:

-- 创建一个用户表
CREATE TABLE Users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(255) UNIQUE,
    password_hash VARCHAR(128)
);

-- 插入一条记录
INSERT INTO Users (id, username, email, password_hash) 
VALUES (1, 'Alice', 'alice@example.com', 'hashed_password');

-- 查询所有用户名
SELECT username FROM Users;

-- 更新邮箱地址
UPDATE Users SET email = 'new_email@example.com' WHERE id = 1;

-- 删除特定记录
DELETE FROM Users WHERE id = 1;

1.2 Python与数据库交互的重要性

1.2.1 Python在数据分析与后端开发中的角色

Python凭借其简洁易读的语法、丰富的库资源和高效的数据处理能力,在数据分析师和后端开发者中广受欢迎。数据分析时,Python可通过Pandas库处理大量CSV或其他格式的数据,之后将结果导入数据库以便长期存储和后续查询。在后端开发中,Python框架如Django、Flask常配合数据库完成数据增删改查功能,构建高效稳定的Web应用。

1.2.2 数据持久化存储需求与Python的解决方案

在软件开发过程中,数据持久化存储是至关重要的环节。Python通过各种数据库驱动模块(如psycopg2、pyodbc等)直接与数据库通信,或者通过ORM框架(如SQLAlchemy)提供更为抽象、面向对象的方式来操作数据库。ORM使得开发者能够使用Python类和对象的方式处理数据,大大提高了开发效率和代码的可维护性。例如,使用SQLAlchemy时,开发者可以像这样定义一个简单的User类并映射到数据库表:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(50), nullable=False)
    email = Column(String(255), unique=True, nullable=False)

# 进一步通过Session进行数据库操作...

这段代码定义了一个表示用户表的Python类,通过SQLAlchemy的声明式API,可以直接将这个类与数据库中的users表关联起来,实现了Python对象与数据库记录的无缝映射。

2、ORM概念与优势

2.1 ORM(Object-Relational Mapping)原理

2.1.1 对象模型与关系模型的映射机制

ORM,即对象关系映射,是一种程序设计技术,它将关系数据库的表结构转化为易于管理和使用的面向对象模型。想象一下,如果你正在构建一款博客平台,你有一个Article对象,它包含标题、内容、作者等属性。而在数据库中,你需要一个articles表来存储这些信息。ORM就像是翻译官,它帮你把Python对象的属性自动转化为数据库表的字段,并在需要的时候将数据库查询结果反向转换为Python对象。

例如,假设我们有一个简单的Article类:

class Article:
    def __init__(self, title, content, author):
        self.title = title
        self.content = content
        self.author = author

ORM框架会将其映射到数据库中的一个表结构:

CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255),
    content TEXT,
    author_id INTEGER REFERENCES authors(id)
);

2.1.2 ORM对比原生SQL的优势与适用场景

ORM的优势在于:

代码层面的抽象:程序员可以用更接近领域模型的语言编写代码,无需关注底层SQL细节,降低了开发难度,提高了代码的可读性和维护性。
减少冗余代码:ORM提供了CRUD操作的自动化,比如自动拼接SQL语句、处理参数绑定、结果集映射等,极大地减少了手动编写SQL的工作量。
跨数据库兼容:ORM框架通常可以支持多种数据库,这意味着即使更换数据库引擎,大部分代码也无需改动。
事务管理:ORM内置了对事务的支持,使得复杂的数据库事务处理变得简单。
然而,ORM并非适用于所有场景,特别是在复杂查询和高性能要求下,有时直接使用原生SQL可以获得更好的性能表现。此外,对于那些已经非常熟悉SQL且强调极致性能的项目,可能会倾向于选择混合使用ORM和原生SQL。

2.2 ORM在Python生态中的地位

2.2.1 Python中主要ORM框架比较

在Python世界里,有多款ORM框架,如SQLAlchemy、Django ORM、Peewee、SQLObject等。其中,SQLAlchemy以其强大的灵活性、广泛的数据库支持和丰富的功能集脱颖而出。

Django ORM:与Django框架深度集成,提供了便捷的一体化解决方案,适合快速开发Web应用。
Peewee:轻量级ORM,专注于简洁和易用,适合小型项目和个人开发。
SQLObject:较早的Python ORM框架,特点是简单直观,但相较于SQLAlchemy,在功能上较为有限。

2.2.2 为什么选择SQLAlchemy作为Python ORM代表

SQLAlchemy因其高度灵活性和强大的功能性,被誉为Python事实上的ORM标准。它不仅提供了完整的ORM解决方案,还包含了更加底层和精细的SQL构造接口SQLAlchemy Core,允许开发者在需要时兼顾性能与便利。SQLAlchemy支持几乎所有的关系型数据库,并提供了强大的元数据描述、数据映射、查询构造、事务处理等功能,以及Alembic这样的数据库迁移工具,使之成为企业级项目和大型应用的理想选择。

3、SQLAlchemy基础架构与安装

3.1 SQLAlchemy核心组件

3.1.1 SQLAlchemy Core与ORM的区别与联系

SQLAlchemy由两个主要部分构成:SQLAlchemy Core和SQLAlchemy ORM。SQLAlchemy Core专注于构建和执行SQL表达式,它不依赖于任何特定的对象模型,而是提供一种灵活且强大的方式来构造SQL查询,甚至可以生成针对不同数据库系统的特定SQL。SQLAlchemy ORM则建立在Core之上,引入了完全的对象关系映射能力,允许开发者以面向对象的方式操作数据库。

SQLAlchemy Core更像是一个低级别的SQL构建工具箱,它允许你精确地控制SQL语法,适用于更复杂或者性能敏感的应用场景。
SQLAlchemy ORM则提供了一套完整的对象模型到关系型数据库的映射方案,你可以通过定义Python类来表示数据库表,然后通过操作类的实例来间接操作数据库。
两者之间的联系在于,ORM在内部正是利用了Core的功能来实现与数据库的交互,当你需要高级抽象时使用ORM,而在需要精细控制SQL时,则可以切换到Core模式。

3.1.2 Session与Transaction管理

在SQLAlchemy中,Session是ORM的核心组件,它是对数据库操作的高级封装,负责跟踪对象的修改、新增和删除状态,并在适当的时候将这些更改提交到数据库。一个Session相当于一次对话或一个数据库事务边界,确保在一系列操作间保持一致性和完整性。

from sqlalchemy.orm import sessionmaker
from myapp.models import Base, engine

# 创建全局Session工厂
Session = sessionmaker(bind=engine)

# 创建一个新的Session实例
session = Session()

# 示例:添加新的用户
user = User(name='John Doe')
session.add(user)
session.commit()  # 提交更改,实际执行INSERT SQL

Transaction则是Session内的一个概念,它保证了数据库操作的原子性。在默认情况下,Session会隐式地开启并管理事务,也可以显式地开启和提交事务:

with session.begin():
    user = session.query(User).filter_by(name='John Doe').first()
    user.email = 'john.doe@example.com'
    session.commit()  # 如果在此处发生异常,事务会自动回滚

3.2 安装与配置SQLAlchemy

3.2.1 Python环境中SQLAlchemy的安装方法

在终端或命令提示符下,使用Python的包管理器pip即可轻松安装SQLAlchemy:

pip install sqlalchemy

3.2.2 配置连接不同数据库引擎(如SQLite、MySQL、PostgreSQL)

在SQLAlchemy中,配置数据库连接需要指定数据库URL,通过create_engine函数创建数据库引擎。以下分别展示三种常见数据库引擎的连接配置示例:

SQLite本地数据库

from sqlalchemy import create_engine

# SQLite数据库文件保存在本地
engine = create_engine('sqlite:///mydatabase.db')

MySQL数据库

engine = create_engine('mysql+pymysql://username:password@localhost/mydatabase')

PostgreSQL数据库

engine = create_engine('postgresql://username:password@localhost/mydatabase')

请注意,上述字符串中包含了数据库驱动名、用户名、密码、主机名(通常是localhost)以及数据库名。根据实际情况替换相应信息即可建立到目标数据库的连接。

4、SQLAlchemy ORM实战

4.1 定义数据模型

4.1.1 使用Declarative基类定义实体类

在SQLAlchemy中,Declarative基类是定义数据库表结构的关键。它允许我们将数据库表映射为Python类,类的属性即为表的字段。以下是一个简单的实体类定义示例:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String, nullable=False, unique=True)
    email = Column(String, nullable=False, unique=True)
    password_hash = Column(String)

    # 示例:定义一对多关系(一个用户可以有多个帖子)
    posts = relationship("Post", back_populates="author")

class Post(Base):
    __tablename__ = 'posts'

    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    content = Column(String)
    user_id = Column(Integer, ForeignKey('users.id'))

    # 关联反向引用
    author = relationship("User", back_populates="posts")

在这个例子中,我们定义了两个类User和Post,分别对应数据库中的users和posts表。Column对象用来定义表的字段,relationship用于定义两个表之间的关系。

4.1.2 表关系映射(一对一、一对多、多对多)

在关系型数据库中,表之间常见的关系有一对一、一对多和多对多。在SQLAlchemy中,这些关系都可以通过relationship函数设置。

一对一关系:通过外键约束,一个用户可能有一个唯一的个人档案。

class UserProfile(Base):
    __tablename__ = 'user_profiles'

    id = Column(Integer, primary_key=True)
    bio = Column(String)
    user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)  # 用户ID作为联合主键
    user = relationship("User", backref=backref("profile", uselist=False))

一对多关系:上面的User和Post示例展示了这种关系,一个用户可以有多个帖子。
多对多关系:例如,用户和组的关系,一个用户可以属于多个组,一个组也可以包含多个用户。通过中间表来实现:

from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship

association_table = Table('user_groups', Base.metadata,
                         Column('user_id', Integer, ForeignKey('users.id')),
                         Column('group_id', Integer, ForeignKey('groups.id')))

class Group(Base):
    __tablename__ = 'groups'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    users = relationship("User", secondary=association_table, back_populates="groups")

class User(Base):
    # ...其他字段省略
    groups = relationship("Group", secondary=association_table, back_populates="users")

4.2 CRUD操作与查询表达式

4.2.1 创建(Create)、读取(Read)、更新(Update)、删除(Delete)

创建(Create):通过实例化对象并添加到Session,然后提交事务,实现数据插入。

new_user = User(username='NewUser', email='newuser@example.com', password_hash='...')
session.add(new_user)
session.commit()  # 新用户会被插入到数据库

读取(Read):使用Query API获取数据。

# 获取所有用户
all_users = session.query(User).all()

# 或者查询特定用户
specific_user = session.query(User).filter(User.username == 'NewUser').first()

更新(Update):直接修改对象属性并提交。

specific_user.username = 'UpdatedUsername'
session.commit()  # 用户名会在数据库中更新

删除(Delete):从Session中删除对象。

session.delete(specific_user)
session.commit()  # 特定用户会被从数据库中删除

4.2.2 SQLAlchemy Query API与过滤条件

SQLAlchemy的查询API提供了强大的过滤和排序功能。例如,查找所有年龄大于30岁的用户,并按姓名排序:

from sqlalchemy import desc

users_over_30 = session.query(User).filter(User.age > 30).order_by(desc(User.name)).all()

4.3 SQLAlchemy与原生SQL结合

4.3.1 使用Text或Core表达式执行原生SQL

当需要执行复杂的SQL查询或者利用数据库特有功能时,可以直接使用原生SQL:

from sqlalchemy import text

result = session.execute(text("SELECT * FROM users WHERE username LIKE :username"), {"username": "%example%"})

4.3.2 结合SQLAlchemy ORM进行复杂查询

尽管ORM提供了很高的抽象层次,但仍然可以与原生SQL相结合以处理复杂的查询需求。例如,可以将原生SQL查询结果映射到ORM实体:

from sqlalchemy.sql import select

stmt = select(User).where(User.username.in_(["Admin", "Moderator"]))
admin_and_mods = session.execute(stmt).scalars().all()

5、SQLAlchemy进阶特性

5.1 数据迁移与Schema同步

5.1.1 Alembic用于数据库版本控制

在项目开发过程中,数据库结构往往随着业务需求的迭代而不断演变。SQLAlchemy通过集成Alembic工具,实现了数据库迁移和版本控制。Alembic允许开发者以脚本化的形式定义数据库 schema 的变化,从而使得团队成员能够协同管理数据库架构,确保在不同开发阶段都能得到正确的数据库结构。

使用Alembic,你可以创建“迁移脚本”,这些脚本描述了从一个数据库版本到另一个版本的变化。例如,当你增加了一个新的表或修改了现有表的字段时,Alembic可以帮助你生成这些变化对应的迁移脚本,并在生产环境中安全地应用这些变更。

# Alembic迁移脚本示例
def upgrade():
    op.create_table(
        'new_table',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(length=100), nullable=False),
        sa.PrimaryKeyConstraint('id'),
    )

def downgrade():
    op.drop_table('new_table')

5.1.2 自动创建和修改表结构

SQLAlchemy的Declarative API允许你在定义Python类时就同步创建数据库表结构。当你运行程序时,若数据库中尚不存在对应的表结构,SQLAlchemy可以根据定义的实体类自动生成表。在类定义中,通过继承declarative_base()并使用__tablename__、Column等属性,可以轻松地创建和更新数据库表结构。

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String, nullable=False, unique=True)
    email = Column(String, nullable=False, unique=True)

# 在初始化时,如果数据库中没有users表,SQLAlchemy会自动创建
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)

5.2 性能优化与最佳实践

5.2.1 查询缓存与批量操作

为了提高数据查询和处理的性能,SQLAlchemy支持查询缓存和批量操作。例如,可以通过启用缓存策略来避免频繁查询同一数据,减少数据库负载。而对于大批量的数据插入或更新,可以使用bulk_save_objects()或bulk_update_mappings()等方法一次性执行多个操作,降低网络开销和数据库事务次数。

from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine, Integer, String

# 创建Session工厂
Session = sessionmaker(bind=create_engine('sqlite:///example.db'))

# 假设已有一些用户数据准备插入
users_to_insert = [
    {'id': 1, 'username': 'user1', 'email': 'user1@example.com'},
    {'id': 2, 'username': 'user2', 'email': 'user2@example.com'},
    # 更多用户数据...
]

# 批量插入用户
session = Session()
session.bulk_insert_mappings(User, users_to_insert)
session.commit()

5.2.2 避免N+1查询问题及其他性能陷阱

N+1查询问题是指在循环中执行多次单个查询操作导致的性能瓶颈。为了解决这个问题,SQLAlchemy提供了懒加载(lazy loading)、预加载(eager loading)和立即加载(joined loading)等关系加载策略。合理使用这些策略可以显著提高数据获取效率。

例如,避免N+1查询的一个有效方法是使用joinedload()或subqueryload()来预加载关联对象:

# 预加载所有用户的所有帖子
users = session.query(User).options(joinedload(User.posts)).all()
for user in users:
    print(user.username, [post.title for post in user.posts])

总之,通过熟练掌握SQLAlchemy的各种进阶特性,开发者不仅能更好地管理数据库结构,还能有效地优化数据操作性能,提升整个项目的稳定性和响应速度。随着技术的演进,SQLAlchemy不断改进和增强了其功能,使得在面对复杂数据库操作时,Python开发者能拥有更加强大和高效的工具链。

6、案例分析与项目部署

6.1 实际项目中SQLAlchemy的应用案例

6.1.1 小型Web应用程序示例

设想一个简单的博客系统,我们可以使用SQLAlchemy与Flask框架结合,实现用户账户管理、文章发布和评论功能。首先,定义数据模型:

from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    user = db.relationship('User', backref=db.backref('posts', lazy=True))

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
    user = db.relationship('User', backref=db.backref('comments', lazy=True))
    post = db.relationship('Post', backref=db.backref('comments', lazy=True))

接下来,借助SQLAlchemy的CRUD操作,我们可以轻松实现用户的注册、登录、文章的增删改查以及评论功能:

@app.route('/register', methods=['POST'])
def register():
    # 创建新用户并添加到数据库
    new_user = User(username=request.form['username'], email=request.form['email'])
    db.session.add(new_user)
    db.session.commit()

@app.route('/posts', methods=['GET', 'POST'])
def manage_posts():
    if request.method == 'POST':
        # 创建新文章并关联到当前登录用户
        current_user = get_current_user()  # 通过某种方式获取当前登录用户
        new_post = Post(title=request.form['title'], content=request.form['content'], user=current_user)
        db.session.add(new_post)
        db.session.commit()
    else:
        # 查询所有文章
        posts = Post.query.all()
        return render_template('posts.html', posts=posts)

# 其他路由和操作类似,此处省略...

6.1.2 大型企业级应用中的SQLAlchemy架构设计

在大型企业级应用中,SQLAlchemy常常配合微服务架构使用,为各个服务提供独立且高效的数据库操作层。例如,在电商系统中,订单服务、商品服务、用户服务各自维护一套数据模型,通过SQLAlchemy实现复杂查询和事务处理。

例如,订单服务可能涉及多个数据库表的联动操作,如订单表、订单详情表、库存表等。通过SQLAlchemy的Session和Transaction管理,可以确保事务的一致性,同时利用其强大的查询API和表达式语言实现复杂的业务逻辑:

from sqlalchemy.exc import IntegrityError

try:
    with db.session.begin():
        order = Order(...args)
        order_details = [OrderDetail(...args) for item in shopping_cart]

        db.session.add(order)
        db.session.add_all(order_details)

        # 扣减库存
        for detail in order_details:
            product = Product.query.get(detail.product_id)
            product.stock -= detail.quantity
            db.session.add(product)

        db.session.commit()
except IntegrityError:
    db.session.rollback()

6.2 SQLAlchemy在微服务与容器化环境中的部署

6.2.1 集成至Django、Flask等Web框架

在Django或Flask项目中,只需通过相关的插件(如flask-sqlalchemy、django-sqlalchemy)即可轻松整合SQLAlchemy。在Docker或Kubernetes等容器化环境中,通过环境变量传递数据库连接字符串,使服务能够在不同的部署环境中灵活配置数据库连接。

例如,在Dockerfile中:

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

# 设置环境变量
ENV DATABASE_URL=postgres://user:password@host:port/database

CMD ["gunicorn", "-b", "0.0.0.0:8000", "app.wsgi"]

6.2.2 Kubernetes等容器环境下数据库连接池配置

在Kubernetes集群中部署应用时,可以利用ConfigMap或Secret来存储数据库凭据,然后注入到Pod中作为环境变量。此外,SQLAlchemy本身支持连接池配置,可以大大提高数据库连接的复用率,降低系统开销:

from sqlalchemy.pool import QueuePool

# 配置连接池大小和超时时间
engine = create_engine(
    os.environ.get('DATABASE_URL'),
    pool_size=5,  # 最大活动连接数
    max_overflow=10,  # 超过连接池大小时的最大连接数
    pool_timeout=30,  # 连接池耗尽时等待连接的秒数
    poolclass=QueuePool
)

通过合理的配置和部署策略,SQLAlchemy在企业级项目中展现出卓越的性能和稳定性,为开发者提供了强大而灵活的数据管理能力。无论是小型Web应用还是大型分布式系统,SQLAlchemy都是值得信赖的ORM工具。


http://www.kler.cn/a/394838.html

相关文章:

  • NIO(New IO)和BIO(Blocking IO)的区别
  • 条款14 如果函数不抛出异常请使用noexcept
  • QT-【常用容器类】-QList类 QLinkedList类
  • QT笔记- QTreeView + QFileSystemModel 当前位置的保存与恢复 #选中 #保存当前索引
  • 视频监控平台:Liveweb视频汇聚融合平台智慧安防视频监控应用方案
  • 我的创作纪念日(五年)
  • 国产系统给在线的Word文件创建表格
  • unity3d————接口基础知识点
  • STM32 创建一个工程文件(寄存器、标准库)
  • Android在使用RecycylerView开发中,设置item单选效果,并且设置默认选中第一个
  • fastadmin多个表crud连表操作步骤
  • 《鸿蒙生态:开发者的机遇与挑战》
  • ddl/dml/dcl
  • 【计算机网络】【传输层】【习题】
  • 子集选择——基于R语言实现(最优子集选择法、逐步回归法、Lasso回归法、交叉验证法)
  • 通过vmware虚拟机安装和调试编译好的 ReactOS
  • 使用etl工具kettle的日常踩坑梳理之一、从mysql中导出数据
  • 【springboot使用sqlite数据库】Java后台同时使用mysql、sqlite
  • 零基础想学习 Web 安全,如何入门?
  • 深入探索 React Hooks:原理、用法与性能优化全解
  • nVisual自定义工单内容
  • 力扣第48题“旋转图像”
  • 计算机网络-2.1物理层
  • C++全局构造和初始化
  • 算法训练(leetcode)二刷第二十五天 | *134. 加油站、*135. 分发糖果、860. 柠檬水找零、*406. 根据身高重建队列
  • 24/11/14 算法笔记 EM算法期望最大化算法