第3课 数据结构基础(二)¶
栈¶
栈是一种特殊的线性表,但是他对于其中数据进行操作进行了一些限制。在一个栈中,所有的操作只能发生在栈顶,所以我们只能在栈顶中压入元素和弹出元素。请看下面的示意图:
栈有三个最基本最核心的操作:入栈(Push)、出栈(Pop)和查看栈顶元素(Peek)。 从上面的示意图中,可以看到栈有一个非常鲜明的特点:就是最后进入栈中的元素在出栈的过程中是最先被弹出的。所以栈也叫做先进后出(FILO, First In Last Out)表。
在生活中,我们也可以看到不少类似栈的例子。比如在打开一箱子书时,我们先取出的书一定是最后放入的书;比如手枪的弹夹中的子弹,最先发射出的子弹一定是最装入弹夹的子弹;比如在取盘子时,先取出的盘子是最后叠起来的盘子。
栈的实现¶
下面的python代码给出了一个栈class的基本框架。我们设定这个栈中储存的元素是整型的,并且它最多能容纳capacity
个元素。
class Stack:
def __init__(self, capacity: int):
# 这是栈的构造方法,一般我们会给栈设置一个容量,表明他能容纳的元素个数。
def push(self, val: int):
# 将val压入栈中
def pop(self) -> int:
# 将栈顶元素弹出,并且返回
def peek(self) -> int:
# 查看栈项元素,并不弹出
def empty(self) -> bool:
# 栈是否为空
def full(self) -> bool:
# 栈是否已满
栈由于是一种特殊的线性表,所以在实现上,可以用数组或者链表来实现。下面我们使用数组来实现栈。
栈的构造方法¶
我们首先需要一个数组做为存储元素的空间,所以在构造方法中我们需要根据容量构造一个列表。同时我们需要一个变量保存目前栈顶的位置。请看代码:
def __init__(self, capacity: int):
# 这将生成一个列表,并且其中有capacity个元素
self.array = list(range(capacity))
# 当前栈内没有元素,所以栈项的指针设为-1
self.top = -1
栈是否为空和满¶
def empty(self) -> bool:
# 当栈顶指针为-1时,栈为空
return self.top == -1
def full(self) -> bool:
# 当栈顶指针为列表最后一位时,栈已满
return self.top == len(self.array) - 1
查看栈顶元素¶
def peek(self) -> int:
# 返回当前栈顶元素。注意当栈是空的时候,我们返回一个None。
if self.empty():
return None
else:
return self.array[self.top]
压栈操作¶
在压栈操作时,要注意需要检查当前栈是否已满,如果已满则不能压栈。
def push(self, val: int):
if self.full():
# 这是一个新的语法,表示程序出错,我们抛出一个错误,这个错误的类型是字符串型。
raise "The Stack is full."
else:
# 首先将栈顶指针往前移动一格,并且在此处赋值
self.top += 1
self.array[self.top] = val
出栈操作¶
同样的,在出栈操作时,我们需要注意需要检查当前栈是否已空
def pop(self) -> int:
if self.empty():
raise "The stack is empty"
else:
# 首先将栈底指针往后移动一格,并且将原栈顶元素返回
# 注意保存栈顶元素和栈顶变化的顺序
val = self.array[self.top]
self.top -= 1
return val
就这样我们就完成了一个数组版本的栈。下面请大家思考一下,如何用链表来实现一个版本的栈呢?
课堂任务一:¶
已知链表节点类ListNode
,请使用链表的方法实现栈。注意,在使用链表实现栈时,可以不设置栈的容量。
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
class Stack:
def __init__(self):
self.head = None
def push(self, val):
# 将val压入栈中
def pop(self):
# 将栈顶元素弹出,并且返回
def peek(self):
#查看栈项元素,并不弹出
def empty(self):
# 栈是否为空
def full(self):
# 栈是否已满
队列¶
关于队列,大家在生活中已经遇到很多了。比如不管去哪儿,只要人多了,一定会排队。并且排队最重要的特点就是先进先出,后进后出,非常公平。 所以关于队列,我们把他叫做先进先出(FIFO, First In First Out)线性表。
同样的,队列也会有下面几个比较重要的核心操作:入队(push_back),出队(pop_front)与查看队首或者队尾。
下面给出了一个队列的声明。在栈的实现中,我们使用列表来实现了一个栈,下面我们使用链表来实现一个队列。
class Queue:
def __init__(self, capacity):
def push_back(self, val):
def pop_front(self):
def front(self):
def empty(self):
def full(self):
队列的构造函数¶
在构造函数中,因为我们频繁的要使用入队和出队,所以我们保留两个节点,分别指向队列的队首和队尾,同时我们需要设置队列最大的容量。
def __init__(self, capacity):
self.head = None
self.tail = None
self.capacity = capacity
self.size = 0
初始化时,队列里还没有任何的元素,所以我们需要将当前的size置为0。
检查队列是否为空或者满¶
直接检测当前的队列的size
是否为0或者是否为capacity
。
def empty(self):
return self.size == 0
def full(self):
return self.size == self.capacity
入队¶
注意,入队时,我们要从队尾入队。其次要注意,当队列为空时,要将self.head
指向刚增加的节点
def push_back(self, val):
if self.full():
raise "The queue is full."
if self.empty():
self.head = Node(val)
self.tail = self.head
else:
self.tail.next = Node(val)
self.tail = self.tail.next
self.size += 1
出队¶
同样的,出队时我们从队首出。要特别注意,当最后一个元素被弹出时,队尾指针也需要置为None。
def pop_front(self):
if self.empty():
raise "The queue is empty."
temp = self.head
self.head = self.head.next
if self.head is None:
self.tail = None
self.size -= 1
return temp.val
查看队首元素和队尾元素¶
这两个就比较简单,直接看代码
def front(self):
if self.empty():
return None
else:
return self.head.val
def back(self):
if self.empty():
return None
else:
return self.tail.val
课堂任务二:¶
同样的,请你用链表来实现一个队列。
作业¶
请完成C004 - C005
题目