Python数据类型

collections

array

headq

bisect

weakref

datetime

calender

types

copy

pprint

reprlib

enum

Python概述

综述

  • 语言的具体实现可能发生改变、其他实现可能使用不同方式
  • 在语言的参考文档中加入过多细节实现很危险

Python实现

python只是一种语言,其具体解释器实现有很多种

  • CPython:C语言实现,最原始版本

    • 通常就被称为Python,其他实现区分时才强调为CPython
    • 新语言特性通常较早出现
  • Jython:Java实现

    • 将Python代码编译为Java字节码
    • 可以左线Java应用的脚本语言、创建需要Java类库支持的 应用
    • 在JVM上运行
  • Python for .NET:实际上使用CPython实现,但是属于.NET 托管应用,可以引入.NET类库

  • IronPython:.NET实现

    • 生成IL的完全Python实现,将Python代码直接编译为.NET 程序集
  • PyPy:RPython(Python语言子集)实现

    • JIT编译器,执行效率高于CPython
    • 非栈式支持
    • 允许方便修改解释器,鼓励对语言本身进行实验
  • CPython是解释器实现版本,cython是将Python代码翻译为 C插件的项目/包

Notation说明

标注:词法、句法解析的描述使用修改过的BNF语法标注

1
2
name ::= lc_letter(lc_letter | "_")*
lc_letter ::= "a"..."z"
  • ::=:声明规则,左侧为规则名称
  • |:分隔可选项
  • *:前一项的零次、多次重复
  • +:前一项的一次、多次重复
  • []:括起内容可选,即出现零次、一次
  • ():分组
  • "":固定字符串包含在引号内
  • :空格仅用于分隔token
  • ...:三个点分割的本义字符表示在指定区间范围内的任意 单个字符
  • <>:对所定义符号的非常描述,在必要时用于说明“控制字符” 意图
  • 每条规则通常为一行,多个可选项规则可用|为界分为多行
  • 词法定义:作用域输入源中的单独字符
  • 句法定义:作用于词法分析生成的token stream

约定

  • 实例方法:定义在类命名空间中、未因访问而绑定函数
  • 绑定方法:已绑定实例方法
  • 静态方法
  • 类方法
  • [类]实例:类实例化所得对象
  • 对象:泛指所有python对象,包括类、实例

Global Intepretor Lock

全局内存锁:GIL,任何python字节码执行前必须获得的解释器锁

  • 在任何时刻,只能有一个线程处于工作状态
  • 避免多个线程同时操作变量导致内存泄漏、错误释放

优势

  • GIL实现简单,只需要管理一把解释器锁就能保证线程内存安全

    • 当然GIL只能保证引用计数正确,避免由此导致内存问题
    • 还需要原子操作、对象锁避免并发更新问题
  • GIL单线程情况下性能更好、稳定,若通过给所有对象引用计数 加锁来实现线程安全

    • 容易出现死锁
    • 性能下降很多
  • 方便兼容C遗留库,这也是python得以发展的原因

    • 很多python需要的C库扩展要求线程安全的内存管理

影响

  • Python线程是真正的操作系统线程

    • 在准备好之后必须获得一把共享锁才能运行
    • 每个线程都会在执行一定机器指令和后切换到无锁状态, 暂停运行
    • 事实上程序在开始时已经在运行“主线程”
    • 解释器检查线程切换频率sys.getcheckinterval()
  • Python线程无法在多核CPU间分配,对CPU-Bound程序基本没有 提升效果,对于IO-Bound的程序性能仍然有巨大帮助

解决方案

  • 多进程

    • 进程分支:os.fork
    • 派生进程:multiprocessing.Processconcurrent.futures
  • C语言库封装线程:ctypescython

    • C扩展形式实现任务线程可在python虚拟机作用域外运行 可以并行运行任意数量线程
    • 在运行时释放GIL、结束后继续运行python代码时重新获取 GIL,真正实现独立运行
  • 使用其他版本Python解释器:只有原始Python版本CPython使用 GIL实现

    • Jython
    • IronPython
    • PyPy

Python最高层级组件

完整的Python程序

  • 完整的python程序会在最小初始化环境中被执行

    • 所有内置、标准模块均可用,但均处于未初始化状态
    • 只有sysbuiltins__main__已经初始化
    • __main__模块为完整程序的执行提供局部、全局命名空间
  • 完整程序可通过三种形式传递给解释器

    • -c命令行选项传递字符串
    • 文件作为第一个命令行参数
    • 标准输入
    • 若文件、标准输入是tty设备,解释器进入交互模式,否则 将文件当作完整程序执行
  • 解释器也可以通过交互模式被发起调用

    • 每次读取执行一条语句,语句会在__main__命名空间中 被执行
    • 初始环境同完整程序

输入类型

文件输入

文件输入:从非交互式文件读取的输入,具有相同形式

1
file_input ::= (NEWLINE|statement)*
  • 适合以下几种情况

    • 解析完整的python程序(从文件、字符串)
    • 解析模块
    • 解析传递给exec()函数的字符串

交互式输入

交互式输入:从tty设备读取输入

1
interactive_input ::= [stmt_list] NEWLINE | compound_stmt NEWLINE
  • 注意
    • 交互模式中(最高层级)复合语句后必须带有空行,帮助 解释器确定输入的结束

表达式输入

表达式输入

1
eval_input ::= expression_list NEWLINE*
  • eval被用于表达式输入
  • 忽略开头空白

Python包、模块

综述

导入方式

importing操作可以使得模块能够访问其他模块中代码

  • import:结合了以下两个操作,发起导入调机制最常用方式

    • 搜索指定名称模块:对__import__()带有适当参数调用
    • 将搜索结果绑定到当前作用域中名称:__import__返回值 被用于执行名称绑定操作
  • __import__():只执行模块搜索、找到模块后创建module

    • 可能产生某些副作用
      • 导入父包
      • 更新各种缓存:sys.modules
  • importlib.import_module()

    • 可能会选择绕过__import__,使用其自己的解决方案实现 导入机制
    • 用于为动态模块导入提供支持
    • importlib模块参见标准库

子模块

  • 任意机制加载子模块时,父模块命名空间中会添加对子模块对象 的绑定

Packages

  • python只有一种模块对象类型:所有模块都属于该类型,C、 Python语言实现均是

  • 包:为帮助组织模块、并提供名称层次结构引入

    • 可将包视为文件系统中目录、模块视为目录中文件, 但包、模块不是必须来自文件系统
    • 类似文件系统,包通过层次结构进行组织:包内包括模块、 子包
  • 所有包都是模块,但并非所有模块都是包

    • 包是一种特殊的模块
    • 特别的,任何具有__path__属性的模块都被当作包
  • 所有模块都有自己的名字

    • 子包名与其父包名以.分隔,同python标准属性访问语法

Regular Packages

正规包:通常以包含__init__.py文件的目录形式出现

  • __init__.py文件可以包含和其他模块中包含python模块相似 的代码

  • 正规包被导入时

    • __init__.py文件会隐式被执行,其中定义对象被绑定到 该包命名空间中名称
    • python会为模块添加额外属性

Namespace Packages

命名空间包:由多个部分构成,每个部分为父包增加一个子包

  • 包各部分可以物理不相邻,不一定直接对应到文件系统对象, 可能是无实体表示的虚拟模块

    • 可能处于文件系统不同位置
    • 可能处于zip文件、网络上,或在导入期间其他可搜索位置
  • __path__属性不是普通列表,而是定制的可迭代类型

    • 若父包、或最高层级包sys.path路径发生改变,对象会 在包内的下次导入尝试时,自动执行新的对包部分的搜索
  • 命名空间包中没有__init__.py文件

    • 毕竟可能有多个父目录提供包不同部分,彼此物理不相邻
    • python会在包、子包导入时为其创建命名空间包

导入相关模块属性

  • 以下属性在加载时被设置,参见 cs_python/py3ref/import_system
  • __name__:模块完整限定名称,唯一标识模块

  • __loader__:导入系统加载模块时使用的加载器对象

    • 主要用于内省
    • 也可用于额外加载器专用功能
  • __package__:取代__name__用于主模块计算显式相对 导入

    • 模块为包:应设置为__name__
    • 模块非包:最高层级模块应设为空字符串,否则为父包名
    • 预期同__spec__.parent值相同,未定义时,以 __spec__.parent作为回退项
  • python <PYSCIRPT>直接执行脚本时__name__被设置为 __main____package__设置为None,此时导入器无法 解释相对导入中.,相对导入报错
  • python -m <PYSCIRPT>则会按模块逻辑设置__name____package__,相对导入可以正常执行

__spec__

__spec__:导入模块时要使用的模块规格说明

  • __spec__正确设置将同时作用于解释器启动期间 初始化的模块
  • __main__某些情况下被设置为None

__path__

  • 具有该属性模块即为包:包模块必须设置__path__属性, 非包模块不应设置

  • 在导入子包期间被使用,在导入机制内部功能同sys.path, 即用于提供模块搜索位置列表

    • 但受到更多限制,其必须为字符串组成可迭代对象,但若其 没有进一步用处可以设置为空
    • 适用作用于sys.path的规则
    • sys.path_hooks会在遍历包的__path__时被查询
  • 可在包的__init__.py中设置、更改

    • PEP420引入之后,命名空间包不再需要提供仅包含操作 __path__代码的__init__.py文件,导入机制会自动为 命名空间包正确设置__path__
    • 在之前其为实现命名空间包的典型方式

__repr__

  • 若模块具有__spec__,导入机制将尝试使用其中规格信息生成 repr

    • name
    • loader
    • origin
    • has_location
  • 若模块具有__file__属性,将被用作repr的一部分

  • 否则若模块具有__loader__属性且非None,则加载器repr 将被用作模块repr的一部分

  • 其他情况下,仅在repr中适用模块的__name__

  • 可以在模块规则说明中显式控制模块对象repr

__file__/__cached__

  • __file__:模块对应的被加载文件的路径名
  • __cached__:编译版本代码(字节码文件)路径
  • __file__为可选项,须为字符串

    • 可以在其无语法意义时不设置
    • 对从共享库动态加载的扩展模块,应为共享库文件路径名
  • __cached__

    • 不要求编译文件已经存在,可以表示应该存放编译文件 的位置
    • 不要求__file__已经设置
      • 有时加载器可以从缓存加载模块但是无法从文件加载
      • 加载静态链接至解释器内部的C模块
  • .pyc文件加载缓存字节码前会检查其是否最新

    • 默认通过比较缓存文件中保存的源文件修改时间戳实现
    • 也支持基于哈希的缓冲文件,此时.pyc文件中保存源文件 哈希值
      • 检查型:求源文件哈希值再和缓存文件中哈希值比较
      • 非检查型:只要缓存文件存在就直接认为缓存文件有效
    • --check-hash-based-pycs命名行选项设置基于哈希的 .pyc文件有效性

执行相关模块属性

  • __doc__:模块文档字符串
  • __annotaion__:包含变量标注的字典
    • 在模块体执行时获取
  • __dict__:以字典对象表示的模块命名空间
  • CPython:由于CPython清理模块字典的设定,模块离开作用域时 模块字典将被清理,即使字典还有活动引用,可以复制该字典、 保持模块状态以直接使用其字典

sys.modules模块缓存

sys.modules映射:缓存之前导入的所有模块(包括中间路径) (即导入子模块会注册父模块条目)

  • 其中每个键值对就是限定名称、模块对象

  • 在其中查找模块名称

    • 若存在需要导入模块,则导入完成
    • 若名称对应值为Noneraise ModuleNotFoundError
    • 若找不到指定模块名称,python将继续搜索
  • 映射可写,可删除其中键值对

    • 不一定破坏关联模块,因为其他模块可能保留对其引用
    • 但是会使命名模块缓存条目无效,导致下次导入时重新 搜索命名模块,得到两个不同的两个模块对象
    • importlib.reload将重用相同模块对象,通过重新运行 模块代码重新初始化模块内容

Finders And Loaders

  • Finders:查找器,确定能否使用所知策略找到指定名称模块
  • Loaders:加载器,加载找到的指定模块
  • Importer:导入器,同时实现两种接口的对象,在确定能加载 所需模块时会返回自身
  • 导入机制通过import hooks实现扩展

    • 可以加入新的查找器以扩展模块搜索范围、作用域
  • 工作流程:在sys.modules缓存中无法找到指定名称模块时

    • 查找器若能找到指定名称模块,返回模块规格说明spec
    • 加载器将利用查找器返回的模块规格说明加载模块

Import Path

导入路径:文件系统路径、zip文件等path term组成的位置列表

  • 其中元素不局限于文件系统位置,可扩展为字符串指定的任意 可定位资源

    • URL指定资源
    • 数据库查询
  • 位置条目来源

    • 通常为sys.path
    • 对次级包可能来自上级包的__path__属性
  • 其中每个路径条目指定一个用于搜索模块的位置

    • path based finder将在其中查找导入目标

sys.path

