How collections.deque works?

Cosven

前言:在 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

  1. 创建一个 block
  2. 实例化一个 dequeobject Python 对象(这一块的内在逻辑目前我也不太懂)
  3. leftblock 和 rightblock 指针都指向这个 block
  4. leftindex 是 CENTER+1,rightindex 是 CENTER
  5. 初始化其他一些属性, 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 实现

步骤:

  1. 如果 rightblock 可以容纳更多的元素,则放在 rightblock 中
  2. 如果不能,就新建一个 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 ,图文不怎么有关系列

转载于:https://www.cnblogs.com/bonelee/p/11433743.html

python deque的内在实现 本质上就是双向链表所以用于stack、队列非常方便相关推荐

  1. python数据库开发 dga_使用深度学习检测DGA(域名生成算法)——LSTM的输入数据本质上还是词袋模型...

    from:http://www.freebuf.com/articles/network/139697.html DGA(域名生成算法)是一种利用随机字符来生成C&C域名,从而逃避域名黑名单检 ...

  2. python的字符串类型本质上_4.3Python数据类型(3)之字符串类型

    目录: 1.字符串的概念 2.字符串的形式 3.字符串的转义符 4.字符串一般操作 5.字符串函数操作 (一)字符串的概念 由单个字符组成的一个集合 (二)字符串的形式 双引号与单引号的效果一样: ( ...

  3. Python之路--WEB框架本质

    一.本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #coding:utf-8im ...

  4. DQN 处理 CartPole 问题——使用强化学习,本质上是训练MLP,预测每一个动作的得分...

    代码: # -*- coding: utf-8 -*- import random import gym import numpy as np from collections import dequ ...

  5. python入门之函数调用第二关_猪行天下之Python基础——5.1 函数(上)

    内容简述: 1.函数定义 2.形参与实参 3.关键字参数与默认参数 4.可变参数 5.全局变量与局部变量 6.内部函数 7.闭包 8.lambda表达式 9.递归 1.函数定义 我们可以将一些实现特定 ...

  6. Python 装饰器详解(上)

    Python 装饰器详解(上) 转自:https://blog.csdn.net/qq_27825451/article/details/84396970,博主仅对其中 demo 实现中不适合pyth ...

  7. 优秀!Python神器NumPy 论文终登上了 顶刊Nature!

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作学术分享,不代表本公众号立场,侵权联系删除 转载于:机器之心 AI博士笔记系列推荐 周志华<机器学习> ...

  8. python Django之Web框架本质 (2)

    文章目录 一.Web应用本质 1.socket本质 二.发送HTTP协议.响应 1.HTTP协议 2.HTTP发送响应 ◼ 静态网页 ◼ 动态网页 三.jinja2模板渲染 一.Web应用本质 为了了 ...

  9. python的顶级库_世界上最顶级的python库,NumpyPandas

    在本教程中,我将尝试对Python中最重要的两个库做一个简短的描述Numpy和熊猫...我们不要再拖延了,让我们过去吧Numpy第一. Numpy numpy是Python中科学计算的核心库.它为处理 ...

最新文章

  1. magicui系统会不会升级鸿蒙,华为EMUI 11和Magic UI 4.0同步开启内测,均升级为鸿蒙系统...
  2. 数组的一些与遍历相关的方法总结
  3. 李宏毅机器学习完整笔记发布,AI界「最热视频博主」中文课程笔记全开源
  4. C语言学习之输入一个大于三的值判断是否为素数
  5. wav音量和分贝转换关系_Permute 3 for mac(音视频格式转换器) 最新版
  6. JMM设计原理之双重检查Lock
  7. JavaScript Unicode字符操作
  8. 微信小程序开发——以简单易懂的浏览器页面栈理解小程序的页面路由
  9. android平台Camera采集数据ffmpeg进行编码探究
  10. Linux 的 history 命令显示时间
  11. Navicat 8 用户与注册码
  12. Python:混合动力汽车能量管理_动态规划简版(1/2)
  13. 用云服务器架设好服务器显示无法连接
  14. 什么是 JScript?
  15. Approaching (Almost) Any Machine Learning Problem
  16. Vue源码阅读(28):mergeOptions() 方法源码解析
  17. 谷歌中国3月28日发年终奖 不少员工将选择离职【转载】
  18. 数字信号处理综合MATLAB设计 双音多频拨号系统
  19. 学习之Java(方法)
  20. KodBox_v1.15_学习笔记

热门文章

  1. 8086汇编4位bcd码_逆向工程——汇编基础[一]
  2. 一文简单弄懂tensorflow_【TensorFlow】一文弄懂CNN中的padding参数
  3. java兔子_JAVA经典兔子问题
  4. java ee核心技术与应用_Java EE核心技术与应用(全面覆盖Java EE 6) 郝玉龙等著 pdf扫描版[103MB]...
  5. JAVA8常量池监控_深入探索Java常量池
  6. bootstrap缩小后div互相叠加_纯 JS 实现放大缩小拖拽踩坑之旅
  7. 论述Linux文件系统,linux文件系统概述
  8. linux中yum进程占cpu百分之九十,在Deepin Linux系统中kworker进程占用CPU达到100%的解决...
  9. php mysql集群_PHP如何访问数据库集群
  10. 32 usb 配置描述符_USB协议详解第4讲(USB描述符标准配置描述符)