python deque的内在实现 本质上就是双向链表所以用于stack、队列非常方便
How collections.deque works?
前言:在 Python 生态中,我们经常使用 collections.deque 来实现栈、队列这些只需要进行头尾操作的数据结构,它的 append/pop 操作都是 O(1) 时间复杂度。list 的 pop(0) 的时间复杂度是 O(n), 在这个场景中,它的效率没有 deque 高。那 deque 内部是怎样实现的呢? 我从 GitHub 上挖出了 CPython collections 模块的第二个 commit 的源码。
dequeobject 对象定义
注释写得优雅了,无法进行更加精简的总结。
/* The block length may be set to any number over 1. Larger numbers
* reduce the number of calls to the memory allocator but take more
* memory. Ideally, BLOCKLEN should be set with an eye to the
* length of a cache line.
*/
#define BLOCKLEN 62
#define CENTER ((BLOCKLEN - 1) / 2)
/* A `dequeobject` is composed of a doubly-linked list of `block` nodes.
* This list is not circular (the leftmost block has leftlink==NULL,
* and the rightmost block has rightlink==NULL). A deque d's first
* element is at d.leftblock[leftindex] and its last element is at
* d.rightblock[rightindex]; note that, unlike as for Python slice
* indices, these indices are inclusive on both ends. By being inclusive
* on both ends, algorithms for left and right operations become
* symmetrical which simplifies the design.
*
* The list of blocks is never empty, so d.leftblock and d.rightblock
* are never equal to NULL.
*
* The indices, d.leftindex and d.rightindex are always in the range
* 0 <= index < BLOCKLEN.
* Their exact relationship is:
* (d.leftindex + d.len - 1) % BLOCKLEN == d.rightindex.
*
* Empty deques have d.len == 0; d.leftblock==d.rightblock;
* d.leftindex == CENTER+1; and d.rightindex == CENTER.
* Checking for d.len == 0 is the intended way to see whether d is empty.
*
* Whenever d.leftblock == d.rightblock,
* d.leftindex + d.len - 1 == d.rightindex.
*
* However, when d.leftblock != d.rightblock, d.leftindex and d.rightindex
* become indices into distinct blocks and either may be larger than the
* other.
*/
typedef struct BLOCK {
struct BLOCK *leftlink;
struct BLOCK *rightlink;
PyObject *data[BLOCKLEN];
} block;
typedef struct {
PyObject_HEAD
block *leftblock;
block *rightblock;
int leftindex; /* in range(BLOCKLEN) */
int rightindex; /* in range(BLOCKLEN) */
int len;
long state; /* incremented whenever the indices move */
PyObject *weakreflist; /* List of weak references */
} dequeobject;
下面是我为 Block 结构体画的一个图
+----------------------------------------+
| data: 62 objects |
+----------+ | | +-----------+
| leftlink |---| | ... | Obj1 | Obj2 | Obj3 | ... | |---| rightlink |
+----------+ | 30 31 32 | +-----------+
+----------------------------------------+
创建一个 block
static block *
newblock(block *leftlink, block *rightlink, int len) {
block *b;
/* To prevent len from overflowing INT_MAX on 64-bit machines, we
* refuse to allocate new blocks if the current len is dangerously
* close. There is some extra margin to prevent spurious arithmetic
* overflows at various places. The following check ensures that
* the blocks allocated to the deque, in the worst case, can only
* have INT_MAX-2 entries in total.
*/
if (len >= INT_MAX - 2*BLOCKLEN) {
PyErr_SetString(PyExc_OverflowError,
"cannot add more blocks to the deque");
return NULL;
}
b = PyMem_Malloc(sizeof(block));
if (b == NULL) {
PyErr_NoMemory();
return NULL;
}
b->leftlink = leftlink;
b->rightlink = rightlink;
return b;
}
创建一个 dequeobject
- 创建一个 block
- 实例化一个 dequeobject Python 对象(这一块的内在逻辑目前我也不太懂)
- leftblock 和 rightblock 指针都指向这个 block
- leftindex 是 CENTER+1,rightindex 是 CENTER
- 初始化其他一些属性, len state 等
这个第一步和第四步都有点意思,第一步创建一个 block,也就是说, deque 对象创建的时候,就预先分配了一块内存。第四步隐约告诉我们, 当元素来的时候,它先会被放在中间,然后逐渐往头和尾散开。
static PyObject *
deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
dequeobject *deque;
block *b;
if (type == &deque_type && !_PyArg_NoKeywords("deque()", kwds))
return NULL;
/* create dequeobject structure */
deque = (dequeobject *)type->tp_alloc(type, 0);
if (deque == NULL)
return NULL;
b = newblock(NULL, NULL, 0);
if (b == NULL) {
Py_DECREF(deque);
return NULL;
}
assert(BLOCKLEN >= 2);
deque->leftblock = b;
deque->rightblock = b;
deque->leftindex = CENTER + 1;
deque->rightindex = CENTER;
deque->len = 0;
deque->state = 0;
deque->weakreflist = NULL;
return (PyObject *)deque;
}
deque.append 实现
步骤:
- 如果 rightblock 可以容纳更多的元素,则放在 rightblock 中
- 如果不能,就新建一个 block,然后更新若干指针,将元素放在更新后的 rightblock 中
static PyObject *
deque_append(dequeobject *deque, PyObject *item)
{
deque->state++;
if (deque->rightindex == BLOCKLEN-1) {
block *b = newblock(deque->rightblock, NULL, deque->len);
if (b == NULL)
return NULL;
assert(deque->rightblock->rightlink == NULL);
deque->rightblock->rightlink = b;
deque->rightblock = b;
deque->rightindex = -1;
}
Py_INCREF(item);
deque->len++;
deque->rightindex++;
deque->rightblock->data[deque->rightindex] = item;
Py_RETURN_NONE;
}
看了 append 实现后,我们可以自行脑补一下 pop 和 popleft 的实现。
小结
deque 内部将一组内存块组织成双向链表的形式,每个内存块可以看成一个 Python 对象的数组, 这个数组与普通数据不同,它是从数组中部往头尾两边填充数据,而平常所见数组大都是从头往后。 得益于 deque 这样的结构,它的 pop/popleft/append/appendleft 四种操作的时间复杂度均是 O(1), 用它来实现队列、栈数据结构会非常方便和高效。但也正因为这样的设计, 它不能像数组那样通过 index 来访问、移除元素。链表 + 数组、或者链表 + 字典 这样的设计在实践中有很广泛的应用,比如 LRUCache, LFUCache,有兴趣的同鞋可以继续探索。
- PS1: LRUCache 在面试中不要太常见
- PS2: 出 LFUCache 题的面试官都是变态
- PS3: 头图来自 quora ,图文不怎么有关系列
python deque的内在实现 本质上就是双向链表所以用于stack、队列非常方便的更多相关文章
- C、C++、C#、Java、php、python语言的内在特性及区别
C.C++.C#.Java.PHP.Python语言的内在特性及区别: C语言,它既有高级语言的特点,又具有汇编语言的特点,它是结构式语言.C语言应用指针:可以直接进行靠近硬件的操作,但是C的指针操作 ...
- 使用深度学习检测TOR流量——本质上是在利用报文的时序信息、传输速率建模
from:https://www.jiqizhixin.com/articles/2018-08-11-11 可以通过分析流量包来检测TOR流量.这项分析可以在TOR 节点上进行,也可以在客户端和入口 ...
- ARIMA模型——本质上是error和t-?时刻数据差分的线性模型!!!如果数据序列是非平稳的,并存在一定的增长或下降趋势,则需要对数据进行差分处理!ARIMA(p,d,q)称为差分自回归移动平均模型,AR是自回归, p为自回归项; MA为移动平均,q为移动平均项数,d为时间序列成为平稳时所做的差分次数
https://www.cnblogs.com/bradleon/p/6827109.html 文章里写得非常好,需详细看.尤其是arima的举例! 可以看到:ARIMA本质上是error和t-?时刻 ...
- DQN 处理 CartPole 问题——使用强化学习,本质上是训练MLP,预测每一个动作的得分
代码: # -*- coding: utf-8 -*- import random import gym import numpy as np from collections import dequ ...
- python模块介绍和 import本质
模块的定义: 用来从逻辑上组织python代码(变量,函数,类,逻辑:实现一个功能),本质上就是.py结尾的python文件. 包的定义: 用来从逻辑上组织模块的,本质上就是一个目录.(必须有一个__ ...
- python学习笔记(5)—— tuple 本质探究
>>> t=(1,2,3,['a','b','c'],4,5) >>> t[3][0]='x' >>> t (1, 2, 3, ['x', 'b' ...
- Jsp与servlet本质上的区别
1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)2.jsp更擅长 ...
- 如何解决Python脚本在Linux和Windows上的格式问题
python是一种对缩进有严格要求的语言, Python脚本可以使用非常多的工具进行编写,笔者在Linux系统使用JEdit进行Python脚本编写,由于在Linux编写脚本比较痛苦,比如想一眼看出相 ...
- jQuery的$.ajax方法响应数据类型有哪几种?本质上原生ajax响应数据格式有哪几种,分别对应哪个属性?
jQuery的$.ajax方法响应数据类型有:xml.html.script.json.jsonp.text 本质上原生ajax响应数据格式只有2种:xml和text,分别对应xhr.response ...
随机推荐
- jenkins自动化视频地址
1.腾讯课堂的视频 http://www.ctnrs.com/study.html 我的课程所有列表 2.百度网盘里面的
- 在 Hibernate 中inverse的属性
hibernate配置文件中有这么一个属性inverse,它是用来指定关联的控制方的.inverse属性默认是false,若为false,则关联由自己控制,若为true,则关联由对方控制.见例子: 一 ...
- 华为交换机trunk端口更改access提示:Error: Please renew the default configurations.
现象: 华为交换机接口由原来 trunk 接口更改 access 提示 Error: Please renew the default configurations. 解决方法: 在交换机视图模式下, ...
- odoo self.ensure_one()
源码: def ensure_one(self): """ Verifies that the current recorset holds a single recor ...
- JS系列:三元运算符与循环
三元运算符 语法: 条件?成立做的事情:不成立做的事情:<=>相当于简单的if/else判断(简化写法) var num = 12; if(num>10){ num ++; }el ...
- QQ联合登录(基于Oauth2.0协议)
1. 获取授权码Authorization Code https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id= ...
- C++ Primer第五版(中文带书签)
本想发github的(链接更稳定),但是大小超出限制了. 本文件为扫描件,还是在我找了大半天之后的结果.能找到的免费的貌似都是扫描件,在看了一百多页之后(我不喜欢文字不能选中的感觉),我果断买了纸质书 ...
- git 学习笔记 ---标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本.将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来.所以,标签也是版本库的一个快照 ...
- WPF 判断一个对象是否是设计时的窗口类型,而不是运行时的窗口
原文:WPF 判断一个对象是否是设计时的窗口类型,而不是运行时的窗口 当我们对 Window 类型写一个附加属性的时候,在属性变更通知中我们需要判断依赖对象是否是一个窗口.但是,如果直接判断是否是 W ...
- easy ui 弹框叠加问题
1.框架用的是.net MVC,Index页面如下所示: @{ Layout = "~/Views/Shared/_CustomerLayout.cshtml"; ViewBag. ...