sys.path:模块、包搜索位置的字符串列表

  • 初始化自PYTHONPATH环境变量、特定安装和实现的默认设置、 执行脚本目录(或当前目录)

  • 其中条目可以指定文件系统中目录、zip文件、可用于搜索模块 的潜在位置

  • 只能出现字符串、字节串,其他数据类型被忽略

    • 字节串条目使用的编码由导入路径钩子、 path entry finder确定
  • 所以可以修改sys.path值定制导入路径,CPython实现参见 cs_python/py3ref/import_system

sys.path_import_cache

sys.path_importer_cache:存放路径条目到路径条目查找器映射 的缓存

  • 减少查找路径条目对应路径条目查找器的消耗,对特定路径条目 查找对应路径条目查找只需进行一次

  • 可从中移除缓存条目,以强制基于路径查找器执行路径条目搜索

Import Hooks

  • meta hooks:元[路径]钩子

    • 导入过程开始时被调用,此时仅sys.modules缓存查找 发生,其他导入过程未发生
    • 所以允许元钩子重载sys.path过程、冻结模块甚至内置 模块
    • 元钩子即导入器/元路径查找器
    • sys.meta_path为元路径查找器列表,可在其中注册定制 元钩子
  • path[ entry] hooks:导入路径钩子

    • sys.pathpackage.__path__处理的一部分
    • 基于路径的查找器调用其处理路径条目,以获取路径条目 查找器
    • 导入路径钩子返回路径条目查找器
    • sys.path_hooks为导入路径钩子列表,可在其中注册 定制导入路径钩子

默认元路径查找器/导入器

python默认实现sys.meta_path有以下导入器(元路径查找器)

  • BuiltinImporter:定位、导入内置模块
  • FrozenImporter:定位、导入冻结模块
  • PathFinder:定位、导入来自import path中模块
  • 尝试导入模块时,内置模块、冻结模块导入器优先级较高,所以 解释器首先搜索内置模块

Finder

  • 指定名称模块在sys.modules找不到时,python继续搜索 sys.meta_path,按顺序调用其中元路径查找器

  • sys.meta_path处理到列表末尾仍未返回说明对象,则 raise ModuleNotFoundError

  • 导入过程中引发的任何异常直接向上传播,并放弃导入过程
  • 对非最高层级模块的导入请求可能会多次遍历元路径

Meta Path Finders

元路径查找器:

  • 元路径查找器可使用任何策略确定其是否能处理给定名称模块

    • 若知道如何处理指定名称的模块,将返回模块规格说明
    • 否则返回None
  • 模块规格协议:元路径查找器应实现find_spec()方法

    • 接受名称、导入路径、目标模块作为参数
    • 返回模块规格说明

Spec

  • 模块规格[说明]:基于每个模块封装的模块导入相关信息

    • 模块规格中大部分信息对所有模块是公用的
    • 模块规格说明作为模块对象的__spec__属性对外公开
  • 用途

    • 允许状态在导入系统各组件间传递,如:查询器和加载器
    • 允许导入机制执行加载的样板操作,否则该由加载器负责

find_spec

1
2
def finder.find_spec(fullname, path=None, target=None):
pass
  • fullname:被导入模块的完整限定名称
  • path:供模块搜索使用的路径条目
    • 对最高层级模块应为None
    • 对子模块、子包应为父包__path__属性值,若 相应__path__属性无法访问将 raise ModuleNotFoundError
  • target:将被作为稍后加载目标的现有模块对象
    • 导入系统仅在重加载期间传入目标模块
  • 导入器的find_spec()返回模块规格说明中加载器为self
  • 有些元路径查找器仅支持顶级导入,path参数不为None时 总返回None

Loaders

  • 模块规格说明被找到时,导入机制将在加载该模块时使用
    • 其中包含的加载器将被使用,若存在

加载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module = None

if spec.loader is not None and hasattr(spec.loader, 'create_module'):
# 模块说明中包含加载器,使用加载器创建模块
module = spec.loader.create_module(spec)

if module is None:
# 否则创建空模块
module = types.ModuleType(spec.name)

# 设置模块导入相关属性
_init_module_attrs(spec, module)

if spec.loader is None:
# 模块说明中不包含加载器
# 检查模块是否为为命名空间包
if spec.submodule_search_locations is not None:
# 设置`sys.modules`
sys.modules[spec.name] = module
else:
raise ImportError
elif not hasattr(spec.loader, "exec_module"):
# 向下兼容现有`load_module`
module = spec.loader.load_module(spec.name)
else:
sys.modules[spec.name] = module
try:
# 模块执行
spec.loader.exec_module(module)
except BaseException:
try:
# 加载模块失败则从`sys.modules`中移除
del sys.modules[spec.name]
except KeyError:
pass
raise
return sys.modules[spec.name]
  • 创建模块对象
  • 设置模块导入相关属性:在执行模块代码前设置
  • sys.modules注册模块
  • 模块执行:模块导入关键,填充模块命名空间

create_module创建模块对象

  • 模块加载器可以选择通过实现create_module方法在加载 期间创建模块对象

    • 其应接受模块规格说明作为参数
  • 否则导入机制使用types.ModuleType自行创建模块对象

sys.modules注册模块

  • 在加载器执行代码前注册,避免模块代码导入自身导致无限 递归、多次加载
  • 若模块为命名空间包,直接注册空模块对象

exec_module模块执行

  • 导入机制调用importlib.abc.Loader.exec_module()方法执行 模块对象

    • CPython:exec_module不定返回传入模块,其返回值将被 忽略
      • importlib避免直接使用返回值,而是通过在 sys.modules中查找模块名称获取模块对象
      • 可能会间接导致被导入模块可能在sys.modules中 替换其自身
  • 加载器应该该满足

    • 若模块是python模块(非内置、非动态加载),加载器应该 在模块全局命名空间module.__dict__中执行模块代码

    • 若加载器无法执行指定模块,则应raise ImportError, 在exec_module期间引发的任何其他异常同样被传播

  • 加载失败时作为附带影响被成功加载的模块仍然保留

    • 重新加载模块会保留加载失败模块(最近成功版本)

Path Based FinderPathFinder

基于路径的查找器:在特定path entry中查找、加载指定的python 模块、包

  • 基于路径查找器只是遍历import path中的路径条目,将其 关联至处理特定类型路径的path entry finder

  • 默认路径条目查找器集合实现了在文件系统中查找模块的所有 语义,可以处理多种文件类型

    • python源码.py
    • python字节码.pyc
    • 共享库.so
    • zip包装的上述文件类型(需要zipimport模块支持)
  • 作为元路径查找器

    • 实现有find_spec协议
    • 并提供额外的钩子、协议以便能扩展、定制可搜索路径条目 的类型,定制模块从import path的查找、加载

流程

导入机制调用基于路径的查找器的find_spec()迭代搜索 import path的路径条目,查找对应路径条目查找器

  • 先在sys.path_impporter_cache缓存中查找对应路径条目 查找器

  • 若没有在缓存中找到,则迭代调用sys.path_hooksPath Entry Hook

  • 迭代结束后若没有返回路径条目查找器,则

    • sys.path_importer_cache对应值为None
    • 返回None,表示此元路径查找器无法找到该模块

当前目录

对空字符串表示的当前工作目录同sys.path中其他条目处理方式 有所不同

  • 若当前工作目录不存在,则sys.path_importer_cache 中不存放任何值

  • 模块查找回对当前工作目录进行全新查找

  • sys.path_importer_cache使用、 importlib.machinery.PathFinder.find_spec()返回路径将是 实际当前工作目录而非空字符串

Path Entry Hook

路径条目钩子:根据路径条目查找对应路径条目查找器的可调用对象

  • 参数:字符串、字节串,表示要搜索的目录条目

    • 字节串的编码由钩子自行决定
    • 若钩子无法解码参数,应raise ImportError
  • 路径条目钩子返回值

    • 可处理路径条目的路径条目查找器
    • raise ImportError:表示钩子无法找到与路径条目对应 路径条目查找器
      • 该异常会被忽略,并继续对import path迭代

Path Entry FinderPathEntryFinder

路径条目查找器:

  • 元路径查找器作用于导入过程的开始,遍历sys.meta_path
  • 路径条目查找器某种意义上是基于路径查找器的实现细节

find_spec

1
2
def PathEntryFinder.find_spec(fullname, target=None):
pass
  • 路径条目查找器协议:目录条目查找器需实现find_spec方法

    • 以支持模块、已初始化包的导入
    • 给命名空间包提供组成部分
  • 参数

    • fullname:要导入模块的完整限定名称
    • target:目标模块
  • 返回值:完全填充好的模块规格说明

    • 模块规格说明总是包含加载器集合
    • 但命名空间包的规格说明中loader会被设置为None, 并将submodule_search_locations设置为包含该部分的 列表,以告诉导入机制该规格说明为命名空间包的portion
  • Portion:构成命名空间包的单个目录内文件集合
  • 替代旧式find_loader()find_module()方法

替换标准导入系统

  • 替换sys.meta_path为自定义元路径钩子

    • 替换整个导入系统最可靠机制
  • 替换内置__import__()函数

    • 仅改变导入语句行为而不影响访问导入系统其他接口
    • 可以在某个模块层级替换,只改变某块内部导入语句行为
  • 替换find_spec(),引发ModuleNotFoundError

    • 选择性的防止在元路径钩子导入某些模块

__main__

  • __main__模块是在解释器启动时直接初始化,类似sysbuiltins,但是不被归类为内置模块,因为其初始化的方式 取决于启动解释器的旗标(命令行参数)

__spec__

根据__main__被初始化的方式,__main__.__spec__被设置为 None或相应值

  • -m选项启动:以脚本方式执行模块

    • 此时__spec__被设置为相应模块、包规格说明

    • __spec__会在__main__模块作为执行某个目录、zip 文件、其他sys.path条目的一部分加载时被填充

    • 此时__main__对应可导入模块和__main__被视为不同 模块

  • 其余情况

    • __spec__被设置为None

    • 因为用于填充__main__的代码不直接与可导入模块相对应

      • 交互型提示
      • -c选项
      • 从stdin运行
      • 从源码、字节码文件运行
  • -m执行模块时sys.path首个值为空字符串,而直接执行脚本 时首个值为脚本所在目录

Import[ Search] Path定制

动态增加路径

1
2
3
import sys
sys.path.insert(1, /path/to/fold/contains/module)
# 临时生效,对不经常使用的模块较好

修改PYTHONPATH环境变量

1
2
 # .bashrc
export PYTHONPATH=$PYTHONPATH:/path/to/fold/contains/module
  • 对许多程序都使用的模块可以采取此方式
  • 会改变所有Python应用的搜索路径

增加.pth文件

/path/to/python/site-packages(或其他查找路径目录)下 添加.pth配置文件,内容为需要添加的路径

1
2
 # extras.pth
/path/to/fold/contains/module
  • 简单、推荐
  • python在遍历已知库文件目录过程中,遇到.pth文件会将其中 路径加入sys.path

表达式

Atoms

原子:表达式最基本元素

  • 最简单原子

    • 标识符
    • 字面值
  • 以圆括号、方括号、花括号包括的形式在语法上也被归为原子

1
2
atom      ::=  identifier | literal | enclosure
enclosure ::= parenth_form | list_display | dict_display | set_display | generator_expression | yield_atom

Indentifiers/Names

名称:作为原子出现的标识符

  • 名称被绑定到对象时:对原子求值将返回相应对象
  • 名称未绑定时:对原子求值将raise NameError

Private Name Mangling

  • 类的私有名称:文本形式出现在类定义中以两个、更多下划线 开头且不以两个、更多下划线结尾的标识符

私有名称转换:在为私有名称生成代码前,其被转换为更长形式

  • 转换方式:在名称前插入类名、下划线

    • 若转换后名称太长(超过255字符),某些实现中可能 发生截断
  • 转换独立于标识符使用的句法

  • 若类名仅由下划线组成,则不会进行转换

Literals

字面值:

1
literal ::=  stringliteral | bytesliteral | integer | floatnumber | imagnumber
  • 对字面值求值将返回改值对应类型的对象

    • 对浮点数、复数,值可能为近似值
  • 所有字面值都对应不可变数据类型

    • 所以对象标识的重要性不如其实际值
  • 多次对具有相同值的字面值求值,可能得到相同对象、或具有 相同值的不同对象

    • 元组是不可变对象,适用字面值规则:两次出现的空元组 产生对象可能相同、也可能不同

      todo

Parenthesized Forms

带括号形式:包含在()可选表达式列表

1
parenth_form ::= "(" [starred_expression] ")"
  • 带圆括号表达式列表将返回表达式所产生的任何东西

    • 内容为空的圆括号返回空元组对象
    • 列表包含至少一个逗号,产生元组
    • 否则,产生表达式列表对应的单一表达式
  • 元组不是由圆括号构建,实际是,逗号操作符起作用

    • 空元组是例外,此时圆括号必须,因为表达式中不带圆括号 的“空”会导致歧义

List/Set/Dict/Generator-Tuple

  • display:显式列出容器内容
  • comprehension:推导式,通过循环、筛选指令计算
