一个堆栈按照添加的顺序保存一个项目的集合。你只能向堆栈的顶部添加物品,并从顶部移除物品。如果你把堆栈想象成一叠煎饼,你只能向煎饼堆的顶部添加,并从煎饼堆的顶部删除。这是一个后进先出的系统,因为你最近添加的物品就是接下来要被移除的物品。这也被称为LIFO。
Python List As Stack
为了实现一个 Stack,我们需要一个可变的数据类型来保存一个有序的项目集合。事实证明,Python 的 list 数据类型完全符合这一要求。在这个Python堆栈数据结构教程中,我们现在要做的是在Python中实现一个类,利用列表作为我们堆栈的数据存储。当我们编写堆栈类的代码时,我们将使用 list 的右侧来表示堆栈的顶部。你也可以把这个位置看作是列表的末端。
基本的堆栈操作
- 添加到堆栈中。
- 从堆栈中删除。
- 堆栈是空的吗?
- 堆栈里有多少个项目?
- 下一个要移除的项目是什么?
堆栈数据的考虑
- 任何可以存储在列表中的数据类型都可以存储在堆栈中
- 有限的访问,因为我们只能从一个地方访问这些数据
创建一个堆栈类
现在我们已经介绍了堆栈的抽象数据类型,我们知道我们希望堆栈做什么,我们可以开始建立一个堆栈类和它的方法。首先,我们需要定义这个类本身。所以我们就叫它堆栈。我们还可以创建一个init方法,用一个列表来保存堆栈中的项目。最后,我们将创建一个名为self.items 的变量,并将其初始化为我们的空列表。
class Stack:
def __init__(self):
self.items = []
推送()
我们要做的第一件事是向栈中添加一个项目。我们使用的词是push。我们还需要在push中传递我们想添加到堆栈中的项目。push()方法接受一个项目作为参数,将其添加到我们列表的末尾,并且不返回任何东西。这方面的代码在下面强调。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
让我们检查一下这段代码是否有效。我们可以在终端通过交互式地运行 Python 文件来测试这一点。这段代码在一个名为 stacks.py 的文件中。所以要运行这段代码,我们可以使用python -i stacks.py。一旦我们进入终端,我们可以用 stack = Stack() 代码创建一个新的堆栈实例。然后我们通过运行stack.push('Orange')测试push()方法。最后,我们简单地调用stack.items,我们可以看到'Orange'现在在栈中。
让我们再试着使用push()几次,以确保它工作得很好。我们将添加两个新的项目,然后检查这些项目以确认我们现在有三个项目在堆栈中。
注意'酸奶'出现在'橙子'的右边,而'火腿'出现在'酸奶'的右边。这是正确的,之所以出现这种情况,是因为我们总是认为列表的右边是堆栈的顶部,我们只能从顶部添加和删除,所以每次我们添加其他东西时,它总是显示在该列表的最右边。
pop()
就像我们添加一样,我们也需要能够从堆栈中删除一个项目,我们使用pop这个词来实现。现在,因为列表内置的 pop 方法总是返回列表的最后一个项目,所以我们不需要指定一个索引或一个我们想要移除的项目。它将自动为我们解决这个问题。这个方法返回最后一个项目。我们应该说,它删除并返回列表中的最后一个项目,这也是堆栈的顶层项目。现在你会注意到,在pop()方法中我们有一个小的if条件。这是必要的,这样我们就可以在试图弹出一个项目并将其返回之前检查堆栈中是否有项目。如果堆栈中有项目,那么最上面的项目将被移除并返回,否则,该方法将返回一个None值。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.items:
return self.items.pop()
else:
return None
我们可以在交互式python shell中再次测试这个方法。记得我们在堆栈中有三个项目。当我们调用pop()方法时,列表中最顶端或最后一个项目被移除并返回。
现在我们可以检查栈中的项目,我们看到现在只有两个项目。如果我们再次运行pop()方法,我们会得到堆栈中最顶端或最后一个项目,现在是 "Yogurt"。
最后,我们再运行一次pop()方法,我们可以看到堆栈现在是空的。
size()
在这两个基本事项的基础上,我们可能想知道堆栈里有多少个项目,我们用的词是size。我们可以通过简单地返回项目的长度来找到大小,就像代码中显示的那样。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.items:
return self.items.pop()
else:
return None
def size(self):
return len(self.items)
再一次,我们可以在终端测试一下。我们从上一个例子中知道,堆栈目前是空的。因此,当我们调用size()方法时,我们应该得到0的回报,而这正是发生的情况。
让我们再测试一下size()方法。我们可以使用push()方法将 "Python"、"Django "和 "Htmx "添加到栈中。现在当我们调用 size() 方法时,我们得到了正确的值 3。看来我们的堆栈和size()方法工作正常了。
is_empty()
我们希望我们的堆栈类的另一个功能是能够检查堆栈是否是空的。检查的方法是看 items 是否等于一个空列表。当这个表达式被评估时,它要么是真,要么是假,这将通过返回关键字被返回。这段代码就是为我们做的。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.items:
return self.items.pop()
else:
return None
def size(self):
return len(self.items)
def is_empty(self):
return self.items == []
现在我们可以测试我们的堆栈类的 is_empty() 方法了。我们将用stacks.py文件启动一个新的交互式python会话。然后我们初始化一个新的堆栈。在这一点上,堆栈应该是空的。当我们运行is_empty()方法时,我们看到一个返回值为True。这很好,似乎是正确的。接下来,我们将一个新的项目推入堆栈,然后再运行一次is_empty()方法。这次is_empty()方法的返回值为False。这也很好,意味着is_empty()方法正确地确定了堆栈是否为空。
peek()
我们现在有了堆栈类的基本功能,因为我们已经编码了push(), pop(), size(), 和is_empty()方法。另一个你可能在堆栈实现中发现的方法是 peek() 方法。peek()方法要做的是告诉我们下一个准备被弹出的值是什么。换句话说,这应该向我们显示堆栈顶部的项目。我们也想返回这个项目。为了提供这个功能,我们需要返回列表中最后一个索引中的任何值或任何项目。Python 中强大的索引语法使我们很容易通过索引到负的第一个位置来完成这个任务。seek() 方法使用了 if 语句,就像 is_empty() 方法那样。我们首先需要检查 items 变量是否有任何项目。如果有,我们返回堆栈中最顶层的项目。如果没有项目,我们就简单地返回None。这里强调了这个新方法。
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if self.items:
return self.items.pop()
else:
return None
def size(self):
return len(self.items)
def is_empty(self):
return self.items == []
def peek(self):
if self.items:
return self.items[-1]
else:
return None
现在让我们在交互式终端中测试一下peek()方法。我们再一次初始化一个堆栈,然后把一些项目推到堆栈中。首先,我们推 "加拿大",然后推 "美国"。这意味着'美国'在栈的顶部。因此,当我们运行peek()方法时,这就是应该被返回的项目,而且确实如此。peek()方法的工作是正确的。看起来peek()和pop()做的是同一件事,除了一个关键的区别之外,它们确实是这样。这两个方法都返回堆栈中最顶层的项目,但是peek()方法并没有移除堆栈中最顶层的项目,而pop()方法则移除了。我们可以通过观察运行peek()方法后的items变量,当它返回'United States'时,就可以看到情况是这样的。那个项目仍然在栈上。