数据结构与算法——栈(python详细版附带例子)


author:小黄
缓慢而坚定的生长


栈是由一系列对象组成的一个集合,这些对象的插入和删除遵循后进先出(LIFO)的原则。其基本结构如下图所示,
在这里插入图片描述
栈是最简的数据结构,但同样也是最重要的书籍结构。


用S表示一个栈,其基本的一些函数如下:

  • S.push(a):将一个元素a添加到栈S的栈顶;
  • S.pop():从栈S中移除并返回该元素,如果此时栈是空的,则返回一个错误;
  • S.top():返回栈顶元素但并不移除,就是说看栈顶的元素值,如果栈为空,则返回一个错误;
  • S.isEmpty():如果是空栈,则返回“True”,否则“False”;
  • S.len():返回栈S中元素的数量。

下面用一个例子讲解一下栈的相关操作:
在这里插入图片描述
看完这个例子,相信你们对这个栈有了进一步的了解,下面将用函数来带你们了解栈。

基于数组的栈实现

可以通过在python列表中存储一些元素来实现一个栈;用到的相关函数有:append()、pop()、len()
详细代码如下:

class ArrayStack:

    def __init__(self):
        # 创建一个空栈
        self._data = []
    
    def __len__(self):
        # 返回栈中的元素数量
        return len(self._data)

    def isEmpty(self):
        # 如果栈为空则返回“True”
        return len(self._data) == 0
    
    def push(self,a):
        # 向栈顶添加元素a
        self._data.append(a)

    def pop(self):
        # 移除栈顶的元素并返回,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data.pop()

    def top(self):
        # 返回栈顶的元素但不移除,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data[-1]

复杂度分析:

操作运行时间
S.push(a)O(1)
S.pop()O(1)
S.top()O(1)
S.isEmpty()O(1)
S.len()O(1)

例1:()匹配问题

每个开始符合必须与其相对应的结束符合相匹配:

  • 正确 ()(())
  • 错误 ()()(
  • 错误 ()())

完整代码如下:

class ArrayStack:

    def __init__(self):
        # 创建一个空栈
        self._data = []
    
    def __len__(self):
        # 返回栈中的元素数量
        return len(self._data)

    def isEmpty(self):
        # 如果栈为空则返回“True”
        return len(self._data) == 0
    
    def push(self,a):
        # 向栈顶添加元素a
        self._data.append(a)

    def pop(self):
        # 移除栈顶的元素并返回,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data.pop()

    def top(self):
        # 返回栈顶的元素但不移除,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data[-1]


def parCheck(s):
    # s是字符串类型
    a = ArrayStack()
    balance = True # 这是标记
    for i in  s:
        if i == '(':
            a.push(i)
        else:
            if a.isEmpty():
                balance = False
                break
            else:
                a.pop()
    if a.isEmpty() and balance:
        return True
    else:
        return False
    

例2:{}匹配问题

  • 正确 (){}[]
  • 错误 {}[(])
class ArrayStack:

    def __init__(self):
        # 创建一个空栈
        self._data = []
    
    def __len__(self):
        # 返回栈中的元素数量
        return len(self._data)

    def isEmpty(self):
        # 如果栈为空则返回“True”
        return len(self._data) == 0
    
    def push(self,a):
        # 向栈顶添加元素a
        self._data.append(a)

    def pop(self):
        # 移除栈顶的元素并返回,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data.pop()

    def top(self):
        # 返回栈顶的元素但不移除,如果栈为空,则返回一个错误
        if self.isEmpty():
            raise Exception('Stack is empty')
        return self._data[-1]

def isright(s):
    left = '{[(' # 注意左右括号的索引要一一对应
    right = '}])'
    a = ArrayStack()
    for i in s:
        if i in left:
            a.push(i)
        elif i in right:
            if a.isEmpty():
                return False
            # 判断此时右括号与当前栈弹出的左括号的索引是否相同
            if right.index(i) != left.index(a.pop()):
                return False
    return a.isEmpty()
### 什么是算法复杂度 在计算机科学领域,算法的复杂度通常分为时间复杂度和空间复杂度。时间复杂度衡量的是算法完成其任务所需的执行时间输入规模的关系;而空间复杂度则描述了算法所需存储空间其输入大小之间的关系。 #### 时间复杂度的概念 时间复杂度是一种理论上的工具,用于量化程序运行时间和输入数据量增长之间的关系[^1]。它帮助开发者预测当输入变大时,算法的表现是否会变得不可接受地慢。常用的大 O 表示法来表达这种复杂度,其中最常遇到的形式有 \(O(1)\),\(O(\log n)\),\(O(n)\),\(O(n \log n)\),以及 \(O(n^2)\) 等[^4]。 #### 计算时间复杂度的方法 为了计算一个给定算法的时间复杂度,可以遵循以下几个原则: - **单层循环**:如果一段代码只包含单一的 `for` 或者 `while` 循环,则该段代码的时间复杂度通常是 \(O(n)\)。 ```python def single_loop(n): total = 0 for i in range(n): # 这里有一个简单的循环 total += i # 假设每次迭代操作都是恒定时间 return total # 返回结果 ``` 上述例子中的函数具有线性的行为模式,因此它的复杂度为 \(O(n)\)。 - **嵌套循环**:对于多层嵌套的循环结构,外层每增加一次迭代次数都会使内部所有语句重复一遍,所以总的运算次数等于各层循环变量取值范围乘积的结果。比如双重循环可能达到平方级的增长速度——即 \(O(n^2)\)[^3]。 ```python def nested_loops(n): sum_result = 0 for i in range(n): # 外部循环从0到n-1共n次 for j in range(i, n):# 内部循环依赖外部索引i变化 sum_result += (i * j) return sum_result ``` 这里展示了两重循环的情况,整体表现为二次方级别的增长率,故此例中时间为 \(O(n^2)\)[^3]。 - **递归调用**:某些情况下,尤其是涉及树形遍历或者动态规划等问题时常采用递归来解决问题,在分析这类情况下的时间消耗时需特别注意递归深度及其分支因子等因素的影响[^2]。 #### 空间复杂度的理解 除了考虑运行效率之外,还需要留意内存占用状况。所谓空间复杂度就是指随着问题规模增大所需要额外分配的空间数量的变化趋势。例如简单数组访问只需要固定单位长度即可满足需求属于低阶别的开销;然而像快速排序过程中需要用到辅助区存放中间状态就可能导致较高层次的需求出现。 ##### 示例说明 下面给出几个典型场景并附带相应解释: ```python def space_complexity_example(n): data_storage = [] # 创建列表对象准备储存临时资料 for num in range(n): data_storage.append(num)# 将当前数值加入集合当中保存起来直到结束为止 return data_storage[-1] # 只返回最后一个元素作为最终输出项 ``` 在此案例里面因为要持续累积新增项目至容器之中直至最后一步才取出特定位置处的内容呈现出来,因而整个过程涉及到的数据保留动作构成了正比例关联特性,也就是常说的 \(O(n)\) 类型的空间耗费情形。 ### 结论总结 综上所述,无论是时间还是空间维度方面的考量都极为重要,只有全面掌握这两方面知识才能更好地优化现有解决方案从而提升系统整体表现水平。利用 Python 实现具体功能的同时也要时刻铭记背后隐藏着怎样的资源代价,并据此作出合理调整改进措施使得目标达成更加高效便捷。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiao黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值