1
2
3
4
comprehension ::=  expression comp_for
comp_for ::= ["async"] "for" target_list "in" or_test [comp_iter]
comp_iter ::= comp_for | comp_if
comp_if ::= "if" expression_nocond [comp_iter]
  • 推导式结构:单独表达式后加至少一个for子句以及零个、或 多个forif子句

    • forif子句视为代码块,按从左到右顺序嵌套 (类似for循环嵌套)
    • 每次到达最内层代码块时对表达式求值以产生元素
  • 除最左边for子句中可迭代表达式,推导式在另一个隐式嵌套 作用域内执行

    • 确保赋给目标列表的名称不会“泄露”到外层作用域
    • 最左边for子句中可迭代表达式直接在外层作用域中被 求值,然后作为参数传递给隐式嵌套作用域
    • 后续for子句、最左侧for子句中任何筛选条件不能在 外层作用域中被求值,因为其可能依赖于从最左侧可迭代 对象中获得的值
  • 为确保推导式总能得到类型正确的容器,隐式嵌套作用域内禁止 使用yieldyield from表达式,因为其会对外层作用域 造成附加影响

  • 若推导式包含async for子句、await表达式,则为异步 推导式

List Displays

列表显式:用[]方括号括起的、可能为空的表达式系列

1
list_display ::= "[" [starred_list | comprehesion] "]"
  • 列表显式会产生新的列表对象,内容通过表达式、推导式指定
  • 提供逗号分隔的表达式时:元素从左至右求值,按此顺序放入 列表对象
  • 提供推导式时:根据推导式产生结果元素进行构建

Set Displays

集合显式:用{}花括号标明,与字典区别在于没有冒号分隔键值

1
set_display ::=  "{" (starred_list | comprehension) "}"
  • 集合显式产生可变集合对象,内容通过表达式、推导式指定
  • 提供逗号分隔的表达式时:元素从左至右求值,按此顺序放入 列表对象
  • 提供推导式时:根据推导式产生结果元素进行构建
  • 空集合不能使用{}构建,此构建的是空字典

Dict Displays

字典显式:用{}花括号括起来的、可能为空的键值对

1
2
3
4
dict_display       ::=  "{" [key_datum_list | dict_comprehension] "}"
key_datum_list ::= key_datum ("," key_datum)* [","]
key_datum ::= expression ":" expression | "**" or_expr
dict_comprehension ::= expression ":" expression comp_for
  • **:映射拆包,操作数必须是映射
  • 字典显式产生新的字典对象

  • 提供,分隔键值对序列

    • 从左至右被求值以定义字典条目
    • 可多次指定相同键,最终值由最后给出键值对决定
  • 提供字典推导式

    • 以冒号分隔的两个表达式,后者带上标准forif子句
    • 作为结果键值对按产生顺序被加入新字典
  • 键类型需要为hashable

Generator Expression

生成器表达式:用圆括号括起来的紧凑形式生成器(迭代器)标注

1
generator_expression ::=  "(" expression comp_for ")"
  • 生成器表达式会产生新的生成器(迭代器)对象

    • 句法同推导式,但使用圆括号括起
    • 圆括号在只附带一个参数(省略expression)的调用中 可以被省略
  • 生成器表达式中使用的变量在生成器对象调用__next__方法 时以惰性方式被求值,同普通生成器

    • 最左侧for子句内可迭代对象会被立即求值,则其造成的 错误会在生成器表达式被定义时被检测到

Yield Expression

yield表达式:将控制权交还给调度程序

1
2
yield_atom       ::=  "(" yield_expression ")"
yield_expression ::= "yield" [expression_list | "from" expression]
  • yield:返回其后表达式求值
  • yield表达式是赋值语句右侧唯一表达式时,括号可以省略
  • 在定义生成器函数、异步生成器函数时才会用到,也只能在函数 定义内部使用yield表达式,将函数变为(异步)生成器函数

  • yield表达式会对外层作用域造成附带影响,不允许作为实现 推导式、生成器表达式隐式作用域的一部分

  • 生成器、异步生成器参见cs_python/py3ref/dm_gfuncs

yield from

yield from:其后表达式视为子迭代器,将控制流委托给其

  • 类似管道,迭代器参数、异常都被传递给子迭代器

    • 子迭代器依次迭代结果被传递给生成器方法调用者
    • .send传递值、.throw生成异常被传递给子迭代器
  • .send传入值、.throw传入异常如果有适当方法将被传递给 下层迭代器,否则

    • sendraise AttributeErrorraise TypeError
    • throw将立即引发传入异常
  • 子迭代器完成后引发的StopIteration实例的value属性将 作为yield表达式值

    • 可以在引发StopIteration时被显式设置#todo
    • 在子迭代器是生成器时通过从子生成器返回值自动设置
  • 用途

    • 展开嵌套序列

Primaries

原型:代表编程语言中最紧密绑定的操作(优先级最高)

1
primary ::= atom | attributeref | subscription | slicing | call

Attributeref

属性引用:后面带有句点加名称的原型

1
attributeref ::= primary "." identifier
  • 要求值为支持属性引用类型的对象(多数对象支持)
  • 对象会被要求产生以指定标识符为名称的属性
    • 产生过程可以通过重载__getattr__()方法自定义

Subscriptions

抽取:在序列(字符串、元组、列表)、映射(字典)对象中选择 一项

1
subscription ::= primary "[" expression_list "]"
  • 要求值必须为支持抽取操作的对象

    • 可以定义__getitem__()方法支持抽取操作
  • 映射:表达式列表求值须为键值

    • 抽取操作选择映射中键对应值
    • 表达式列表为元组,除非其中只有一项
  • 序列:表达式列表求值须为整数、或切片

    • 正式句法规则没有要求实现对负标号值处理,但内置序列 __getitem__()方法结合序列长度解析负标号
    • 重载__getitem__的子类需要显式添加对负标号、切片 支持

Slicings

切片:在序列对象(字符串、元组、列表)中选择某个范围内的项

1
2
3
4
5
6
7
slicing      ::=  primary "[" slice_list "]"
slice_list ::= slice_item ("," slice_item)* [","]
slice_item ::= expression | proper_slice
proper_slice ::= [lower_bound] ":" [upper_bound] [ ":" [stride] ]
lower_bound ::= expression
upper_bound ::= expression
stride ::= expression
  • 可以用作表达式赋值、del语句的目标

  • 形似表达式列表的东西同样形似切片列表,所以任何抽取操作 都可以被解析为切片

    • 通过定义将此情况解析为抽取优先于切片以消除歧义
  • 原型使用__getitem__、根据切片列表构造的键进行索引

    • 切片列表包含逗号:键将为包含切片项转换的元组
    • 否则:键为单个切片项的转换
    • 切片项若为表达式:切片的转换即为切片对象

Calling

调用:附带可能为空的一系列参数来执行可调用对象

1
2
3
4
5
6
7
8
9
10
11
call                 ::=  primary "(" [argument_list [","] | comprehension] ")"
argument_list ::= positional_arguments ["," starred_and_keywords]
["," keywords_arguments]
| starred_and_keywords ["," keywords_arguments]
| keywords_arguments
positional_arguments ::= ["*"] expression ("," ["*"] expression)*
starred_and_keywords ::= ("*" expression | keyword_item)
("," "*" expression | "," keyword_item)*
keywords_arguments ::= (keyword_item | "**" expression)
("," keyword_item | "," "**" expression)*
keyword_item ::= identifier "=" expression
  • 要求值为可调用对象

    • 用户定义函数
    • 内置函数
    • 内置对象方法
    • 类对象
    • 类实例方法
    • 任何具有__call__()方法的对象
  • 调用流程

    • 参数表达式在尝试调用前被求值
    • 所有参数表达式被转换为参数列表
    • 代码块将形参绑定到对应参数表达式值
  • 除非引发异常,调用总有返回值

    • 返回值可能为None
    • 返回值计算方式取决于可调用类型
      • 用户定义函数、实例方法、类实例:函数返回值
      • 内置函数:依赖于编译器
      • 内置对象方法:类新实例
  • 在位置参数、关键字参数后加上括号不影响语义

关键字实参转位置实参

若存在关键字实参,会通过以下操作被转换为位置参数

  • 为正式参数创建未填充空位的列表
  • 若有N个位置参数:将其放入前N个空位
  • 对每个关键字参数
    • 使用标识符确定对应的空位:若标识符与第k个正式 参数名相同,使用第k个空位
    • 若空位已被填充:则raise TypeError
    • 否则将参数值放入空位进行填充
  • 所有参数处理完毕后,未填充空位使用默认值填充
  • 若仍有未填充空位,则raise TypeError;否则填充完毕 列表被作为调用的参数列表

多余实参

  • 若有关键字参数没有与之对应的正式参数名称,将 raise TypeError,除非有形参使用**indentifier句法

    • identifier将被初始化新的有序映射接收任何额外关键字 参数
    • 若没有多余关键字实参,则为相同类型空映射
  • 若位置实参数目多余位置形参数目,将raise TypeError, 除非有形参使用*identifier句法

    • identifier将初始化为元组接受任何额外位置参数
    • 没有多余位置实参,则为空元组

实参解包

  • 若实参中出现*expression句法

    • expression求值须为iterable
    • 来自该可迭代对象的元素被当作额外位置实参
    • *expression可以放在关键字实参后而没有语法错误

      • expression会优先被迭代,元素用于填充参数列表
      • 可能和关键字参数冲突,导致关键字参数对应空位被 填充
      • 一般位置实参必须位于关键字实参前,否则有语法错误
  • 若实参中出现**expresion句法

    • expression求值须为mapping
    • 其内容被当作额外关键字参数
      • 若关键字已存在,将raise TypeError

运算符

  • 运算符优先级:从低到高

    |运算符|描述| |——-|——-| |lambda|lambda表达式| |if--else|条件表达式| |or|布尔逻辑或| |and|布尔逻辑与| |not|布尔逻辑非| |innot inisis not<<=>=>!===|比较运算,包括成员检测、标识号检测| |||按位或| |^|按位异或| |&|按位与| |<<>>|移位| |+-|加、减| |*@///%|乘、矩阵乘、除、整除、取余(字符串格式化)| |+x-x~x|正、负、按位取反| |**|幂次| |await|await表达式| |x[index]x[start:end]x(arguments...)|抽取、切片、调用、属性调用| |(expression...)[expressions...]{key:value}{expressions...}|绑定或元组、列表、字典、集合显示|

  • 求值顺序:从左至右对表达式求值

    • 但赋值操作时,右侧先于左侧求值
  • 算术类型转换

    • 若任意参数为复数,另一参数转换为复数
    • 否则,若任意参数为浮点数,另一参数为浮点数
    • 否则,二者均为整数,不需要转换

await

await:挂起coroutine执行以等待awaitable对象

1
await_expr ::= "await" primary
  • 只能在协程函数中使用

幂运算符

幂运算符

1
power ::= (await_expr | primary) ["**" u_expr]
  • 优先级高于左侧一元运算符、低于右侧一元运算符

    1
    2
    3
    -1 ** 2 == -1
    0 ** 0 == 1
    # 编程语言得普遍做法
  • 语义同两个参数调用内置power函数

    • 左参数进行右参数所指定的幂次乘方运算
    • 数值参数会转换为相同类型,返回转换后类型
      • int类型做负数幂次:参数转换为float
      • 0进行负数幂次:raise ZeroDivisionError
      • 负数进行分数次幂次:返回complex类型

一元算术、位运算

一元算术、位运算

1
u_expr ::= power | "-" u_expr | "+" u_expr | "~" u_expr
  • 一元算数、位运算具有相同优先级
  • 若参数类型不正确将raise TypeError
    • +:产生数值参数相同的值
    • -:产生数值参数的负值
    • ~:只作用于整数,对整数参数按位取反,返回-(x+1) (即负值使用补码存储)

二元算术运算符

二元算术运算符

1
2
3
4
m_expr ::=  u_expr | m_expr "*" u_expr | m_expr "@" m_expr |
m_expr "//" u_expr | m_expr "/" u_expr |
m_expr "%" u_expr
a_expr ::= m_expr | a_expr "+" m_expr | a_expr "-" m_expr
  • 二元算术运算符遵循传统优先级,除幂运算符外只有两个优先 级别

    • 乘法型
    • 加法型
  • python支持混合算术,二元运算符可以用于不同类型操作数

    • 精度较低者会被扩展为另一个操作数类型

