一、栈
基本概念
栈:又名堆栈,是一种运算受限的线性表,仅允许在线性表的一端进行插入(push)和移除(pop)运算,可以进行运算的一端称为栈顶,另一端称为栈底。遵循先进后出原理。先进入的数据被压入栈底,后放入的数据置于栈顶。桟的插入数据、删除数据操作都是实现在栈顶当中:读取数据的时候从栈顶开始弹出数据(最后一个插入的数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。向栈中插入新元素称为进栈、压栈、入栈(PUSH),即把新元素放入栈顶元素的上面,使之成为新的栈顶元素。从栈中删除元素成为出栈、退栈(POP),即将栈顶元素删除,让其相邻的元素成为新的栈顶元素。允许进行插入和移除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈,栈也称为后进先出表。举个栗子:一群人挤电梯时,先进电梯的人想要出来,必须让后进电梯的人出去之后,先进电梯的人才能出去。
两种基本操作
堆栈常用一维数组或链表来实现。包括推入(压栈,push)和弹出(弹栈,pop):
- 推入:将数据放入栈顶,栈顶指向这个新放入的数据。
- 弹出:将栈顶数据移除,栈顶指向这个移除后数据的下一个数据。
基本特点:
- 先进后出,后进先出
桟的应用场景
- 内存管理中使用的堆栈;
- 基于桟实现的二叉树的遍历;
- 在处理语言的符号对等问题,在语言中,往往很多符号是成对出现的,比如<>,{},[],()等,如何判断符号是否漏了,一种实现方式就是:假设在读入一串字符串以后,如果遇到对称符号的左边部分,则将其压入栈中,当遇到对称符号的右边部分,则弹出栈中的一个对象,如果所有的符号都是平衡的,栈中此时应该就是为空,通过判断栈中是否为空,说明字符串是否是符号平衡的。
代码实现:
### 列表实现桟 class Stack(object): def __init__(self): self._li = list() def peek(self): """获取栈顶元素""" if not self._li: return None else: return self._li[-1] def push(self, item): """推送新元素 """ self._li.append(item) return self._li[-1] def pop(self): """ 弹出栈顶元素""" if not self._li: return None else: return self._li.pop() ### 链表实现桟 class Node(object): def __init__(self, elem): self.elem = elem # 保存着结点对象的真正元素 self.next = None # 保存着当前元素的下一指向 class Stack(object): def __init__(self): self._top = None # 栈顶元素的指向 def peek(self): """返回栈顶元素""" if not self._top: # 如果栈顶元素的指向为空,代表是个空桟 return None else: # self._top相当于是一个结点对象; self._top.elem取出结点对象的元素 return self._top.elem # 若不是空桟,就返回当前栈顶所指向的元素 def push(self, item): """推送新元素""" node = Node(item) # 创建新结点对象 node.next = self._top # 将新结点对象下一指向原先的栈顶元素,新结点对象变为栈顶元素 self._top = node # 将栈顶元素指向新创建的结点对象 return node.elem # 返回新结点对象的elem属性中保存的元素 def pop(self): """弹出栈顶元素""" if not self._top: return None else: node = self._top.elem # 获取栈顶元素 self._top = self._top.next # 改变新栈顶元素的指向; 新栈顶元素=原栈顶元素的下一指向 return node # 弹出原栈顶元素 In [66]: s = Stack() In [67]: s.push(1) Out[67]: 1 In [68]: s.push(2) Out[68]: 2 In [69]: s.pop() Out[69]: 2 In [70]: s.pop() Out[70]: 1 In [72]: print(s.peek()) None
二、队列
基本概念
队列:是一种特殊的线性表,特殊之处在于它只允许在线性表的前端(front)进行删除操作,在线性表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。遵循先进先出(FIFO, First-In-First-Out)原则。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队;当队列中没有元素时,称为空队列。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。在具体应用中通常用链表或者数组来实现。举个栗子:现有一只队伍需要过马路,总共有22人,你是最后一个,你为了能够顺利过马路必须等待前面所有人过去之后,你才能过马路,排在第一位的人最先过马路,然后的第二个、第三个 ... 对应着:最先插入线性表的元素,最先被删除/读取。
五种基本操作
- 创建队列:Queue() 初始条件:队q 不存在。 操作结果:创建一个空队;
- 入队(队尾)操作:enqueue(item) 初始条件: 队q 存在。 操作结果: 对已存在的队列q,向队尾插入一个元素item;
- 出队(队头)操作:dequeue() 初始条件: 队q 存在且非空。 操作结果: 删除队首元素,并返回其值;
- 统计队列元素:length() 初始条件: 队q 存在且非空。 操作结果: 返回队列中元素个数;
- 队列判空操作:empty() 初始条件: 队q 存在/不存在。 操作结果: 若q 为空队则返回False,否则返回为True。
基本特点:
先进先出,后进后出
队列的应用场景
- 异步处理,举个栗子:现有用户注册模块,需要同时完成写入注册数据至数据库、发送激活邮件、发送短信验证码。实现包括:串行方式、并行方式
- 串行方式:先将注册信息写入数据库成功后,再发送激活邮件,最后发送短信验证码。以上三个任务依次全部完成后,返回给客户。
- 并行方式:先将注册信息写入数据库成功后,发送注册邮件的同时,一起发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间。
- 应用解耦,举个栗子:现有用户下单模块,当用户下单后,订单系统需要通知库存系统。传统的做法是,在订单系统调用库存系统的接口。假如库存系统无法访问,则订单系统减库存将失败,从而导致订单失败,订单系统与库存系统耦合度过高。
-
订单系统:用户下单后,在订单系统中将调用库存系统接口的操作放入到消息队列,订单系统中不再阻塞等待库存系统的返回结果。并将订单下单成功返回给用户。
-
库存系统:在消息队列中订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作。
-
假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦。
- 流量削锋,一般在秒杀或团抢活动中使用广泛。举个栗子:现有一个秒杀活动,一般会因为流量过大、暴增而导致应用挂掉。为解决这个问题,一般需要将用户请求加入消息队列,达到控制活动的人数,可以缓解短时间内高流量压垮应用。
- 服务器接收用户请求后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
-
秒杀业务根据消息队列中的请求信息,再做后续处理。
- 消息通讯
- 点对点通讯:客户端A和客户端B使用同一队列,进行消息通讯;
- 聊天室通讯:客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。
代码实现
### 列表实现队列 class Queues(object): def __init__(self): self._li = list() def is_empty(self): """队列判空""" if len(self._li) == 0: return True else: return False def length(self): """返回队列长度""" return len(self._li) def enqueue(self, item): """队尾插入元素""" self._li.append(item) return self._li[-1] def dequeue(self): """队头删除元素""" if not self.is_empty(): return None else: return self._li.pop(0) ### 链表实现队列