008 栈(stack)

栈是一种后进先出(last-in, first-out)(LIFO)的数据结构。

允许添加元素的一端为栈顶(top),另一端为栈底(bottom)。

栈的空间复杂度为O(n)。

栈的操作以及时间复杂度

操作 含义 时间复杂度
push 进栈 O(1)
pop 出栈 O(1)
peek 取栈顶元素 O(1)
is_empty 判空 O(1)

栈的所有操作均可以在常数时间(O(1))完成。

将列表用作栈

对于判空操作,可以有两种方式:

  • len(stack) == 0
  • try...except...尝试pop如果出错则为空

Python内置的数据结构,因为有cpython在底层c的加速和优化实现,len操作都会非常快。但理论上讲,如果是自定义的数据结构,有可能会尝试遍历整个结构,从而时间复杂度达到O(n)。

直接尝试pop从理论上讲也达到了O(1)级别,所以也许会更快。

前者更加具有简洁性和可读性,后者效率更高、性能更好。大多数情况下,日常使用中,栈这一结构的元素数量也许不会太多,比如1000条以内,优化个几纳秒(ns)的速度其实无关紧要。但有人觉得万一数量变多了呢,还有人觉得要追求极致的速度优化,况且后者更符合某种风格。这个就看实际情况,见仁见智了。

总之,日常使用时两者都可以。(我还是更推荐try-except)

日常用栈

# 建立栈  
stack = []  
  
# push  
stack.append(1)  
stack.append(2)  
  
# pop  
top = stack.pop()  
  
# peek  
peek_item = stack[-1]  
  
# is_empty  
is_empty = len(stack) == 0

也可以简单封装一下

class Stack:  
    def __init__(self):  
        self._data = []  
  
    def push(self, item):  
        self._data.append(item)  
  
    def pop(self):  
        return self._data.pop()  
  
    def peek(self):  
        return self._data[-1]  
  
    def is_empty(self):  
        try:  
            self._data.pop()  
        except IndexError:  
            return True  
        else:  
            return False  
  
  
if __name__ == '__main__':  
    s = Stack()  
    s.push(1)  
    s.push(2)  
    top = s.pop()  
    print(f'top={top}')  
    peek_item = s.peek()  
    print(f'peek={peek_item}')  
    print(s.is_empty())

栈帧(Frame)与递归(Recursion)

几乎所有的编程语言都会有函数调用栈,Python称之为帧(Frame)。

每调用一个函数,就是在做push操作;函数执行完成,回到上一帧,即pop。

递归(Recursion)就是通过栈帧来实现的。

可以通过栈的数据结构,建立一个栈,自行实现递归操作。这作为学习和练习可能是个好案例,但实际当中用得很少,也似乎没有必要。

Python中的栈帧是在底层C语言中优化过的,尤其是在Python 3.11有了很大程度的提升。

以下所有均取自官方文档

开销更低、更为惰性的 Python 帧

存放执行信息的 Python 帧会在 Python 调用一个 Python 函数时被自动创建。 下面是新帧的优化操作:

  • 优化改进了帧创建进程。
  • 通过大量重用 C 栈上的帧空间来避免内存分配。
  • 将内部帧结构优化为仅包含关键信息。 在此之前的帧保存有额外的调试和内存管理信息。

现在旧式的帧对象仅在调试器或 Python 内省函数如 sys._getframe() 和 inspect.currentframe() 发出请求时才会被创建。 对于大多数用户代码,将不会创建任何帧对象。 因此,几乎所有 Python 函数调用都有显著的提速。 我们在 pyperformance 中测得了 3-7% 的提速。

内联的 Python 函数调用

在 Python 函数调用期间,Python 将调用一个评测 C 函数来解读该函数的代码。 这会有效地将纯 Python 递归限制在 C 栈的安全范围以内。

在 3.11 中,当 CPython 检测到 Python 代码调用了另一个 Python 函数时,它会设置一个新帧,并“跳转”到新帧内部的新代码。 这可以避免全部调用 C 解析函数。

大多数 Python 函数调用现在将不消耗任何 C 栈空间,这提升了它们的速度。 在简单的递归函数如斐波那契或阶乘函数中,我们测得了 1.7x 的提速。 这还意味着递归函数能够递归得更深(如果用户通过 sys.setrecursionlimit()提升了递归限制的话)。 我们在 pyperformance 中测得了 1-3% 的提升。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容