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

      一种重要的线性结构;后进先出。特殊的线性表,只能在表尾进行插入(push)和删除(pop)操作。 表尾称为栈顶(top),表头称为栈底(bottom)。一般用顺序表实现。

    清空一个栈:s->base = s->top

    销毁一个栈:释放其所占的物理内存空间。

    for(i=0, i<s->stackSize) {free(s->base); s->base++}

    s->base = s->top = NULL; s->stackSize = 0

    用栈实现二进制转十进制

    def Bin2Dec(BinStrs):
        strStack = []
        for elem in BinStrs:
            strStack.append(elem)
        DecRes = 0
        n = 0
        while strStack:
            DecRes += eval(strStack.pop())*(2**n)
            n += 1
        return DecRes
    
    BinInputs = input('Binary input:')
    print('Decade output:', Bin2Dec(BinInputs))
    

    栈的链式存储结构,将栈顶放在单链表的头部,即栈顶指针和单链表的头指针合二为一。

    逆波兰表达式RPN:利用栈来进行运算的数学表达式。(中缀表达式转换为后缀表达式)

    class Solution:
        def evalRPN(self, tokens: List[str]) -> int:
            Stack = []
            operations = {'+','-','*','/'}
            # 遇到操作符就pop出两个元素进行操作,结果再push进栈
            for token in tokens:
                if token in operations:
                    right = Stack.pop()
                    left = Stack.pop()
                    tmp = 0
                    if token == '+':
                        tmp = left + right
                    elif token == '-':
                        tmp = left - right
                    elif token == '*':
                        tmp = left*right
                    else:  # 整除要判断正负性
                        if right*left > 0:
                            tmp = left//right
                        elif right*left < 0:
                            tmp = -(-left//right)
                    Stack.append(tmp)
                else:
                    Stack.append(int(token))
            return Stack.pop()
    

    中缀表达式转换为后缀表达式

    1.遇到操作数,直接输出; 
    2.栈为空时,遇到运算符,入栈; 
    3.遇到左括号,将其入栈; 
    4.遇到右括号,执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出; 
    5.遇到其他运算符’+”-”*”/’时,弹出所有优先级大于或等于该运算符的栈顶元素,然后将该运算符入栈; 
    6.最终将栈中的元素依次出栈,输出。 

    def middle2behind(expresssion):  
        result = []             # 结果列表
        stack = []              # 栈
        operations = {'+','-','*','/','(',')'}
        for item in expression: 
            if item not in operations:  # 数字直接输出
                result.append(item) 
            else:                     # 如果是操作符
                if len(stack) == 0:   # 若栈空,直接入栈
                    stack.append(item)
                elif item in '*/(':   # 如果当前字符为*/(,直接入栈
                    stack.append(item)
                elif item == ')':     # 如果右括号则全部弹出(碰到左括号停止,左括号不输出)
                    t = stack.pop()
                    while t != '(':   
                        result.append(t)  
                        t = stack.pop()
                # 如果当前字符为加减且栈顶为乘除,则开始弹出
                elif item in '+-' and stack[-1] in '*/':
                    if stack.count('(') == 0:  # 如果没有有左括号,全部弹出     
                        while stack:
                            result.append(stack.pop())
                    else:                      # 如果有左括号,弹出到左括号为止
                        t = stack.pop()  
                        while t != '(':
                            result.append(t)
                            t = stack.pop()
                        stack.append('(')  # 左括号pop出来了,再补回去
                    stack.append(item)  # 弹出操作完成后将‘+-’入栈
                else:
                    stack.append(item)# 其余情况直接入栈(如当前字符为+,栈顶为+-)
    
        # 栈中还有操作符不满足弹出条件,把栈中的东西全部弹出
        while stack:
            result.append(stack.pop())
        # 返回字符串
        return "".join(result)
    

      

    单调栈:保持先进后出的栈特性,每次新元素入栈后,栈内的元素都保持有序。

    处理Next Greater Element问题。给定一个数组a,返回一个等长数组b,b中存储a中相同索引的元素的下一个更大元素,如果没有就存-1。例如a=[2,1,2,4,3],返回b=[4,2,4-1,-1]。

    暴力解,两层遍历O(n2),不写了。

    单调栈,每个元素入栈一次,最多pop一次,O(n)。

    def nextGreaterElement(nums):
        if not nums:
            return []
        
        n = len(nums)
        ans = [-1] * n
        stack = []  # stack
        
        for i in range(n-1, -1, -1):  # nums中的元素倒着入栈,就正着出栈
            while stack and stack[-1] <= nums[i]:  # 只要栈顶元素不大于当前元素,就pop出去
                stack.pop()   # 所以stack始终被维护成一个从底到顶单调递减的栈
                
            ans[i] = stack[-1] if stack else -1   # 栈顶元素是第一个比当前元素大的元素。按ans索引从后往前赋值
            stack.append(nums[i])  # 当前元素入栈,等着和nums中i之前位置的元素比较
        return ans
    

      

    问题改为,返回一个数组,存的是每个元素距其Next Greater Element的距离。例如a=[2,1,2,4,3],返回b=[3,1,1,0,0]。

    def distance_nextGreaterElement(nums):
        if not nums:
            return []
        
        n = len(nums)
        ans = [-1] * n
        stack = []  # stack
        
        for i in range(n-1, -1, -1):  # nums中的元素倒着入栈,就正着出栈
            while stack and nums[stack[-1]] <= nums[i]:  # 只要栈顶索引对应的元素不大于当前元素,就把索引pop出去
                stack.pop()   # 所以stack始终被维护成一个索引对应值单调递减的栈
                
            ans[i] = stack[-1] - i if stack else 0   # 栈顶元素是第一个比当前元素大的,按索引从后往前赋距离
            stack.append(i)  # 当前元素索引入栈
        return ans
    

      

    还是Next Greater Element问题,假设给定的数组是环形的。那么某个元素的Next Greater Element就有可能在它本身之前了。例如a=[2,1,2,4,3],返回b=[4,1,1,-1,4]。

    可以直接在原数组后面再挂一个原数组,还用上面的思路求解。例如 [2,1,2,4,3, 2,1,2,4,3],结果为[4,2,4,-1,4, 4,2,4,-1,-1]取前一半即可。

    或者直接用索引mod n的技巧模拟这种double数组的情况。

    ef nextGreaterElement(nums):
        if not nums:
            return []
        
        n = len(nums)
        ans = [-1] * n
        stack = []  # stack
        
        # 假设数组长度翻倍了,还是从后往前
        # 遍历后半部分求解的时候没有按循环数组取下一个大值,但在遍历到前半部分的时候就把正确结果覆盖了
        # 相当于ans更新了两遍
        for i in range(2*n-1, -1, -1):  
            while stack and stack[-1] <= nums[i % n]:  
                stack.pop()   
                
            ans[i % n] = stack[-1] if stack else -1 
            stack.append(nums[i % n])  # 索引 mod n 取到在nums中真实位置的值 
        return ans
    

      

    队列

      先进先出,可以用线性表或链表实现。一般用链表实现。队列头指针front指向头节点,队列尾指针rear指向尾节点。链表尾插头出。

    队列的顺序存储结构:循环队列解决假溢出。

    还是用数组,其实只需要让 front 和 rear 不断加 1(插入改rear、弹出改front),如果超出了地址范围就从头开始(避免假溢出),直到 front 和 rear 相遇就满了。可以采取 mod 运算实现。

    - (rear+1) % queueSize

    - (front+1) % queueSize

    双端队列:这种混合的线性数据结构拥有栈和队列各自拥有的所有功能。插入和删除操作的规律性需要由用户自己维持。

    应用:判断回文字符

    单调队列: 队列中元素单调递增或者递减。单调队列的 push 方法依然在队尾添加元素,但是要把前面比新元素小的元素都删掉。如果每个元素被加入时都这样操作,最终单调队列中的元素大小就会保持一个单调递减的顺序。

    回顾一下leetcode 239 滑动窗口最大值。窗口滑动的过程中新增一个数又减少一个数,这时候最值的更新就不是那么快可以直接算,要重新遍历窗口中的所有数据。

    class Solution:
        def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
            if not nums:
                return []
            
            n = len(nums)
            res = []
            window = []  # 双端队列实现单调队列
            
            for i in range(n):
                if i >= k and window[0] <= i-k:
                    window.pop(0)  # 从第二个窗口开始需要pop最左边滑出窗口的值
    
                while window and nums[window[-1]] <= nums[i]:  # 维护递减的单调队列
                    window.pop()  # popright
                window.append(i)   # 新的值进来,此时window最左端就是当前窗口的最大值
                
                if i >= k-1:   # 从第一个窗口开始记录结果
                    res.append(nums[window[0]])
        
            return res
    

      

    优先队列:正常入队,按优先级出队。当插入或者删除元素的时候,元素会自动排序。实现机制为堆或者二叉搜索树。

    大/小顶堆的性质就是每个父节点都大于等于/小于等于其子节点。维护堆的操作就是下沉和上浮。

    特别的一点是,元素存储在数组里,数组的索引作为指针,注意第一个索引0空着不用。给定一个节点root,其孩子节点为2*root和2*root+1;同理给定一个root,其父节点为root//2。

    大顶堆的常用操作为 insert 和 delMax;小顶堆的常用操作为 insert 和 delMin。

    以大顶堆为例,如果一个节点的val比父节点的val大,需要上浮;如果小于其孩子节点,需要下沉。

    伪代码

    def swim(k):
        while k > 1 and less(parent(k), k):  # 如果浮到顶了就不用动了,如果k比其父节点大,把k换上去
            exchange(parent(k), k)
            k = parent(k)
    
    def sink(k):
        while left(k) <= N:  # 沉到堆底就不用沉了
            larger = left(k)     # 假设左边节点较大
            if right(k) <= N and less(larger, right(k)):
                larger = right(k)   # 如果右边节点存在且更大,就更新larger
            if less(larger, k):   # 和k比较,如果k比其孩子节点都大,不用下沉了
                break
            exchange(k, larger)   # 否则的话往larger方向下沉k节点
            k = larger
    

      

    插入和删除

    def insert(e):
        N ++  # 先把元素放最后
        pq[N] = e
        swim(N)  # 上浮到正确位置
    
    
    def delMax()
        Max = pq[1]   # 堆顶最大
        exchange(1, N)  # 换到最后,删除
        pq[N] = null
        N -- 
    
        sink(1)   # 让pq[1]沉到正确位置
        return Max
    

      

    堆的各种实现形式及对应的操作效率

     常用数据结构及其各种操作的时间复杂度

  • 相关阅读:
    2017-2018-1 团队名称 第一周 作业
    20162307 2017-2018-1 《程序设计与数据结构》第3周学习总结
    20162307 2017-2018-1 《程序设计与数据结构》第1周学习总结
    20161207 结对编程-马尔可夫链-自动生成短文
    20162307 2016-2017-2《程序设计与数据结构》课程总结
    20162307 实验五 网络编程与安全
    20162307 结对编程-四则运算(挑战出题)
    20162306 2016-2017-2《程序设计与数据结构》课程总结
    实验五 网络编程与安全实验报告 20162306陈是奇
    实验补充
  • 原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11229288.html
Copyright © 2011-2022 走看看