算符说明

  • @:目标是用于矩阵乘法,没有内置类型实现此运算符

  • %:模,输出第1个参数除以第2个参数的余数

    • 参数可以是浮点数
    • 结果正负总是与第2个操作数一致、或为0
    • 结果绝对值一定小于第2个操作数绝对值(数学上必然真, 但对浮点数而言由于舍入误差存在,数值上未必真)
  • //:整除,结果就是floor函数处理算术除法/的结果

    • 整除、模语义同内置函数divmod(x,y) == (x//y, x%y)
    • x接近y的整数倍,由于舍入误差的存在,x//y可能 大于(x-x%y)//y,此时python返回后一个结果,保证 divmod(x,y)[0]*y + x % y尽量接近x
  • 某些运算符也作用于特定非数字类型

    • *:两个参数分别为整数、序列,执行序列重复
    • %:被字符串对象重载,用于执行旧式字符串格式化/插值
    • +:两个参数为相同类型序列,执行序列拼接操作

移位运算

移位运算

1
shift_expr ::= a_expr | shift_expr ("<<" | ">>") a_expr
  • 优先级低于算术运算
  • 运算符接受整数参数
    • 将第一个参数左移、右移第二个参数指定的bit数
    • 右移:x >> n == x // power(2, n)
    • 左移:x << n == x * power(2, n)

二元位运算

二元位运算

1
2
3
and_expr ::=  shift_expr | and_expr "&" shift_expr
xor_expr ::= and_expr | xor_expr "^" and_expr
or_expr ::= xor_expr | or_expr "|" xor_expr
  • 三种位运算符具有不同的优先级

  • 两个参数须为整数

    • &:对两个参数进行按位AND运算
    • ^:对两个参数进行按位XOR运算
    • |:对两个参数进行按位OR运算

比较运算

比较运算

1
2
3
comparison    ::=  or_expr (comp_operator or_expr)*
comp_operator ::= "<" | ">" | "==" | ">=" | "<=" | "!="
| "is" ["not"] | ["not"] "in"
  • 所有比较运算优先级相同(与C不同)

    • 低于任何算术、移位、位运算
  • 比较运算可以任意串联a op1 b op2 c ... y opN z等价于 a op1 b and b op2 c and ... and y opN z

    • 只是后者中每个表达式最多只被求值一次

    • 例:a < b >= c类似表达式会被按照传统比较法则解读

      • 等价a < b and b >= c
      • 仍具有短路求值特性,a < b == false时,c不会 被求值

值比较

  • ><==!=<=>=比较两个对象值

    • 不要求两个对象为相同类型
    • 比较运算符实现了特定对象值概念,可以认为是通过 实现对象比较间接定义对象值
  • 所有类型继承于object,从其继承了默认比较行为

    • =!=:一致性比较,基于对象标识id

      • 具有相同标识的实例一致性比较结果相等
      • 此默认行为动机:希望对象都应该是自反射,即 x is y就意味着x == y
    • <><=>=:没有默认值提供

      • 尝试比较raise TypeError
      • 此默认行为动机:缺少一致性比较类似固定值
数字类型

数字类型:intfloatcomplex以及标准库类型 fractions.Fractiondecimal.Decimal

  • 可进行类型内部、跨类型比较

    • 类型相关限制内按数学(算法)规则正确进行比较,且不会 有精度损失
    • 复数不支持次序比较
  • 非数字值float('NaN')decimal.Decimal('NaN')

    • 同任何其他数字值比较均返回False
    • 不等于自身,但是是同一个对象(标识相同)
    • 具体实现参见cs_python/py3ref/#todo
二进制码序列

二进制码序列:bytesbytearray

  • 可以进行类型内部、跨类型比较
  • 使用元素数字值按字典序进行比较
字符串

字符串:str

  • 使用字符的Unicode码位数字值、按字典序比较
  • 字符串、二进制码序列不能直接比较
序列

序列:tuplelistrange

  • 只能进行类型内部比较

    • 跨类型:一致性比较结果为否、次序比较将 raise TypeError
    • range不支持次序比较
  • 序列元素通过相应元素进行字典序比较

    • 序列相等:相同类型、相同长度,每对相应元素必须 相等
    • 对支持次序比较序列:排序同第一个不相等元素排序,若 对应元素不同,较短序列排序较小
  • 强制规定元素自反射性:序列元素xx==x总为真

    • 即序列元素比较比较时:须首先比较元素标识,仅会对不同 元素执行==严格比较运算

    • 若序列元素为自反射元素,结果与严格比较相同;若序列 元素为非自反射元素,结果与严格比较不同

      1
      2
      3
      4
      nan = float("NaN")
      (nan is nan) == True
      (nan == nan) == False
      ([nan] == [nan]) == True
映射

映射:dict

  • 映射相等:当且进行具有相同键值对
    • 键、值一致性比较强制规定自反射性
集合

集合:setfrozenset

  • 可进行类型内部、跨类型比较

  • 比较运算符定义为子集、超集检测

    • 这类关系没有定义完全排序,如:{1,2}{2,3}集合 不相等,也没有大小比较关系
    • 所以,集合不应作为依赖完全排序的函数参数,如: minmaxsorted,将产生未定义结果
  • 集合强制规定其元素自反射性

自定比较行为
  • 其他内置类型没有实现比较方法,继承object默认比较行为
  • 可以通过实现富比较方法自定义类型的比较行为,最好 遵守一些一致性规则(不强制)
  • 自反射:相同对象比较应该相等

    • x is yx == y
  • 对称性

    • x == yy == x
    • x != yy != x
    • x < yy > x
    • x <= yy >= x
  • 可传递

    • x > y and y > zx > z
    • x < y and y <= zx < z
  • 反向比较应该导致布尔取反

    • x == ynot x != y
    • x < ynot x >= y(对完全排序)
    • x > ynot x <= y(对完全排序)
  • 相等对象应该具有相同hash值,或标记为不可hash

  • 具体实现参见cs_python/py3ref/#todo

成员检测

innot in:成员检测,后者为前者取反

  • listtuplesetfrozensetdictcollections.deque内置容器类型

    • x in yany(x is e or x == e for e in y)
    • 映射检测是否有给定键
  • 对字符串、字节串

    • 当且进当xy其子串时x in y返回True,空字符串 总被视为其他字符串子串
    • x in y等价于y.find(x) != -1
  • 自定义类型可以自定义成员检测

    • __contains__方法返回值即为x in y返回值
    • 未定义__contains__方法但定义__iter__,若迭代y 得到值z == x,则x in y == True,出现异常等同于 in引发异常
    • 具体实现参见cs_python/py3ref/#todo

标识号比较

isis not:对象标识号检测,后者为前者取反

  • 当且仅当xy为同一对象x is y == True

布尔运算

布尔运算

1
2
3
or_test  ::=  and_test | or_test "or" and_test
and_test ::= not_test | and_test "and" not_test
not_test ::= comparison | "not" not_test
  • 执行布尔运算、表达式用于流程控制语句时,以下值被解析为 假值,其余值被解析为真值

    • False
    • None
    • 所有数值类型的数值0
    • 空字符串
    • 空容器
  • andor返回最终求值参数而不是FalseTrue

    • x and y:首先对x求值

      • x求值,若为假直接返回x求值
      • 否则对y求值并返回
    • x or y:首先对x求值

      • x求值,若为真直接返回x求值
      • 否则对y求值并返回结果值
  • not必须创建新值,无论参数为和类型均范围布尔值TrueFalse

  • 可以通过自定义__bool__方法定制逻辑值

    • 具体实现参见cs_python/py3ref/#todo

条件表达式

条件表达式:三元运算符

1
2
3
conditional_expression ::=  or_test ["if" or_test "else" expression]
expression ::= conditional_expression | lambda_expr
expression_nocond ::= or_test | lambda_expr_nocond
  • 在所有python运算中具有最低优先级
  • x if C else y
    • 首先对条件C求值
    • C为真,x被求值并返回
    • 否则将对y求值并返回

lambda表达式

lambda表达式:创建匿名函数

1
2
lambda_expr        ::=  "lambda" [parameter_list] ":" expression
lambda_expr_nocond ::= "lambda" [parameter_list] ":" expression_nocond
  • lambda parameters: expression返回函数对象,同

    1
    2
    def <lambda>(parameters):
    return expression
  • lambda表达式只是简单函数定义的简单写法

表达式列表

表达式列表:除作为列表、集合显示的一部分,包含至少一个逗号 的列表表达式将生成元组

1
2
3
4
expression_list    ::=  expression ("," expression)* [","]
starred_list ::= starred_item ("," starred_item)* [","]
starred_expression ::= expression | (starred_item ",")* [starred_item]
starred_item ::= expression | "*" or_expr
  • 末尾逗号仅在创建单独元组时需要,在其他情况下可选
  • 元组长度就是列表中表达式的数量

    • 表达式将从左至右被求值
  • *表示可迭代拆包:操作数必须为iterable(同实参调用)

    • 可迭代对象将被拆解为迭代项序列,并被包含于新建的元组 、列表、集合中

Python标准库

综述

  • 内置类型相关参见cs_python/py3ref/dm_basics

常用参数说明

Functional Programming

  • key=None/callable

    • 含义:key函数,接受一个参数,返回用于排序的值
    • 默认:None,不处理
  • iterable=iterable

    • 含义:可迭代对象

数据模型--基本数据类型

对象、值、类型

对象:python中对数据的抽象

  • python中所有数据都是由对象、对象间关系表示

    • 按冯诺依曼“存储程序计算机”,代码本身也是由对象表示

编号、类型、值

每个对象都有各自编号类型

  • 编号:可以视为对象在内存中地址,对象创建后不变

    • id()函数:获取代表对象编号的整形
    • is算符:比较对象编号判断是否为同一对象
  • 类型:决定对象支持的操作、可能取值

    • 类型会影响对象行为几乎所有方面,甚至对象编号重要性 也受到影响,如:对于会得到新值的运算
      • 不可变类型:可能返回同类型、同取值现有对象引用
        • a = b = 1ab可能指向相同对象1 (取决于具体实现)
      • 可变类型:不允许返回已存在对象
        • c=[];d=[]:会保证cd指向不同、单独 空列表(c=d=[]将同一对象赋给cd
    • 对象创建后保持不变
    • type:返回对象类型
    • CPython:相同整形值都引用同一个对象
  • 值:通过一些特征行为表征的抽象概念

    • 对象值在python中是抽象概念

      • 对象值没有规范的访问方法
      • 不要求具有特定的构建方式,如:值由其全部数据 属性组成
    • 对象值可变性由其类型决定

      • 可变的:值可以改变的对象
      • 不可变的:值(直接包含对象编号)不可改变的对象
    • 比较运算符实现了特定对象值概念,可以认为是 通过实现对象比较间接定义对象值
  • CPython:id(x)返回存放x的地址

对象销毁

对象不会被显式销毁(del仅是移除名称绑定)

  • 无法访问时可能被作为垃圾回收

    • 允许具体实现推迟垃圾回收或完全省略此机制
    • 实现垃圾回收是质量问题,只要可访问对象不会被回收 即可
    • 不要依赖不可访问对象的立即终结机制,应当总是显式 关闭外部资源引用
  • 以下情况下,正常应该被回收的对象可能继续存活

    • 使用实现的跟踪、调试功能
    • 通过try...except...语句捕捉异常
  • CPython:使用带有(可选)延迟检测循环链接垃圾的 引用计数方案
    • 对象不可访问时立即回收其中大部分,但不保证 回收包含循环引用的垃圾

标准类型层级结构

  • 以下是python内置类型的列表,扩展模块可以定义更多类型
  • 以下有些类型有特殊属性,这些特殊属性不应用作通常使用, 其定义在未来可能改变

None

NoneType:只有一种取值,None是具有此值的唯一对象

  • 通过内置名称None访问
  • 多数情况表示空值,如
    • 未显式指明返回值函数返回None
  • 逻辑值:假

NotImplemented

NotImplementedType:只有一种取值,NotImplemented是具有 此值的唯一对象

  • 通过内置名称NotImplemented访问
  • 数值、富比较方法在操作数没有该实现操作时应返回此值
    • 返回NotImplemented前,解释器会依据运算符尝试反射 方法、委托回退方法
  • 逻辑值:真

Ellipsis

ellipsis:只有一种取值,Ellipsis是具有此值的唯一对象

  • 通过字面值...、内置名称Ellipsis访问
  • 逻辑值:真

numbers.Number

number.Number:由数字字面值创建,被作为算法运算符、算数 内置函数返回结果

  • 不可变:一旦创建其值不再改变
  • 类似数学中数字,但也受限于计算机对数字的表示方法

numbers.Integral

numbers.Integral:表示数学中整数集合

  • int:整形,表示任意大小数字,仅受限于可用内存

    • 变换、掩码运算中以二进制表示
    • 负数以2的补码表示(类似符号位向左延伸补满空位)
  • bool:布尔型,表示逻辑值真、假

    • TrueFalse是唯二两个布尔对象
    • 整形子类型:在各类场合中行为类似整形10,仅在 转换为字符串时返回"True""False"

方法、函数

  • int.bit_length():不包括符号位、开头0位长
  • int.to_bytes(length, byteorder, *, signed=False)
  • class int.from_bytes(bytes, byteorder, *, signed=False)

numbers.Real(float)

float:表示机器级双精度浮点数

  • 接受的取值返回、溢出处理取决于底层结构、python实现
  • python不支持单精度浮点
  • 没必要因为节省处理器、内存消耗而增加语言复杂度

特殊取值

1
2
3
4
5
infty = float("inf")
neg_infty = float("-inf")
# 正/负无穷大
nan = float("nan")
# Not a Number
  • 特殊取值根据定义==is肯定返回False

    • float.__eq__内部应该有做检查,保证==返回False
    • 每次会创建“新”的nan/infty
    • 连续执行id(float("nan"))返回值可能相等,这是因为 每次生成的float("nan")对象被回收,不影响
  • np.nan is np.nan返回True,应该是numpy初始化的时候 创建了一个float("nan"),每次都是使用同一个nan

相关操作

  • float.as_integer_ratio()
  • float.is_integer()
  • float.hex()
  • classmethod float.fromhex(s)
  • round(f[,n])
  • math.trunc(f)
  • math.floor(f)
  • math.ceil(f)

numbers.Complex(complex)

complex:以一对机器级双精度浮点数表示复数值

  • 实部、虚部:可通过只读属性z.realz.imag获取

Iterators

迭代器类型

  • 迭代器对象需要自身支持以下两个方法,其共同组成迭代器协议
    • iterator.__iter__()
    • iterator.__next__()
  • 方法详细参考cs_python/py3ref/cls_special_method

Generator

生成器类型:提供了实现迭代器协议的便捷形式

  • 将容器对象的__iter__()方法实现为生成器,方便实现容器对 迭代器支持
  • 创建、使用参见cs_python/py3ref/dm_gfuncs

序列

序列:表示以非负整数作为索引的有限有序集

  • 不可变序列类型:对象一旦创建不能改变

    • 若包含其他可变对象引用,则可变对象“可改变”
    • 但不可变对象所直接引用的对象集是不可变的
    • 包括
      • str
      • tuple
      • bytes
      • range:非基本序列类型
  • 可变序列:创建后仍可被改变值

    • list
    • bytesarray

通用序列操作

  • x in sx not in s

    • strbytesbytearray支持子序列检测
  • s + t:拼接

    • 拼接不可变总会生成新对象
    • 重复拼接构建序列的运行时开销将基于序列总长度乘方
  • s * nn * ss自身拼接n

    • n<0被当作0处理
    • s中项不会被复制,而是被多次引用
  • s[i]s[i:j]s[i:j:step]

    • i<0索引为负值:索引顺序相对于序列s末尾,等价于 对序列长度取模
    • 序列切片:与序列类型相同的新序列
      • 索引从0开始
      • 左闭右开
    • 某些序列支持a[i:j:step]扩展切片
  • s.index(x[, i[, j]])

    • 仅部分序列支持
    • 类似s[i:j].index(x),但返回值是相对序列开头
  • s.count(x):序列中元素x数目

  • len(s):返回序列条目数量

  • min(s)max(s):序列最小、最大值

  • 序列比较运算默认实现参见cs_python/py3ref/expressions
  • 以上运算自定义实现参见 cs_python/py3ref/cls_special_methods

不可变序列

不可变序列普遍实现而可变序列未实现的操作

  • hash()内置函数

可变序列

  • s[i]=xs[i:j]=ts[i:j:k]=t:下标、切片被赋值
    • s[i:j:k]=tt长度必须和被替换切片长度相同
  • del s[i:j]del s[i:j:k]:移除元素
    • 作为del语句的目标
    • 等同于s[i:j]=[]
  • s.append():添加元素
    • 等同于s[len(s):len(s)] = [x]
  • s.clear():移除所有项
    • 等同于del s[:]
  • s.copy():浅拷贝
    • 等同于s[:]
  • s.extend(t):扩展(合并)序列
    • 基本上等于s += t
  • s.insert(i, x):向序列中插入元素
    • 等同于s[i:i] = [x]
  • s.pop(i=-1):弹出序列中元素
  • s.remove(x):删除序列中首个值为x的项
  • s.reverse():反转序列
    • 反转大尺寸序列时,会原地修改序列
    • 为提醒用户此操作通过间接影响进行,不会返回反转后序列
  • arraycollections模块提供额外可变序列类型
  • 可利用collections.abc.MutableSequence抽象类简化自定义 序列操作

tuple

元组

  • 元组中条目可以是任意python对象
  • 元组创建
    • 一对圆括号创建空元组
    • 逗号分隔
      • 单项元组:后缀逗号a,(a,)
      • 多项元组:a,b,c(a,b,c)
    • 内置构建器:tupletuple(iterable)

list

列表

  • 列表中条目可以是任意python对象
  • 构建方式
    • 方括号括起、项以逗号分隔:[][a][a,b]
    • 列表推导式:[x for x in iterable]
    • 类型构造器:list(iterable)

相关操作

.sort
1
2
def list.sort(*, key=None, reverse=False):
pass
  • 用途:对列表原地排序

    • 使用<进行各项之间比较
    • 不屏蔽异常:若有比较操作失败,整个排序操作将失败, 此时列表可能处于部分被修改状态
  • 参数

    • key:带参数函数,遍历处理每个元素提取比较键
      • None:默认,直接使用列表项排序
  • 说明

    • .sort保序,有利于多重排序
    • 为提醒用户此方法原地修改序列保证空间经济性,其不返回 排序后序列(可考虑使用sorted显式请求)
  • CPython:列表排序期间尝试改变、检测会造成未定义影响, CPython将列表排序期间显式为空,若列表排序期间被改变将 raise ValueError

str

1
2
3
4
5
class str(object="")
# 返回`object.__str__()`、`object.__repr__()`
class str(object=b"", encoding="utf-8", errors="strict")
# 给出`encoding`、`errors`之一,须为bytes-like对象
# 等价于`bytes.decode(encoding, errors)`

字符串:由Unicode码位值组成不可变序列(应该是UTF16-bl编码)

  • 范围在U+0000~U+10FFFF内所有码位值均可在字符串中使用
  • 不存在单个“字符”类型
    • 字符串中单个字符为长度为1字符串
  • 不存在可变字符串类型
    • 可以用str.join()io.StringIO高效连接多个字符串 片段
  • 字符串构建
    • 字符串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器str()

相关操作

  • ord():转换单个字符字符串为(整形)码位
  • chr():转换(整形)码位为单个字符字符串
判断
  • str.isalnum()
  • str.isalpha()
  • str.isascii()
  • str.isdecimal()
  • str.isdigit()
  • str.isidentifier()
  • str.islower()
  • str.isnumeric()
  • str.isprintable()
  • str.isspace()
  • str.istitle()
  • str.isupper()
查找
  • str.rfind(sub[, start[, end]])
  • str.rindex(sub[, start[, end]])
  • str.startswith(prefix[, start[, end]])
  • str.endwith(suffix[, start[, end]])
  • str.count(sub[, start[, end]]):子串出现次数
  • str.find(sub[, start[, end]])
    • 仅检查sub是否为子串,应使用in
    • 找不到子串时返回-1
  • str.index(sub[, start[, end]])
    • 类似str.find,但找不到子串时raise ValueError
分隔
  • str.partition(sep)
  • str.rpartition(sep)
  • str.rsplit(sep=None, maxsplit=-11)
  • str.split(sep=None, maxsplit=-1)
  • str.splitline([keepends])
拼接
  • str.join(iterable)
  • str.strip([chars])
  • str.lstrip([chars])
  • str.rstrip([chars])
  • str.rstrip([chars])
转换
  • str.lower()
  • str.upper()
  • str.swapcase()
  • str.translate(table)
  • str.replace(old, new[, count])
  • static str.maketrans(x[, y[, z]])
  • str.encode(encoding="utf-8", errors="strict"):使用 指定编码方案编码为bytes
  • str.expandtabs(tabsize=8)
  • str.capitalize():首字符大写副本
  • str.casefold():消除大小写副本
  • str.center(width[, fillchar]):字符串位于中间的字符串
  • str.title()
格式化
  • str.ljust(width[, fillchar])
  • str.rjust(width[, fillchar])
  • str.zfill(width)
  • str.format(*args, **kwargs)
  • str.format_map(mapping)
    • 类似str.format(**mapping),但mapping不会被复制 到dict
      1
      2
      3
      4
      class Default(dict):
      def __missing__(self, key):
      return key
      "{name} was born in {country}".format_map(Default(name="Guido"))
printf风格字符串格式化
  • format % values中:format%转换标记符将被转换 为values中条目

    • 效果类似于sprintf
    • values为与format中指定转换符数量等长元组、或映射 对象,除非format要求单个参数
  • 转换标记符按以下顺序构成

    • %字符:标记转换符起始
    • 映射键:可选,圆括号()括起字符序列
      • values为映射时,映射键必须
    • 转换旗标:可选,影响某些类型转换效果
      • #:值转换使用“替代形式”
      • 0:为数字值填充0字符
      • -:转换值左对齐(覆盖0
      • :符号位转换产生整数(空字符串)将留出空格
      • +:符号字符显示在开头(覆盖
    • 最小字段宽度:可选
      • *:从values读取下个元素
    • 精度:可选,.之后加精度值
      • *:从values读取下个元素
    • 长度修饰符:可选
    • 转换类型
      • d/u/i:十进制整形
      • o:8进制整形
        • #替代形式,前端添加0o
      • x/X:小/大写16进制整形
        • #替代形式,前端添加0x/0X
      • e/E:小/大写浮点指数
        • #替代形式,总是包含小数点
      • f/`F:浮点10进制
        • #替代形式,总是包含小数点
      • g/G:指数小于-4、不小于精度使用指数格式
        • #替代形式,总是包含小数点,末尾0不移除
      • c:单个字符(接收整数、单个字符字符串)
      • r/s/a:字符串(repr/str/ascii转换)
        • 按输出精度截断
      • %:输出%字符

技巧

  • 快速字符串拼接
    • 构建包含字符串的列表,利用str.join()方法
    • 写入io.StringIO实例,结束时获取值

bytes/bytearray

1
class bytes([source[, encoding[, errors]]])
  • 字节串:单个字节构成的不可变序列
  • 字节数组:字节串可变对应版本,其他同不可变bytes
  • 字节串构建

    • 字节串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器bytes()
      • 指定长度零值填充:bytes(10)
      • 整数组成可迭代对象:bytes(range(20))
      • 通过缓冲区协议复制现有二进制数据:bytes(obj)
  • 字节数组构建

    • 字节数组没有字面值语法,只能通过构造器构造
    • 可变,构建空字节数组有意义
  • 类似整数构成序列

    • 每个条目都是8位字节
    • 取值范围0~255,但只允许ASCII字符0~127
    • b[0]产生整数,切片返回bytes对象
    • 可通过list(bytes)bytes对象转换为整数构成列表
  • memeoryview提供支持

相关函数、方法

  • bytes.decode:解码为相关字符串
  • classmethod bytes.fromhex(string)
  • bytes.hex()

技巧

  • 快速字节串拼接
    • 构建包含字节串的列表,利用bytes.join()方法
    • 写入io.BytesIO实例,结束时获取值
    • 使用betaarray对象进行原地拼接

memoryview

1
class memoryview(obj)

内存视图:允许python代码访问对象内部数据

  • 若对象支持缓冲区协议,则无需拷贝

    • 支持缓冲区协议的内置对象包括bytesbytesarray
  • 内存视图元素:原始对象obj处理的基本内存单元

    • 对简单bytesbytesarray对象,一个元素就是一字节
    • array.array等类型可能有更大元素
  • 内存视图支持索引抽取、切片

    • 若下层对象可选,则支持赋值,但切片赋值不允许改变大小

相关操作

  • mv.__eq__(exporter)
  • mv.__len__()
  • mv.tobyte()
  • mv.hex()
  • mv.tolist()
  • mv.release()
  • mv.cast(format[, shape]):将内存视图转换新格式形状

可用属性

以下属性均只读

  • mv.obj:内存视图的下层对象
  • mv.nbytes
    • == product(shape) * itemsize = len(mv.tobytes())
  • mv.readonly
  • mv.format:内存视图中元素格式
    • 表示为struct模块格式
  • mv.itemsize
  • mv.ndim
  • mv.shape
  • mv.strides
  • mv.suboffsets
  • mv.c_contiguous
  • mv.f_contiguous
  • mv.contiguous

Slices Object

切片对象:表示__getitem__()方法得到的切片

  • 可以使用内置的slice()函数创建
  • a[start: stop]形式的调用被转换为 a[slice(start, stop, None)]
  • 切片对象是内部类型,参见cs_python/py3ref/dm_exec,也 不是序列类型

特殊只读属性

  • start:下界
  • stop:上界
  • step:步长值
  • 属性可以具有任意类型

方法

  • .indices(self, length):计算切片对象被应用到length 长度序列时切片相关信息
    • 返回值:(start, stop, step)三元组
    • 索引号缺失、越界按照正规连续切片方式处理

range

range:不可变数字序列类型(非不是基本序列类型)

1
2
class range(stop)
class range(start=0, stop[, step=1])
  • 参数:必须均为整数(int或实现__index__方法)

    • step > 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] < stop
    • step < 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] > stop
    • step = 0raise ValueError
  • 说明

    • 允许元素绝对值大于sys.maxsize,但是某些特性如: len()可能raise OverflowError
    • range类型根据需要计算单项、切片值
      • 相较于常规listtuple占用内存较小,且和表示 范围大小无关
      • 只能表示符合严格模式的序列
    • range类型实现了collections.abc.Sequence抽象类
      • 基本实现序列所有操作:检测、索引查找、切片等
      • 除拼接、重复:拼接、重复通常会违反严格模式
    • !===range对象视为序列比较,即提供相同值即 认为相等

集合类型

  • 表示不重复不可变对象组成的无序、有限集合

    • 不能通过下标索引
    • 可以迭代
    • 可以通过内置函数len返回集合中条目数量
  • 常用于

    • 快速成员检测、去除序列中重复项
    • 进行交、并、差、对称差等数学运算

公用操作

  • len(s)
  • x [not ]in s
  • s.isdisjoint(other)
  • s.issubset(other)/s <= other
  • s < other
  • s.issuperset(other)/s >= other
  • s > other
  • s.union(*others)/s | other |...
  • s.intersection(*others)/s & other &...
  • s.difference(*other)/s - other - other
  • s.symmetric_difference(other)/s ^ other
  • s.copy()
  • 集合比较仅定义偏序,集合列表排序无意义

可变集合独有

  • s.update(*others)/s |= other |...
  • s.intersection_update(*others)/s &= other &...
  • s.difference_udpate(*others)/s -= other |...
  • s.symmetric_difference_update(other)/set ^= other
  • s.add(elem)
  • s.remove(elem)
  • s.discard(elem)
  • s.pop()
  • s.clear()

set/frozenset

1
2
class set([iterable])
class frozenset([iterable])
  • 集合:由具有唯一性的hashable对象组成的多项无序集
  • 冻结集合:不可变集合,可哈希,可以用作集合元素、字典键
  • 创建集合

    • set()内置构造器
    • 花括号包括、逗号分隔元组列表:{a, b}
  • 创建冻结集合

    • frozenset()内置构造器
  • python中集合类似dict通过hash实现

    • 集合元素须遵循同字典键的不可变规则
    • 数字:相等的数字1==1.0,同一集合中只能包含一个

操作说明

  • .remove.__contains__discard等可以接收set类型 参数,其将被转换为临时frozenset对象

  • 非运算符版本操作可以接受任意可迭代对象作为参数,运算符 版本只能接受集合类型作为参数

映射

映射:表示任何索引集合所索引的对象的集合

  • 通过下标a[k]可在映射a中选择索引为k的条目
    • 可在表达式中使用
    • 可以作为赋值语句、del语句的目标
  • dbm.ndbmdbm.gnucollections模块提供额外映射类型

通用操作

  • len(d)
  • d[key]
  • key [not ]in d
  • iter(d)
  • d.keys():返回字典视图对象
  • d.values():返回字典视图对象
  • d.items():返回字典视图对象
  • d.get(key[, default])
  • d.copy()
  • classmethod fromkey(iterable[, value])

可变映射独有

  • d[key]=value
  • del d[key]
  • d.clear()
  • d.setdefault(key[, default])
  • d.pop()
  • d.popitem()
  • d.copy()
  • d.update()

dict

1
2
3
class dict(**kwargs)
class dict(mapping, **kwargs)
class dict(iterable, **kwargs)

字典:可由几乎任意值作为索引的有限个对象可变集合

  • 字典的高效实现要求使用键hash值以保持一致性

    • 不可作为键的值类型
      • 包含列表、字典的值
      • 其他通过对象编号而不是值比较的可变对象
    • 数字:相等的数字1==1.0索引相同字典条目
  • 创建字典

    • 花括号括起、逗号分隔键值对:{key:value,}
    • 内置字典构造器:dict()

字典视图对象

字典视图对象:提供字典条目的动态视图,随字典改变而改变

  • len(dictview)
  • iter(dictview)
  • x in dictview

Hashing

Hashing Table

  • 哈希表/散列表:可根据哈希值直接访问的数据结构
  • 原理:以哈希值做为地址,缩小搜索空间、提高查找效率

    • 使用哈希函数为每个键计算哈希值,得到位于 $0, \cdots, m-1$之间整数
    • 按照哈希值把键分布在$H[0, \cdots, m-1]$哈希表中
    • 查找匹配键时,以查找键哈希值作为起点在哈希表中 搜索
  • 应选择合适的哈希函数、哈希表长度,尽量把键尽量均分在 哈希表中

    • 哈希函数$hash$:参见math_algebra/#todo
      • 对闭散列:减少冲突
      • 对开散列:避免数据集中
    • 散列表长度$m$:常为质数(方便双散列)

Load Factor

负载因子:$\alpha = \frac {noempty} {m}$

  • $m$:哈希表中slots数量(长度)(哈希桶数量)
  • $noempty$:非空数量
  • 闭散列:负载因子反映哈希表冲突可能性、查找效率

    • 负载因子过小:冲突可能性小,查找效率高,但浪费空间
    • 负载因子过大:冲突可能性大,查找效率低,空间利用率高
    • 负载因子取值最大为1
    • 应适当平衡负载因子,负载因子接近1时重散列,避免冲突 过多影响查找效率
    • Java中HashMap初始负载值为0.75
  • 开散列:负载因子反映查找效率

    • 但应该无法反映冲突可能性(也无必要)
      • 开散列往往被用于应对大规模数据,冲突总是存在
      • 查找效率更多取决于数据(哈希值)偏倚程度
    • 负载因子可以大于1

应用

  • 字典/映射实现:cs_algorithm/data_structure/set

Open Addressing

闭散列/开放寻址:所有键存储在散列表本身中,不扩展存储空间

  • 哈希表$m$至少要和哈希键数量$n$一样大

  • 冲突问题解决:根据一定规则计算下个地址

  • cluster:聚合,散列表接近满时,一序列连续单元格被占据

    • 线性探查性能恶化,操作效率降低
    • 聚合越来的越大时,新元素插入聚类可能性增加
    • 聚合可能被新插入元素连接,导致更大程度聚合

增量类型

增量类型:碰撞发生后,根据一定规则对原哈希值修正

  • $d_i = i$:linear probing,线性探查
  • $d_i = i^2, -i^2$:quadratic probing,二次探查
  • $d_i = 伪随机数$:伪随机探查
  • $d_i = i hash_2(K), i=0,1,2,\cdots$:double hashing* ,再散列法
  • 再散列法说明:为保证哈希表中每个位置被探查,增量$s(K)$ 必须互质
    • $m$为质数时自动满足
    • 文献推荐:$s(K) = m - 2 - K mod (m-2)$
    • 对较小散列:$s(K) = 8 - (K mod 8)$
    • 对较大散列:$s(K) = K mod 97 + 1$

操作

  • 插入:依次检查哈希值$h(K)$、探查目标序列,直至找到空 单元格放置键

  • 查找:给定查找键K,计算哈希值$h(K)$、探查目标序列,比较 K和单元格中键值

    • 若查找到匹配键,查找成功
    • 遇到空单元格,查找失败
  • 删除:延迟删除,用特殊符号标记曾经被占用过、现被删除 的位置

    • 不能直接删除,否则的中间出现空单元格,影响查找正确性

算法效率

  • 成功查找访问次数: $S \approx \frac 1 2 (1+\frac 1 {(1-\alpha)})$

  • 失败查找访问次数: $U \approx \frac 1 2 [1+\frac 1 {(1-\alpha)^2}]$

  • 简化版本近似结论(散列规模越大,近似结论越正确)
  • 无法避免散列表趋满时性能恶化
  • 再哈希法数学分析困难,经验表明优秀的散列函数(两个), 性能较线性探查好

Multi Hashing

多重哈希:使用一组哈希函数$h_0,\cdots,h_n$依次计算哈希值, 确定插入、查找地址

  • 类似增量类型方法,仅各次地址独立使用哈希函数计算

增大空间

Rehashing

重散列:扫描当前表,将所有键重新放置在更大的表中

  • 散列表趋满时唯一解决办法

Overflow Area

建立公共溢出区:将哈希表分为基本表、溢出表两部分

  • 将发生冲突的元素都放入溢出区
  • 基本表中可以考虑为为每个哈希值设置多个slots
    • 即基本表直接存储哈希桶

hash_overflow_area

Chaining

开散列/分离链:哈希表作为目录,使用额外数据空间组织哈希键

拉链法/分桶法

拉链法/分桶法:哈希表作为目录项存储指向hash桶的指针,hash桶 中存储哈希键

  • 目录项表:顺序表,连续存储空间

    • 可以通过hash值在常数时间内定位:一般其索引位置就是 hash值
    • 目录项越多,数据分布相对越稀疏、碰撞概率越小、效率 越高
  • hash桶:存储具有相同哈希值元素的顺序表

    • 目录项存储chain为顺序表:每个链即为hash桶
    • 目录项存储chain为顺序表链:链中每个顺序表为hash桶
      • 即每个目录项对应多个hash值,链接多个hash桶

操作

  • 查找
    • 对查找键K,使用同样散列函数计算键散的函数值$h(K)$
    • 遍历相应单元格附着链表,查找是否存在键K
  • 插入:计算键对应桶,在链表尾部添加键即可
  • 删除:查找需要删除的键,从链表中移除即可

算法效率

  • 效率取决于链表长度,而链表长度取决于字典、散列表长度 和散列函数质量

    • 成功查找需要检查指针次数$S = 1 + \alpha / 2$
    • 不成功查找需要检查指针次数$U = \alpha$
    • 计算散列函数值是常数时间操作
    • 若n和m大致相等,平均情况下$\in \Theta(1)$
  • 算法查找的高效是以额外空间为代价的

Perfect Hashing

完美哈希:采用两级全域哈希,目录项链接独立哈希表的拉链哈希表

hash_perfect_structure

  • 二级哈希表开头部分存储哈希表元信息

    • $m = n^2$:哈希表槽数,$n$为映射至该槽元素数量 (此时由全域哈希性质:冲突次数期望小于0.5)
    • $a, b$:全域哈希参数
  • 复杂度

    • 时间复杂度:最坏情况下查找为$O(1)$
    • 空间复杂度:期望空间为线性 $E(\sum_{i=1}^{m-1} \theta(n_i^2) = \theta(n)$
  • 完美哈希没有冲突的概率至少为0.5
  • 全域哈希参见math_algebra/hash_funcs

Dynamic Hashing

动态hash:在hash表中元素增加同时,动态调整hash桶数目

  • 在原hash表基础上进行动态桶扩展
  • 不需要遍历表元素重复执行插入操作
  • 开散列法在大规模、在线数据的扩展

多hash表

多hash表:通过建立多个hash表的方式扩展原hash表

  • 思想、实现简单
  • 占用空间大,数据分布偏斜程度较大时,桶利用率不高

实现

操作时需要考虑多个hash表

  • 插入

    • 若存在hash相应桶中存在空闲区域,直接插入 multi_hash_table_ori
    • 否则分裂,新建hash表,插入元素至空闲区域 multi_hash_table_splited
  • 查找:需要查找所有hash表相应桶才能确定

    • 当表中元素较多时,可以考虑并行执行查找操作
  • 删除操作:若删除元素导致某hash表空,可考虑删除该表

可扩展动态hash

可扩展动态hash:只分裂将要溢出的桶,使用目录项作为索引

  • 多个目录项可能指向同一个桶
  • 分裂时代价较小
    • 翻倍目录项替代翻倍整个hash表
    • 每次只分裂将要溢出桶
    • 只需要进行局部重散列,重分布需要分裂的桶
  • 目录指数级增长
    • 数据分布不均时,会使得目录项很大

插入

  • D:全局位深度,hash值截断长度,为局部桶深度最大值
  • L_i:桶局部深度,等于指向其目录项数目
  • 若对应桶存在空闲位,则直接插入

    dynamic_scalable_hash_table_ori

  • 否则分裂桶:分裂后两桶局部深度加1

    dynamic_scalable_hash_table_splited

    • 若分裂桶局部深度不大于全局位深度

      • 创建新桶
      • 重散列原始桶中数据
      • 更新目录项中对应指针:分别指向分裂后桶
    • 若分类桶局部深度大于全局位深度

      • 更新全局位深度
      • 目录项翻倍
      • 创建新桶
      • 重散列原始桶中数据
      • 更新目录项中对应指针
        • (新增)无关目录项仍然指向对应桶
        • 相关目录项指向分别指向分裂后桶

查找

  • 计算原始hash值
  • 按照全局位深度截断
  • 寻找相应目录项,找到对应桶,在桶中进行比较、查找

删除

  • 计算原始hash值
  • 按照全局位深度截断
  • 寻找相应目录项,找到对应桶,在桶中进行比较、删除
    • 若删除后发现桶为空,考虑与其兄弟桶合并,并使局部深度 减1

线性散列

线性散列:按次序分裂桶,保证整个建表过程类似完全二叉树

  • 整个哈希表建表过程始终保持为完全二叉树

    • 每次分裂的桶是完全二叉树编号最小的叶子节点
    • 分裂前后桶间均为有序
  • 相较于可扩展散列

    • 无需存放数据桶指针的专门目录项,节省空间
    • 能更自然的处理数据桶满的情况
    • 允许更灵活的选择桶分裂时机
    • 但若数据散列后分布不均,则问题可能比可扩散散列严重
  • 实现相较而言更复杂

桶分裂

  • N:hash表中初始桶数目,应为2的幂次
  • d = log_2N:表示桶数目需要位数
  • level:分裂轮数,初始值为0,则每轮初始桶数为 $N * 2^{level}$
  • Next:下次要发生分裂的桶编号

linear_hash_ori

  • 每次同分裂条件可以灵活选择

    • 设置桶填充因子,桶中记录数达到该值时进行分裂
    • 桶满时发生分裂
  • 每次发生的分裂的桶总是由Next决定 linear_hash_splited_bucket

    • 与当前被插入的桶溢出无关,可引入溢出页处理桶溢出
    • 每次只分裂Next指向的桶,桶分裂后Next += 1
    • 后续产生映像桶总是位于上次产生映像桶之后
  • “轮转分裂进化”:各桶轮流进行分裂,一轮分裂完成后进入下轮 分裂 linear_hash_splited_level

查找

  • 根据Nlevel计算当前d值,截取原始hash值

  • 若hash值位于NextN之间,说明该轮对应桶还未分裂, 直接在桶中查找

  • 若hash值小于Next,说明该轮对应桶已经分裂,hash值向前 多取一位,在对应桶中查找

删除

  • 删除操作是插入操作的逆操作
  • 若删除元素后溢出块为空,可直接释放
  • 若删除元素后某个桶元素为空,Next -= 1
    • Next减少到0,且最后桶也是空时,Next = N/2 - 1 ,同时level -= 1

1`

二叉树衍生

Huffman Tree

哈夫曼树/最优树:带权路径长度WPL最短的树

  • 哈夫曼树中没有度为1的结点,又称为严格的(正则的)二叉树
  • 树带权路径长度:树中所有叶子结点的带权路径长度之和 $WPL = \sum_{k=1}^n w_k l_k$

哈夫曼算法

哈夫曼算法:构建最小加权路径二叉树

  • 输入:给定的n个权值${w_1, w_2, \cdots, w_n}$
  • 初始化n个单节点二叉树集合$F={T_1, T_2, \cdots, T_n}$
  • 合并权重最小的两棵树,将其权重之和作为新树权重记录于新树 根节点中
  • 重复,直至生成单独一棵树

特点

  • 贪婪算法
  • Huffman算法构建的最优二叉树是只有叶子节点有权值,若所有 节点都有权值的最优二叉查找树,需要使用动态规划算法, 参见cs_algorithm/data_structure/tree_search

哈夫曼编码

  • 哈夫曼编码:编码总长度最短的二进制前缀编码
  • 前缀编码:任意编码都不是其他编码的前缀,此时编码可以
  • 前缀编码可以使用二叉树设计,叶子结点代表字符,根节点到 叶子结点路径上分支代表的二进制串即为其二进制编码

  • 对给定出现频率$P[1..n]$的字符集$C[1..n]$,生成哈夫曼树 即可得到哈夫曼编码

链式存储

哈夫曼树

1
2
3
4
typedef struct HTNode{
unsigned int weight;
struct HTNode *parent, *lchild, *rchild;
}HTNode, *HuffmanTree;

选拔树

优胜树

优胜树:非叶结点取值是两个孩子中较小者的完全二叉树

tree_winner_structure

  • 根据定义,根节点的取值是整个树的最小值
  • 从叶节点构建/重构优胜树的过程中
    • 每对兄弟结点捉对比赛,胜利者晋升为父亲结点
    • 胜者逐级向上直到根节点为止
    • 调整优胜树的时间效率$\in \Theta(logk)$

顺序存储结构

1
typedef SqBiTree SqVictTree;
  • 数组实现的二叉树可以通过完全二叉树性质迅速计算父节点、 孩子节点位置

淘汰树

淘汰树:非叶结点值是两个孩子结点中较大者,即指向失败者的 选拔树

tree_loser_structure

  • 可以简化选拔树重构过程
  • 需要额外结点记录/指向胜者

用途

归并多路有序序列

  • 问题:k路有序(降序)序列,要将其归并为一组有序序列, 归并过程每轮输出一个最小关键字记录 (显然只能是当前k路序列中第一个记录)
  • 以k路序列首k个元素建立k个叶节点的选拔树

  • 构建选拔树,输出最小元素值

    tree_winner_structure

  • 用其所属序列下个元素替换其所在叶节点值,重构选拔

    tree_winner_reconstruct

  • 重复n轮:所有k轮归并总时间为$\in \Theta(nlogk)$

)$

Tree

树&森林

Free tree

自由树:连通无回路图,具有一些其他图不具有的重要 特性

  • 边数总比顶点数少一:$|E|=|V|-1$

    • 这个是图为一棵树的必要条件,但不充分
    • 若图是连通的,则是充分条件
  • 任意两个顶点之间总是存在简单路径

(Rooted)Tree

(有根)树:存在根节点的自由树

  • $root$:根节点数据元素
  • $F=(T_1, T_2, \cdots, T_m)$:森林
  • $T_i=(r_i, F_i)$:根root的第i棵子树
  • 在任意一棵非空树中

    • 有且仅有一个特定称为根的节点
    • 节点数n>1时,其余节点可以分为m个互不相交的有限集 ,每个集合本身又是一棵树,称为根的子树
  • 树中任何两个节点间总存在简单路径,所以可以任选自由树 中某节点,作为有根树的根

  • 有根树远远比自由树重要,所以也简称为树

    • 根一般放在树的顶层,第0层
    • 之后节点根据和根的距离放在相应层数

Forest

森林:无回路但不一定连通的图

  • 其每个连通分量是一棵树
  • 对树中每个节点,其子树集合即为森林

Ordered Tree

有序树:所有顶点的所有子女都是有序(不能交换次序)的有根树

应用

  • 常用于描述层次关系

    • 文件目录
    • 企业的组织结构
    • 字典的实现
    • 超大型的数据集合的高效存储
    • 数据编码
  • 用于分析递归算法

    • state-space tree:状态空间树,强调了两种算法设计 技术:回溯、分支界限

结构

  • ancestor:从根到该顶点上的简单路径上的所有顶点
  • proper ancestor:除自身外的所有祖先顶点
  • parent:从根到顶点简单路径中,最后一条边的另一端节点
  • parental:至少有一个子女的顶点
  • child
  • sibling:具有相同父母的顶点
  • leaf:没有子女的顶点
  • descendent:所有以该顶点为祖先的顶点
  • proper descendent:不包括顶点自身的子孙
  • subtree:顶点的所有子孙、连接子孙的边构成以该顶点为根的 子树
  • depth:根到该顶点的简单路径的长度
  • height:根到叶节点的最长简单路径的长度

链式存储结构

  • 链表结点代表树中一个顶点,其中至少包含:数据域、指向子女 的指针域
  • 链表头指针指向二叉树根节点

双亲表存储

双亲表示法

  • 利用除根节点外,每个结点只有一个双亲,给所有结点添加一个 指向双亲的指针域
1
2
3
4
5
6
7
8
typedef struct PTNode{
TElemType data;
int parent;
}PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r, n;
}PTree;
  • 求结点双亲时是常数时间
  • 求结点孩子时需要遍历整个结构

孩子链表存储

孩子表示法

  • 把每个结点的孩子结点排列起来,视为线性表,以单链表作为 存储结构

    • 否则结点同构则浪费空间,不同构时操作不方便

      tree_child_representation_node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct CTNode{
// 逻辑意义的结点,存储兄弟关系
int child;
// 结点位置,`nodes`中序号
struct CTNode *next;
// 指示下个兄弟结点
}*ChildPtr;
typedef struct{
// 实际存储信息的结点
TElemType data;
ChildPtr firstchild;
// 孩子链表头指针
}CTBox;
typedef struct{
CTBox nodes[MAX_TREE_SIZE];
int n, r;
// 节点数,根节点位置
}CTree;

tree_child_representation

二叉链表存储

First Child-next Silbling Representaion:孩子兄弟/二叉链表 /二叉树表示法

  • 每个节点只包含两个指针,左指针指向第一个子女,右指针指向 节点的下一个兄弟
  • 节点的所有兄弟通过节点右指针被单独的链表连接
1
2
3
4
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
  • 可高效的将有序树改造成一棵二叉树,称为关联二叉树
  • 易于实现某些操作
    • 寻找孩子结点

森林与二叉树转换

  • 给定一棵树,可以以二叉链表为媒介导出树与二叉树之间的对应 关系,即可以找到唯一一棵二叉树和与之对应

    • 任何一棵树对应的二叉树,其右子树必然为空
  • 将森林中各棵树的根节点看作兄弟结点,则可以得到森林和 二叉树的对应关系

森林转换为二叉树

森林$F={T_1, T_2, \cdots, T_M}$转换为二叉树的 $B=(root, LB, RB)$

  • 若F为空,即m=0,则B为空树
  • 若F非空
    • root即为森林中第一个树的根$ROOT(T_1)$
    • LB是从$T_1$中根节点的子树森林转换而成的二叉树
    • RB是从森林$F^{‘}$转换而来的二叉树

二叉树转换为森林

二叉树的$B=(root, LB, RB)$转换为森林 $F={T_1, T_2, \cdots, T_M}$

  • 若B为空,即m=0,则F为空树
  • 若B非空
    • F中第一棵树根$ROOT(T_1)$即为二叉树根root
    • $T_1$中根节点的子树森林$F_1$是由B的左子树LB转换来的 子树森林
    • F中除$T_1$外的其余树组成的森林$F^{‘}$是由B的右子树 RB转换而来的子树森林

树、森林遍历

  • 以二叉链表作树、森林的存储结构式,树、森林的先(根)序 遍历、后(根)序遍历对应二叉树先序、后序遍历

Binary Tree

二叉树:所有顶点子女个数不超过2个,每个子女不是父母的 left child就是right child的有序树

  • 二叉树的根是另一棵二叉树顶点的左(右)子女
  • 左右子树也是二叉树,所以二叉树可以递归定义
  • 涉及二叉树的问题可以用递归算法解决

特点

  • 二叉树第i层之多有$2^{i-1}$个节点
  • 深度为k的二叉树最多有$2^k-1$节点
  • 对任何二叉树$T$,如果其终端节点数$n_0$,度为2的节点数为 $n_2$,则$n_0=n_2+1$

    • $n, n_0, n_1, n_2$:总结点数、终端节点数、度1节点数 、度2节点数

顺序存储结构

完全二叉树

顺序存储结构:顺序存储完全二叉树结点元素

1
2
typedef TElemType SqBiTree[MAX_TREE_SIZE];
SqBiTree bt;
  • 将完全二叉树编号为i的结点存储在一维数组中下标为i-1分量中
  • 一般二叉树则将每个结点与完全二叉树结点相对照,存储在相应 分量中,并标记不存在的结点
    • 对某些二叉树空间利用效率极低
    • 所以顺序存储结构只适合完全二叉树

链式存储结构

  • 二叉树的链表节点中至少包含3个域:数据域、左右指针域
  • 链表头指针指向二叉树根节点
    • 为方便,也可以添加一个头结点,其lchild指针域指向 根结点

二叉链表

1
2
3
4
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
  • 含有n个结点的二叉链表中有n+1个空链域

三叉链表

1
2
3
4
typedef struct BiTriNode{
TElemType data;
struct BiTriNode *parent, *lchild, *rchild;
}BiTriNode, *BiTriTree;
  • 在二叉链表基础上增加指向双亲结点的指针域

二叉线索链表

Threaded Binary Tree:线索二叉树/线索化树

  • 使用二叉链表中的n+1个空链域存放二叉树遍历的前驱、后继 信息
  • 附设标记域区分指针域存放子孙结点、前驱/后继
  • 适合经常需要遍历的二叉树、查找遍历所得线性序列中前驱、 后继
    • 时间复杂度常数系数小
    • 无需设栈
1
2
3
4
5
6
typedef enum PointerTag{Link, Thread};
typedef struct BiThrNode{
TElemType data;
struct BitThrNode *lchild, *rchild;
PointerTag LTag, RTag;
}BiThrNode, *BiThrTree;
  • 后序线索化树找后继时需要知道双亲,应该使用带标志域的 三叉链表

Complete Binary Tree

  • 满二叉树:深度为k且有$2^k-1$节点的二叉树,每层上的节点数 都是最大节点数

  • 完全二叉树:essentially complete,树的每层都是满的,除 最后一层最右边的元素(一个或多个)可能有缺位

特点

  • 只存在一棵n个节点完全二叉树,高度为 $\lfloor log_2 n \rfloor$

    • 深度为k、节点数为n的完全二叉树同深度为k的满二叉树中 1-n号节点一一对应
    • 叶子节点只可能在层次最大的两层上出现
    • 对任一节点,其左分支只能和右分支深度相同或大1
  • 从上到下、从左到右对结点编号,即使用数组H存储完全二叉树 (从1开始编号,数组H[0]不存储节点值,或存放限位器)

    • 父母节点键会位于数组前$\lfloor n/2 \rfloor$个位置中 ,叶子节点位于后$\lceil n/2 \rceil$

    • 对位于父母位置i的键,其子女位于2i、2i+1,相应的对于 子女位置i的键,父母位于$\lfloor i/2 \rfloor$

应用

二叉树高度

  • 将空树高度定义为-1

算法

1
2
3
4
5
6
7
8
Height(T):
// 递归计算二叉树的高度
// 输入:二叉树T
// 输出:T的高度
if T = null_set
return -1
else
return max{Height(T_left), Height(T_right)} + 1

特点

  • 检查树是否为空是这个算法中最频繁的操作

  • 算法时间效率

    • 树的节点数为n,则根据加法操作次数满足递推式 $A(n(T))=A(n(T{left})) + A(n(T{right})) + 1$, 得到$A(n(T)) = n$

    • 考虑为树中每个节点的空子树添加外部节点得到 扩展树,则外部节点数量x满足$x=n+1$

    • 检查树是否为空次数即为扩展树节点数目 $C(n(T))=n+x=2x+1$

二叉树遍历

  • 不是所有关于二叉树的算法都需要遍历两棵子树,如:查找、 插入、删除只需要遍历两颗子树中的一棵,所以这些操作属于 减可变规模(减治法)

  • 先序、中序、后序遍历都需要用到栈

  • 中序遍历得到的序列称为中序置换/中序序列,先序、后序类似

递归版本

  • Preorder Traversal:先序

    1
    2
    3
    4
    5
    6
    PreOrder(T):
    visit(T)
    if T_left not null:
    PreOrder(T_left)
    if T_right not null:
    PreOrder(T_right)
  • Inorder Traversal:中序

    1
    2
    3
    4
    5
    6
    InOrder(T):
    if T_left not null:
    InOrder(T_left)
    visit(T)
    if T_right not null:
    InOrder(T_right)
  • Postorder Traversal:后序

    1
    2
    3
    4
    5
    6
    PostOrder(T):
    if T_left not null:
    PostOrder(T_left)
    visit(T)
    if T_right not null:
    PostOrder(T_right)

栈非递归

  • 先序遍历

    • 深度优先入栈:左子树优先入栈

      • 节点先访问后入栈,栈内存已访问节点
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      PreOrder(T):
      s = InitStack()
      cur = T
      while s.not_empty() or cur:
      while cur:
      visit(cur)
      s.push_back(cur)
      cur = cur.left
      cur = s.pop()
      cur = cur.right

      // 等价写法,仅使用`if`利用外层循环
      PreOrder(T):
      s = InitStack()
      cur = T
      while s.not_empty() or cur:
      if cur:
      visit(cur)
      s.push_back(cur)
      cur = cur.left
      else:
      cur = s.pop()
      cur = cur.right
      • 基于对遍历性质的考虑
      • 扩展性较好,可以扩展到中序、后序遍历
    • 广度优先入栈:同层右、左节点先后入栈

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      PreOrder(T):
      s = InitStack()
      s.push_back(T)
      while s.not_empty():
      cur = s.pop()
      if cur.right:
      s.push_back(cur.right)
      if cur.left:
      s.push_back(cur.left)
      visit(cur)
  • 中序遍历

    • 深度优先入栈

      • 节点先入栈后访问,栈内存未访问节点
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      InOrder(T):
      s = InitStack()
      cur = T
      while s.not_empty() or cur:
      while cur:
      s.push_back(cur)
      cur = cur.left
      cur = s.pop()
      visit(cur)
      cur = cur.right

      // 等价写法,仅使用`if`利用外层循环
      InOrder(T):
      s = InitStack()
      cur = T
      while s.not_empty() or cur:
      if cur:
      s.push_back(cur)
      cur = cur.left
      else:
      cur = s.pop()
      visit(cur)
      cur = cur.right
  • 后序:需要标记当前节点左、右子树是否被访问

    • 深度优先入栈

      • 节点先入栈后访问,栈内存未访问节点
      • 记录最后一次访问节点,判断右子树是否被访问 (若右子树被访问,右子节点必然是上个被访问节点)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      PostOrder(T):
      s = InitStack()
      cur = T
      last = NULL
      while s.not_empty() or cur:
      while cur:
      s.push_back(cur)
      cur = cur.left
      cur = s.top()
      // 检查右子树是否被访问过
      if cur.right == NULL or cur.right == last:
      visit(cur)
      last = s.pop() // 此时再弹出`cur`
      cur = NULL // 置`cur`为`NULL`,否则
      // 再次访问左子树,死循环
      else:
      cur = cur.right
    • 或者为每个节点附设标志位

层次遍历

  • 队列实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 判断节点是否存在、再填充至队列
    LevelTraversal(T):
    q = InitQueue()
    cur = T
    while q.not_empty() or cur:
    if cur.left:
    q.push_back(cur.left)
    if cur.right:d
    q.push_back(cur.right)
    visit(cur)
    cur = q.pop_first()

    # 先填充、再判断节点是否为`None`
    # 填充可保证、适合节点位置和满二叉树对应
    LevelTraversal(T):
    q = InitQueue()
    q.push(T)
    # 层次遍历使用队列实现,所以无需像栈一样使用
    # 两个判断条件`q.not_empty or cur`
    while q.not_empty():
    cur = q.pop_first()
    # 弹出时判断节点是否有效
    if cur:
    visit(cur)
    q.push_back(cur.left)
    q.push_back(cur.right)
  • 严格分层遍历:记录队列长度、遍历固定长度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    LevelTraversal(T):
    q = InitQueue()
    q.push(T)
    while q.not_empty():
    # 记录当前开始时队列长度
    # 每轮遍历该长度数目元素,严格分层遍历节点
    for i=0 to len(q):
    cur_node = q.pop_left()
    visit(cur_node)
    if cur.left:
    q.push_back(cur.left)
    if cur.right:
    q.push_back(cur.right)

树的计数

  • 二叉树相似:二者为空树或二者均不为空树,且其左右子树分别 相似
  • 二叉树等价:二者不仅相似,而且所有对应结点上的数据元素 均相同
  • 二叉树的计数:n个结点、互不相似的二叉树数量$b_n$
  • 树和一棵没有右子树的二叉树一一对应,所以具有n个结点不同 形态的树的数目,等于具有n-1个结点互不相似的二叉树数目

数学推导

二叉树可以看作是根节点、i个结点的左子树、n-i-1个结点的右子树 组成,所有有如下递推

求解得

遍历性质

  • 给定结点的前序序列、中序序列,可以唯一确定一棵二叉树

  • n个结点,不同形态二叉树数目恰是前序序列为$1 \cdots n$ 二叉树能得到的中序序列数目

  • 中序遍历过程实质是结点进栈、出栈的过程,序列$1 \cdots n$ 按不同顺序进栈、出栈能得到排列的数目即为中序置换数目 $C{2n}^n - C{2n}^{n-1} = \frac 1 {n+1} C_{2n}^n$

高维检索树

K-dimentional Tree

Kd树:循环遍历各维度,按该维度取值二分数据

  • 对高维数据进行快速搜索二叉树

    • 超平面都垂直于轴的BSPTree
  • Kd树对样本点的组织表示对k维空间的划分

    • 每个节点对应k维空间中超矩形区域
    • 构造kd树相当于用垂直于坐标轴超平面不断划分k维空间, 得到一系列超矩形区域
  • Kd树构建目标

    • 树应该尽量平衡,即分割应尽量均匀
    • 最大化邻域搜索的剪枝

建树

  • 输入:数据点$X_i, i=1,2,\cdots,N$
  • 确定划分维度(轴)
    • 选择方差最大的轴,使得数据尽量分散
    • 按次序循环遍历所有轴:方便查找时定位轴
  • 选择该维度上数值中位数作为划分点
    • 中位数查找方法
      • 各维度统一全体排序、记录
      • 抽样,使用样本中位数
    • 小于中位数的数据点划分至左子树,否则划分至右子树
  • 递归建立左、右子树直至无法继续划分
    • 节点中包含数据项数量小于阈值

查找K近邻

  • 输入:Kd树、目标点x
  • 在Kd树中找出包含目标点x的叶节点,以之为近邻点

    • 从根节点出发,与节点比较对应坐标值,递归访问至叶节点 为止
    • 目标点在训练样本中不存在,必然能够访问到叶节点
  • 沿树回溯,检查节点是否距离目标点更近,尝试更新

  • 检查该节点另一子区域是否可能具有更近距离的点

    • 即考察以目标点为圆心、当前近邻距离为半径圆,同划分轴 是否相交
    • 则只需比较目标点同相应切分平面距离、近邻距离
  • 若目标点同该对应切分平面距离小于近邻距离

    • 则将目标节点视为属于该子区域中的点
    • 从节点未访问子树开始重复以上步骤,进行近邻搜索
  • 否则继续回退

  • 退回到根节点时,搜索结束,近邻点

  • 回溯过程中需要盘对子域是否访问过,可以通过标记、比较相应 轴坐标等方式判断
  • k>1的情况类似,不过检测时使用最远近邻,新近邻需要和所有 原近邻依次比较

其他操作

插入新节点

  • 从根节点出发,根据待插入节点、当前节点在对应维度取值确定 插入左、右子树
  • 遍历直至叶子节点,插入

删除节点

  • 简单方法:将待删除节点子节点组成新集合,对其重新构建, 将新子树挂载在原被删节点位置

  • 分类讨论:设删除节点T对应划分维度为D

    • 节点无子树:直接删除
    • 节点有右子树
      • 在右子树寻找维度D取值最小节点P,替换被删除节点T
      • 在右子树递归处理删除节点P
    • 节点无右子树有左子树
      • 在左子树寻找维度D取值最小节点P,替换被删除节点T
      • 将T的左子树作为P的右子树
      • 在右子树递归处理删除节点P

查找维度D最小点

  • 若当前结点切分维度为D:只需查找左子树
  • 否则需要对左、右子树分别递归搜索

Vantage Point Tree

VP树:任选样本点,按照数据点与该点距离二分数据

  • 对高维数据进行快速搜索二叉树

  • VP树对样本点的组织表示对k维空间的划分

    • 每个节点对应k维空间中一个球形划分
    • 构造kd树相当于用以给定样本点为球心不断划分k维空间, 得到一系列球内、球外区域

建树

  • 输入:数据$X_i, i=1,2,\cdots,n$
  • 选择某数据点$X_v$作为划分球心
  • 计算其他数据点距离$D_i = d(X_i, X_v)$
  • 求出$D_i$中位数$M$
    • 与$X_v$距离$D_i \leq M$的数据点$D_i$划分至左子树
    • 与$X_v$距离$D_i \gt M$的数据点$D_i$划分至右子树

Rectangle Tree

R树:将空间划分为有重叠的

  • B树高维推广
    • 类似B树将一维区间划分为多个不重叠的子区间
    • 同样是平衡树,所有叶子位于同一层上
  • R树退化至1维有分割区间重叠问题,效果不如B树

性质

  • $M$:节点中最大键数量
  • $m \leq \frac M 2$:节点中条目最小数量
  • 非根叶节点包含$m-M$索引记录:$I$表示可在空间中完全覆盖 节点中条目点的MBR

  • 非根、非叶节点包含$m-m$个子节点:$I$表示可在空间中完全 覆盖节点中条目矩形的MBR

  • 根节点条目数$[2, m]$,除非为叶子节点

  • minimal bounding rectangleMBR,最小边界矩形

节点结构

  • 叶子节点结构:$(I, tuple-ids)$

    tree_hd_rtree_node_structure

    • $I((s_1, e_1), (s_2, e_2), \cdots, (s_n, e_n))$: n维空间中矩形
    • $tuple-ids$:节点包含的记录
  • 非叶节点:$(I, child-pointer)$

操作

建树

矩形搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SearchRect(T, S, ret):
// 利用R树搜索矩形范围中包含的记录点
// 输入:R树根节点T、待搜索矩形S
// 输出:矩形S覆盖的条目

if T.I join S == NULL:
return

// 若T不是叶子节点,检查其每个条目E
if not T.is_leaf():
for E in T.entries:
// 对与S相交E.I对应条目E,递归调用搜索
if T.I join S != NULL:
SearchRect(E, S, ret)

// 若T是叶子节点且T.I与S相交,检查其每个记录点
elif T.I join S != NULL:
for E in T.entries:
if E in S:
ret.add(E)

选择所属叶子

1
2
3
4
5
6
7
8
9
10
11
12
13
ChooseLeaf(T, E):
// 在R树中寻找新索引条目所属叶子节点
// 输入:R树根节点T、索引条目E
// 输出:E所属R树中叶子节点

if T.is_leaf():
Assert(E.is_subset(T))
return T

else:
for T_E in T.entries:
if E.is_subset(T_E)
return ChooseLeaf(T_E, E) or T_E

插入新条目

1
2
3
4
5
6
7
8
9
10
11
12
Insert(T, E):
// 向R树中插入新条目
// 输出:R树根T、新条目E

L = ChooseLeaf(T, E)
if L.has_slot():
L.add(E)
else:
LL = L.split()
L.add(E)
P = L.get_parent()

调整树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AdjustTree(T, L):
// 从不满足节点开始调整R树至满足要求
// 输入:R树根T、不满足要求节点L
// 输出:

if L.is_root():
return

P = L.get_parent_node()
if L.splitted():
NN = L.get_split_node()
if P.
// 调整节点L在父节点中矩形框I大小
addjust_I(P.L.I)

R*-tree

X-tree

SS-tree

SR-Tree

Metric-tree