[的用别的方式]Python中的Deque:实现高效的队列和堆栈
Python 中的 deque?是一个合情理其它、度强化的单端栈,对于同时实现典雅、高工作效率的Pythonic 栈和栈很管用,它是排序中最常用的条目式正则表达式。
责任编辑中,云彩君将和我们一同自学如下表所示:
已经开始采用deque有效率地插入和新增原素出访deque中的任一原素用deque构筑高工作效率栈
已经开始采用Deque
向 Python 条目的右方新增原素和插入原素的操作形式,一般十分高工作效率。假如用大 O 表示天数复杂程度,所以能说它是?O(1)。而当 Python 须要再次分配缓存来增加下层条目以拒绝接受捷伊原素时,那些操作形式就会减慢,天数维数可能变为?O(n)。
除此之外,在 Python 条目的右方新增和插入原素的操作形式,也是十分低工作效率的,天数维数为O(n)。
由于条目提供了?.append()?和?.pop()?这三种操作形式,它能做为栈和栈采用。而条目的左右方新增和插入操作形式的操控性难题会大幅影响插件的总体操控性。
Python 的 deque 是早在 Python 2.4 中加进到 collections 组件的第二个正则表达式。这个正则表达式是专门针对为消除 Python list 中的?.append()和?.pop()?的工作效率难题而结构设计的。
Deques是近似于字符串的正则表达式,被结构设计为栈和栈的形式化,它在排序机程序的两边全力支持高工作效率的缓存和加速的新增和插入操作形式。
在 deque 第一类两边的新增和插入操作形式是平衡的,而且反之亦然有效率,即使 deque 是做为双镜像条目同时实现。除此之外,deque 上的新增和插入操作形式也是缓存安全可靠的和缓存高工作效率的。那些优点使 deque 在Python中建立自订栈和栈时不光管用。
假如须要留存最后看见的原素条目,也能采用 deque,即使能管制 deque 的最大宽度。假如他们这样做了,所以除非 deque 满了,当他们在另一侧加进捷伊原素时,它会手动弃置另一端的原素。
下面是 deque 的主要特点的总结:
存储任何正则表达式的原素是可变正则表达式全力支持带in操作形式符的成员操作形式全力支持索引,比如a_deque[i]不全力支持切片,比如a_deque[0:2]全力支持对字符串和可迭代第一类进行操作形式的内置函数,如?len() ,sorted() ,reversed()?等不全力支持inplace?排序全力支持正常迭代和反向迭代全力支持采用pickle确保在两边加速、缓存高工作效率和缓存安全可靠的插入和新增操作形式
建立 deque 实例比较简单。只须要从 collection 中导入 deque,然后用一个可选的迭代器做为参数来调用它。
>>> from collections import deque
>>> 建立一个空的 deque
>>> deque()
deque([])
>>> 采用不同的迭代器来建立 deque
>>> deque((1, 2, 3, 4))
deque([1, 2, 3, 4])
>>> deque([1, 2, 3, 4])
deque([1, 2, 3, 4])
>>> deque(range(1, 5))
deque([1, 2, 3, 4])
>>> deque("abcd")
deque([a, b, c, d])
>>> numbers = {"one": 1, "two": 2, "three": 3, "four": 4}
>>> deque(numbers.keys())
deque([one, two, three, four])
>>> deque(numbers.values())
deque([1, 2, 3, 4])
>>> deque(numbers.items())
deque([(one, 1), (two, 2), (three, 3), (four, 4)])
假如实例化 deque 时没有提供 iterable 做为参数,所以会得到一个空的 deque。假如提供并输入 iterable ,所以 deque 会用它的数据初始化新实例。初始化采用?deque.append()?从左到右进行。
Deque?初始化器须要以下两个可选参数。
iterable一个提供初始化数据的迭代器。maxlen一个整数,指定deque的最大宽度。
如前所述,假如不提供一个 iterable ,所以你会得到一个空的 deque。假如给 maxlen 提供一个值,所以你的 deque 只会存储最多的 maxlen 项。
最后,还能采用无序的可迭代第一类,如 collections 来初始化 deque。在那些情况下,不会有最终 deque 中原素的预定义顺序。
有效率地插入和新增原素
Deque 和 List 之间最重要的区别是,前者能在字符串的两边进行有效率的新增和插入操作形式。Deque 类同时实现了专门针对的?.popleft()?和?.appendleft()?方法,直接对字符串的右方进行操作形式。
>>> from collections import deque
>>> numbers = deque([1, 2, 3, 4])
>>> numbers.popleft()
1
>>> numbers.popleft()
2
>>> numbers
deque([3, 4])
>>> numbers.appendleft(2)
>>> numbers.appendleft(1)
>>> numbers
deque([1, 2, 3, 4])
在这里,采用?.popleft()?和?.appendleft()?来分别插入和增加?numbers的右方值。那些方法是针对deque的结构设计的,而 list 没有这样的方法。
Deque也提供了像list一样的?.append()?和?.pop()?方法来对字符串的右方进行操作形式。然而,.pop()?的行为是不同的。
>>> from collections import deque
>>> numbers = deque([1, 2, 3, 4])
>>> numbers.pop()
4
>>> numbers.pop(0)
Traceback (most recent call last):
File "
TypeError: pop() takes no arguments (1 given)
在这里,.pop()?删除并返回 deque 容器中的最后一个原素。该方法不拒绝接受索引做为参数,因此不能采用它从 deque 中删除任一项。只能采用它来删除并返回最右边的项。
他们认为 deque 是一个双链表。因此,给定 deque 容器中的每一项都留存着字符串中上下一项的引用(指针)。
双链表使从两边加进和插入原素的操作形式变得简单而高工作效率,即使只有指针须要更新,因此,这两个操作形式具有相似的操控性,均为O(1)。它在操控性方面也是可预测的,即使不须要再次分配缓存和移动现有项来拒绝接受新项。
从常规 Python 条目的右方新增和插入原素须要移动所有原素,这最终是一个?O(n) 操作形式。除此之外,将原素加进到条目的右方通常须要Python再次分配缓存,并将当前项复制到捷伊缓存位置,之后,它能加进新项。这个过程须要更长的天数来完成,并且新增操作形式从?O(1)传递到?O(n)。
考虑以下关于在字符串右方加进项的操控性测试,deque vs list。
time_append.py
from collections import deque
from time import perf_counter
TIMES = 10_000
a_list = []
a_deque = deque()
def average_time(func, times):
total = 0.0
for i in range(times):
start = perf_counter()
func(i)
total += (perf_counter() - start) * 1e9
return total / times
list_time = average_time(lambda i: a_list.insert(0, i), TIMES)
deque_time = average_time(lambda i: a_deque.appendleft(i), TIMES)
gain = list_time / deque_time
print(f"list.insert() {list_time:.6} ns")
print(f"deque.appendleft() {deque_time:.6} ns ({gain:.6}x faster)")
在这个脚本中,average_time()?排序了执行一个给定次数的函数(func)的平均天数。假如他们在命令行中运行该脚本,所以他们会得到以下输出。
$ python time_append.py
list.insert() 3735.08 ns
deque.appendleft() 238.889 ns (15.6352x faster)
在这个例子中,deque 上的?.appendleft()?要比 list ?上的?.insert()?快几倍。注意?deque.appendleft()?执行天数是常量O(1)。但条目右方的?list.insert()?执行天数取决于要处理的项的数量O(n)。
在这个例子中,假如增加 TIMES 的值,所以?list.insert()?会有更高的天数测量值,而?deque.appendleft()?会得到平衡(常数)的结果。假如对 deque 和 list 的 pop 操作形式进行类似的操控性测试,所以能展开下面的练习块。
Exercise:测试?deque.popleft()?与?list.pop(0)?的操控性
能将上面的脚本修改为天数deque.popleft()与list.pop(0)操作形式并估计它的操控性。
Solution:测试?deque.popleft()?与?list.pop(0)?的操控性
time_pop.py
from collections import deque
from time import perf_counter
TIMES = 10_000
a_list = [1] * TIMES
a_deque = deque(a_list)
def average_time(func, times):
total = 0.0
for _ in range(times):
start = perf_counter()
func()
total += (perf_counter() - start) * 1e9
return total / times
list_time = average_time(lambda: a_list.pop(0), TIMES)
deque_time = average_time(lambda: a_deque.popleft(), TIMES)
gain = list_time / deque_time
print(f"list.pop(0) {list_time:.6} ns")
print(f"deque.popleft() {deque_time:.6} ns ({gain:.6}x faster)")
list.pop(0) 2002.08 ns
deque.popleft() 326.454 ns (6.13282x faster)
反之亦然,它deque比list从下层字符串的右方删除原素要快。
尝试更改TIMES的值,看看会发生什么
Deque 正则表达式的结构设计是为了保证在字符串的两边进行有效率的新增和插入操作形式。它是处理须要在 Python 中同时实现栈和栈排序机程序的难题的理想选择。
出访Deque中的任一原素
Python 的 deque 返回可变的字符串,其工作形式与条目相当类似。除了能有效率地从其末端新增和插入原素外,deque 还提供了一组类似条目的方法和其他类似字符串的操作形式,以处理任一位置的原素。下面是其中的一些。
选项
描述
.insert(i, value)
在索引为i的deque容器中插入一个名为value的原素。
.remove (value)
删除第二个出现的 value ,假如 value 不存在则引发ValueError
a_deque[i]
从一个deque容器中检索索引为 i 的项。
del a_deque[i]
从deque容器中移除索引为 i 的项。
他们能采用那些方法和技术来处理 deque 第一类内部任何位置的原素。下面是如何做到这一点的。
>>> from collections import deque
>>> letters = deque("abde")
>>> letters.insert(2, "c")
>>> letters
deque([a, b, c, d, e])
>>> letters.remove("d")
>>> letters
deque([a, b, c, e])
>>> letters[1]
b
>>> del letters[2]
>>> letters
deque([a, b, e])
在这里,首先将"c"插入到位置?2的letters中。然后采用?.remove()?从deque容器中移除"d"。Deque 还允许索引来出访原素,在这里采用它来出访索引1处的b。最后,你能采用?del?关键字从 deque 中删除任何存在的项。请注意,?.remove()?允许按值删除项,而del则按索引删除项。
尽管?deque?第一类全力支持索引,但它不全力支持切片,即不能像常规条目一样采用切片语法,?[start:stop:step]?从现有的 deque 中提取:
>>> from collections import deque
>>> numbers = deque([1, 2, 3, 4, 5])
>>> numbers[1:3]
Traceback (most recent call last):
File "
TypeError: sequence index must be integer, not slice
Deque全力支持索引,却不全力支持分片。通常来说在一个链表上执行切片十分低工作效率。
虽然 deque 与 list 十分相似,但 list 是基于数组的,而 deque 是基于双链表的。
Deque 基于双链表,在出访、插入和删除任一原素都是无效操作形式。假如须要执行那些操作形式,则解释器必须在deque中进行迭代,直到找到想要的原素。因而他们的天数维数是O(n)而不是O(1)。
下面演示了在处理任一原素时 deques 和 list 的行为。
time_random_access.py
from collections import deque
from time import perf_counter
TIMES = 10_000
a_list = [1] * TIMES
a_deque = deque(a_list)
def average_time(func, times):
total = 0.0
for _ in range(times):
start = perf_counter()
func()
total += (perf_counter() - start) * 1e6
return total / times
def time_it(sequence):
middle = len(sequence) // 2
sequence.insert(middle, "middle")
sequence[middle]
sequence.remove("middle")
del sequence[middle]
list_time = average_time(lambda: time_it(a_list), TIMES)
deque_time = average_time(lambda: time_it(a_deque), TIMES)
gain = deque_time / list_time
print(f"list {list_time:.6} μs ({gain:.6}x faster)")
print(f"deque {deque_time:.6} μs")
这个脚本对插入、删除和出访一个 deque 和一个 list 中间的原素进行计时。假如运行这个脚本,得到如下表所示所示的输出:
$ python time_random_access.py
list 63.8658 μs (1.44517x faster)
deque 92.2968 μs
Deque并不像条目那样是随机出访的排序机程序。因此,从 deque 的中间出访原素的工作效率要比在条目上做反之亦然的事情低。这说明 deque 并不总是比条目更有工作效率。
Python 的 deque 对字符串两边的操作形式进行了强化,所以它在这方面一直比 list 好。另一方面,条目更适合于随机出访和固定宽度的操作形式。下面是 deque 和 list 在操控性上的一些区别。
运作
?
?
通过索引出访任一的原素
O(n)
O(1)
在右方插入和新增原素
O(1)
O(n)
在右方插入和新增原素
O(1)
O(1) + 再次分配
在中间插入和删除原素
O(n)
O(n)
对于条目,当解释器须要扩大条目以拒绝接受新项时,.append()的操控性优势受到缓存再次分配的影响而被降低。此操作形式须要将所有当前项复制到捷伊缓存位置,这将极大地影响操控性。
此总结能帮助他们为手头的难题选择适当的正则表达式。但是,在从条目切换到 deque 之前,一定要对代码进行剖析,它都有各自的操控性优势。
用Deque构筑高工作效率栈
Deque 是一个单端栈,提供了栈和栈的泛化。在本节中,他们将一同自学如何采用deque以典雅、高工作效率和Pythonic的形式在下层同时实现他们自己的栈抽象正则表达式(ADT)。
注意:?在 Python 标准库中,queue 组件同时实现了多生产者、多消费者的栈,能在多个缓存之间安全可靠地交换信息。
假如你正在处理栈,所以最好采用那些高级抽象而不是 deque ,除非你正在同时实现自己的排序机程序。
栈是原素的collections,能通过在另一端加进原素和从另一侧删除原素来修改栈。
栈?以先入先出(FIFO)的形式管理原素,像一个管道一样工作,在管道的另一端推入新原素,并从另一侧插入旧原素。向栈的另一端加进一个原素称为?enqueue?操作形式;从另一侧删除一个原素称为?dequeue。
为了更好地理解栈,以餐厅为例,餐馆里有很多人在排队等着点餐。通常情况下,后来的人将排在栈的末端。除非有了空桌子,排在队伍开头的人就会离开队伍进去用餐。
下面演示了采用一个原始的deque第一类来模拟这个过程。
>>> from collections import deque
>>> customers = deque()
>>> People arriving
>>> customers.append("Jane")
>>> customers.append("John")
>>> customers.append("Linda")
>>> customers
deque([Jane, John, Linda])
>>> People getting tables
>>> customers.popleft()
Jane
>>> customers.popleft()
John
>>> customers.popleft()
Linda
>>> No people in the queue
>>> customers.popleft()
Traceback (most recent call last):
File "
IndexError: pop from an empty deque
首先建立一个空的?deque?第一类来表示到达餐厅的人的栈。person排队放入栈,能采用.append(),将单个条目加进到右方。要从栈中取出一个person,能采用.popleft()?,删除并返回deque容器左侧的各个条目。
用栈模拟工作,然而,由于deque是一个泛化,它的API]不匹配常规的栈API。例如,不是.enqueue(),而是.append()。还有.popleft()?而不是.dequeue()。除此之外,deque?还提供了其他一些可能不符合特定需求的操作形式。
他们能建立具有特定功能的自订栈类。能在内部采用?deque?来存储数据,并在自订栈中提供所需的功能。他们能把它看作是适配器结构设计模式的一个同时实现,在这个模式中,把 deque 的接口转换成看起来更像栈接口的东西。
例如,须要一个自订的栈抽象正则表达式,提供以下功能。
排列原素去排队的原素返回栈的宽度全力支持成员资格测试全力支持正常和反向迭代提供一个方便用户的字符串表示法
此时能写一个Queue类。
custom_queue.py
from collections import deque
class Queue:
def __init__(self):
self._items = deque()
def enqueue(self, item):
self._items.append(item)
def dequeue(self):
try:
return self._items.popleft()
except IndexError:
raise IndexError("dequeue from an empty queue") from None
def __len__(self):
return len(self._items)
def __contains__(self, item):
return item in self._items
def __iter__(self):
yield from self._items
def __reversed__(self):
yield from reversed(self._items)
def __repr__(self):
return f"Queue({list(self._items)})"
._items?是一个 deque 第一类,能存储和操作形式栈中的原素。Queue采用?deque.append()同时实现了?.enqueue(),将原素加进到栈的末端。还用?deque.popleft()?同时实现了?.dequeue(),以有效率地从栈的开头删除原素。
全力支持以下特殊方法
Method
Support
?
宽度的
?
带有?的成员测试
?
常规迭代
?
反向迭代
?
字符串表示形式
理想情况下,.__repr__()返回一个字符串,代表一个有效率的 Python 表达式。能用这个表达式以相同的值再次建立这个第一类。
然而,在上面的例子中,目的是采用方法的返回值在?interactive shell?上典雅地显示第一类。能通过拒绝接受初始化可迭代第一类做为.__init__()?的参数并从中构筑实例,从而从这个特定的字符串表示形式构筑 Queue 实例。
有了那些补充,Queue 类就完成了。要在他们的代码中采用这个类,他们能做如下表所示事情。
>>> from custom_queue import Queue
>>> numbers = Queue()
>>> numbers
Queue([])
>>> Enqueue items
>>> for number in range(1, 5):
... numbers.enqueue(number)
...
>>> numbers
Queue([1, 2, 3, 4])
>>> Support len()
>>> len(numbers)
4
>>> Support membership tests
>>> 2 in numbers
True
>>> 10 in numbers
False
>>> Normal iteration
>>> for number in numbers:
... print(f"Number: {number}")
...
1
2
3
4
总结
栈和栈是编程中常用的?抽象正则表达式。它通常须要在下层排序机程序的两边进行有效率的?pop?和?append?操作形式。Python 的 collections 组件提供了一种叫做 deque 的正则表达式,它是专门针对为两边的加速和节省缓存的新增和插入操作形式而结构设计的。
有了deque,他们能用典雅、高工作效率和Pythonic的形式在低层次上编写他们自己的栈和栈。
总结下责任编辑所学内容:
如何在代码中建立和采用Python的deque如何有效率地从deque的两边新增和插入项目如何采用deque来构筑高工作效率的栈和栈什么时候值得采用deque而不是list
推荐阅读
-
?宝马新5系配置详解!这17款车型你最想入手哪一个?
-
黑龙江省290农场一天比一天热这钱真不好挣是用汗水换来的哎
{{if!data.isVip&&data.isActText}}{{elseif!data.isVip...
-
黑龙江干流堤防290农场段再次出现溃口
本报记者从吉林省水利厅水利厅司令部了解到,继16日再次出现宁远河后,27日7时,吉林河段堤防290农庄段悲剧重演宁远河。历经三个多...
-
黑龙江农险冰火两重天地方财力不足致补贴不一|农业保险|农险|财力
位于中俄林密吉林沿线的集贤县五原镇东方村今年遭遇洪水侵袭,许多农农作物受灾地区,农民周俊民种的200亩小麦几乎无人问津。幸好他参与...
-
黑龙江农垦290农场大雁繁育基地成为湿地生态养殖亮点
【编者按·中国军用养殖业网】日前,农牧一八〇农庄红树林自然保护区不远处,1500万头毛发亮光、身形丰满的雁在大坑里无拘无束地玩耍,...
-
鲜为人知的“料罗湾海战”——晚明与荷兰的战争
事件起因国内背景明崇祯时期,受小冰河期影响。中国北方长年干旱、中原和东部数次特大地震、北方瘟疫流行。除江浙闽粤一带受灾影响后仍然恢...
-
魏县关于进一步调整疫情封控管控措施的通告
肥乡县禽流感防控工作工作组办公室关于更进一步修正禽流感封控管控举措的通告各阶层农村居民:为统筹推进禽流感防控工作和经济社会发展,...
-
高职高考2022年可报考院校及最低录取分数线
-
高尿酸常常没有症状尿酸高可致痛风肾病和结石
-
高一学生举报老师教师节强制收礼:教师节,你准备送礼吗
立刻就要到此日了,每月那个时期,小学生家长们都心里感到恐惧,特别是新升学的小孩小学生家长,不晓得要千万别给同学赠礼,也不晓得新幼儿...