zoukankan      html  css  js  c++  java
  • 栈&队列

    一、栈

    基本概念

    栈:又名堆栈,是一种运算受限的线性表,仅允许在线性表的一端进行插入(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)
    
    
    
    ### 链表实现队列
  • 相关阅读:
    log4j.appender.stdout.layout.ConversionPattern
    log4j:WARN No appenders could be found for logger
    Eclipse中TODO的分类,以及自动去除
    Java泛型类型擦除导致的类型转换问题
    Java中泛型数组的使用
    Java泛型中的通配符的使用
    Java泛型类定义,与泛型方法的定义使用
    Java泛型的类型擦除
    jQuery查询性能考虑
    jQuery判断checkbox是否选中
  • 原文地址:https://www.cnblogs.com/hsmwlyl/p/10612672.html
Copyright © 2011-2022 走看看