1. 栈的定义和基本操作
栈是一个后进先出(LIFO,Last In First Out)的数据结构,它可以用数组或链表实现。栈的基本操作有入栈(Push)和出栈(Pop),以及查询栈顶元素(Top)和判断栈是否为空(IsEmpty)。
2. 数组实现栈
数组实现栈通常需要先定义一个固定大小的数组,然后通过顶部指针来追踪栈顶的位置,入栈操作就是将元素添加到栈顶,同时将顶部指针向上移动,出栈操作则是删除栈顶元素,同时将顶部指针向下移动。
2.1 数组实现栈的初始化
class Stack:
def __init__(self, capacity):
self.capacity = capacity
self.stack = [None] * capacity
self.top = -1
capacity表示栈的最大容量,stack是一个固定大小的数组,用来储存栈中的元素,top表示当前栈顶的位置,初始值为-1。
2.2 数组实现栈的入栈操作
class Stack:
...
def push(self, value):
if self.top == self.capacity - 1:
raise Exception('Stack overflow')
self.top += 1
self.stack[self.top] = value
入栈操作先检查栈是否已满,如果是,则抛出异常。否则,将顶部指针向上移动,并将元素添加到栈顶。
2.3 数组实现栈的出栈操作
class Stack:
...
def pop(self):
if self.top == -1:
raise Exception('Stack underflow')
value = self.stack[self.top]
self.top -= 1
return value
出栈操作先检查栈是否为空,如果是,则抛出异常。否则,将元素从栈顶删除,并将顶部指针向下移动。
2.4 数组实现栈的其他操作
除了入栈和出栈操作外,数组实现栈还可以实现查询栈顶元素和判断栈是否为空。查询栈顶元素可以通过返回数组中顶部指针对应的元素,而判断栈是否为空只需要检查顶部指针的值是否为-1。
class Stack:
...
def top(self):
if self.top == -1:
return None
return self.stack[self.top]
def is_empty(self):
return self.top == -1
3. 链表实现栈
链表实现栈通常需要定义一个链表结构来储存元素,入栈操作就是将元素添加到链表的头部,出栈操作则是删除链表头部的元素。
3.1 链表实现栈的初始化
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Stack:
def __init__(self):
self.top = None
链表实现栈中,Node类表示一个链表节点,它包含一个值和一个指向下一个节点的指针。而Stack类通过储存链表的头节点来追踪栈顶位置,top初始值为None。
3.2 链表实现栈的入栈操作
class Stack:
...
def push(self, value):
node = Node(value)
node.next = self.top
self.top = node
入栈操作先创建一个新节点,并将其指向原来的头节点。然后将链表头节点指向新节点。
3.3 链表实现栈的出栈操作
class Stack:
...
def pop(self):
if not self.top:
raise Exception('Stack underflow')
value = self.top.value
self.top = self.top.next
return value
出栈操作先判断链表是否为空,如果是则抛出异常。否则,将链表头节点删除,并将头节点指向当前节点的下一个节点。
3.4 链表实现栈的其他操作
和数组实现栈类似,链表实现栈也可以实现查询栈顶元素和判断栈是否为空。查询栈顶元素可以通过返回链表头部节点对应的元素,而判断栈是否为空只需要检查头部节点的值是否为None。
class Stack:
...
def top(self):
if not self.top:
return None
return self.top.value
def is_empty(self):
return not self.top
4. 总结
数组和链表都可以用来实现栈,并且它们在时间和空间复杂度上有所不同。数组实现栈可以提供更快的访问时间,但缺点是必须预先分配一定大小的存储空间,并且在入栈时可能需要进行数据迁移。链表实现栈不需要进行数据迁移,并且可以动态扩展,但缺点是可能需要更多的内存来储存指针。在实际应用中,应根据具体情况选择合适的实现方式。