Python数据结构-栈与深度优先搜索(Stack)

2022-01-14  本文已影响0人  ShowMeCoding

栈的顺序存储实现

class Stack:
    # 初始化空栈
    def __init__(self, size = 100):
        self.stack = []
        self.size = size
        self.top = -1     # 栈顶元素指向 -1
    # 判断栈是否为空
    def is_empty(self):
        return self.top == -1
    # 判断栈是否已满
    def is_full(self):
        return self.top + 1 = self.size
    # 入栈操作
    def push(self, value):
        if self.is_full():
            raise Exception('Stack is full!')
        else:
            self.stack.append(value)
            self.top += 1
    # 出栈操作
    def pop(self):
        if self.is_empty():
            raise Exception('Stack is empty!')
        else:
            self.stack.pop()
            self.top -= 1
    # 获取栈顶元素
    def peek(self):
        if self.empty():
            raise Exception('Stack is empty!')
        else:
            return self.stack[self.top]

栈-先进后出

class Test:
    def test(self):
        # create a stack
        stack = []

        # add element
        # time complexity: O(1)
        stack.append(1)
        stack.append(2)
        stack.append(3)
        print(stack)      # [1,2,3]

        # get the top of stack
        # time complexity: O(1)
        stack[-1]

        # remove the top of stack
        # time complexity: O(1)
        temp = stack.pop()
        print(temp)    # 3

        # get stack length
        # time complexity: O(1)
        len(stack)

        # stack is empty?
        # time complexity: O(1)
        len(stack) == 0

        # iterate stack
        # time complexity: O(n)
        while len(stack) > 0:
            temp = stack.pop()
            print(temp)         # 2 1

if __name__ == '__main__':
    test = Test()
    test.test()

3. 堆栈的应用

堆栈是算法和程序中最常用的辅助结构,其的应用十分广泛。堆栈基本应用于两个方面:

练习

20. 有效的括号

输入:s = "()[]{}"
输出:true

# 方法1:使用栈
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        # 首先判断字符串的长度是否为奇数
        if len(s)%2 == 1:
            return False
        # 用栈来完成判断
        for i in s:
            if i == '(' or i == '[' or i == '{':
                stack.append(i)
            else:
                # 先判断栈是否为空
                if len(stack) == 0:
                    return False
                top = stack.pop()
                if i == ')' and top != '(':
                    return False
                elif i == ']' and top != '[':
                    return False
                elif i == '}' and top != '{':
                    return False
        # 最后看栈中元素是否为空
        if len(stack) == 0:
            return True
        else:
            return False
227. 基本计算器 II

整数除法仅保留整数部分。

输入:s = " 3+5 / 2 "
输出:5
输入:s = " 3/2 "
输出:1

class Solution:
    def calculate(self, s: str) -> int:
        # 运算符
        cal = ['+', '-', '*', '/']
        # 辅助栈
        stack = []
        index = 0
        size = len(s)
        # 默认运算,因为除了空字符串,第一个字符串肯定为数字
        op = '+'
        # 字符串遍历
        while index < size:
            # 对于空格的灵活处理
            if s[index] == ' ':
                index += 1
                continue
            # 对于数字字符的处理
            if s[index].isdigit():
                # 将字符串转化为数字
                num = ord(s[index]) - ord('0')
                # 对于连续多位数字字符串的处理
                while index + 1 < size and s[index+1].isdigit():
                    # 需要注意及时更新索引
                    index += 1
                    num = 10*num + ord(s[index]) - ord('0')
                # 得到数字之后和运算符一起压入栈中
                if op == '+':
                    stack.append(num)
                elif op == '-':
                    stack.append(0-num)
                elif op == '*':
                    num_cal = stack.pop()
                    stack.append(num_cal*num)
                # 整数除法仅保留整数部分
                elif op == '/':
                    num_cal = stack.pop()
                    stack.append(int(num_cal/num))
            # 对于运算符的处理
            elif s[index] in cal:
                op = s[index]

            index += 1
        return sum(stack)
155. 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

# 方法1:借用一个辅助栈,同时存储两个值:当前值,当前的最小值
class MinStack:
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = []
    # 将元素压入栈
    def push(self, val: int) -> None:
        # 首先判断栈是否为空
        if len(self.stack) == 0:
            self.stack.append([val, val])
        else:
            self.stack.append([val, min(val, self.stack[-1][1])])
    # 元素弹出
    def pop(self) -> None:
       self.stack.pop()
    # 获取栈顶元素
    def top(self) -> int:
        return self.stack[-1][0]
    # 获取栈的最小值   
    def getMin(self) -> int:
        return self.stack[-1][1]
739. 每日温度

请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

# 使用一个栈
class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        stack = []
        length = len(temperatures)
        result = [0]*length
        count = 0
        for i in range(length):
            # 当栈不为空,且栈顶索引对应的温度小于当前温度
            while stack and temperatures[stack[-1]] < temperatures[i]:
                # 索引出栈
                index = stack.pop()
                result[index] = i - index
            stack.append(i)
        return result
150. 逆波兰表达式求值

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

# 中缀表达式
class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        cal = ['+', '-', '*', '/']
        for tok in tokens:
            if tok not in cal:
                stack.append(int(tok))
            else:
                a = stack.pop()
                b = stack.pop()
                if tok == '+':
                    ans = b + a
                elif tok == '-':
                    ans = b - a
                elif tok == '*':
                    ans = b * a
                elif tok == '/':
                    ans = int(b / a)
                stack.append(ans)
        return stack.pop()
394. 字符串解码

输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"

class Solution:
    def decodeString(self, s: str) -> str:
        stack_num = []
        stack_str = []
        s1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g','h', 'i', 'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
        num = 0
        res = ''
        for s_num in s:
            if s_num.isdigit():
                num = num*10 + int(s_num)
            elif s_num in s1:
                res += s_num
            elif s_num == '[':
                stack_num.append(num)
                stack_str.append(res)
                # 及时初始化
                num = 0
                res = ''
            elif s_num ==']':
                cur_num = stack_num.pop()
                cur_res = stack_str.pop()
                res = cur_res + cur_num * res
        return res
946. 验证栈序列

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

# 对于pushed序列,每次进行push操作之后,有两种选择:继续push、弹出
# 进行实际模拟,查看结果是否相符
class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack = []
        i = 0
        for p in pushed:
            stack.append(p)
            while stack and stack[-1] == popped[i]:
                stack.pop()
                i += 1
            # stack.append(p)
        # 结束的判断:新建的栈是否都为空
        return not stack

二、栈与深度优先搜索

深度优先搜索算法(Depth First Search):英文缩写为 DFS。是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

在深度优先遍历的过程中,我们需要将当前遍历节点 v 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。

2.1 基于递归实现的深度优先搜索

def dfs_recrusive(graph, start, visited):
    ```
    graph: 存储无向图的字典变量
    start: 表示当前遍历边的开始节点
    visited: 标记访问节点的set集合变量
    ```
    visted = set()
    visited.add(start)
    # 遍历图
    for end in graph[start]:
        if end not in visited:
            # 深度优先遍历节点
            dfs_recursive(graph, end, visited)

2.2 基于栈实现的广度优先搜索

def dfs_stack(graph, start):
    # 定义起始节点,并把起始节点进行标记并放在栈中
    visited = set(start)
    stack = [start]
    # 递归结束的条件:栈为空
    while stack:
        node_u = stack.pop()
        for node_v in graph[node_u]:
            if node_v not in visited:
                stack.append(node_v)
                visted.appedn(node_v)
200. 岛屿数量

输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1

class Solution:
    # 先定义深度优先搜索的算法
    def dfs(self, grid, i, j):
        # 计算行数和列数
        n = len(grid)
        m = len(grid[0])
        # 递归的终止条件
        if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0':
            return 0
        # 要及时将grid[i, j] = '1'的位置修改为'0',避免重复查找
        grid[i][j] = '0'
        # 对该点的上下左右进行检查
        self.dfs(grid, i + 1, j)
        self.dfs(grid, i, j + 1)
        self.dfs(grid, i - 1, j)
        self.dfs(grid, i, j - 1)

    def numIslands(self, grid: List[List[str]]) -> int:
        # 计数
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    # count += 1
                    self.dfs(grid, i, j)
                    count += 1
        return count
133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。
图中的每个节点都包含它的值 val(int) 和其邻居的列表(list[Node])。

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

"""
# Definition for a Node.
class Node:
    def __init__(self, val = 0, neighbors = None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []
"""
# 深度优先算法
class Solution:
    def dfs(self, node: 'Node', visited):
        # 递归的终止条件
        if node in visited:
            return visited[node]
        clone_node = Node(node.val, [])
        # 新建复制节点 clone_node,赋值为 node.val。
        visited[node] = clone_node
        for neighbor in node.neighbors:
            clone_node.neighbors.append(self.dfs(neighbor, visited))
        return clone_node

    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return node
        # 使用字典变量 visited 存储访问过的节点,键值对为 原节点 : 新节点
        visited = dict()
        return self.dfs(node, visited)

1)实战-括号匹配

输入:(((()
输出:False

def parCheck(symbolString):
    s = Stack()
    balance = True
    index = 0
    while index < len(symbolString) and balance:
        if symbolString[index] == '(':
            s.push(symbolString[index])
        else:
            if s.isEmpty():
                balance = False
            else:
                s.pop()
        index += 1
    if balance and s.isEmpty():
        return True
    else:
        return False
print(parCheck('()(((())))'))

题目复杂化

要求判别 {{{{[[[((()))]]]}}}}

def parCheck(symbolString):
    s = Stack()
    balance = True
    index = 0
    while index < len(symbolString) and balance:
        symbol = symbolString[index]
        if symbol in '({[':
            s.push(symbolString[index])
        else:
            if s.isEmpty():
                balance = False
            else:
                # s.pop()
                top = s.pop()
                if not matches(top, symbol):
                    balance = False
        index += 1
    if balance and s.isEmpty():
        return True
    else:
        return False

def matches(open, close):
    opens = "([{"
    closes = ")]}"
    return opens.index(open) == closes.index(close)

print(parCheck('()(((([{(}]))))'))

2)十进制转二进制

def divideBy2(decNumber):
    stack = Stack()

    while decNumber > 0:
        rem = decNumber % 2   # 求余数
        stack.push(rem)
        decNumber //= 2       # 取整数
    
    binnumber = ''
    while not stack.isEmpty():
        binnumber += str(stack.pop())
    
    return binnumber
print(divideBy2(5))

进阶:十进制转16以下任意进制

def divideBynum(decNumber, num):
    stack = Stack()
    digits = '0123456789ABCDEF'

    while decNumber > 0:
        rem = decNumber % num   # 求余数
        stack.push(rem)
        decNumber //= num       # 取整数
    
    binnumber = ''
    while not stack.isEmpty():
        binnumber += digits[stack.pop()]
    
    return binnumber
print(divideBynum(5, 16))

3)表达式转换

中缀表达式 A + B; A + B * C; (A + B) * C
前缀表达式 + AB ; + A * BC ; * +ABC
后缀表达式 AB + ; A B C * +; AB + C*
在中缀表达式中必须有的括号,在前缀和后缀表达式中消失了

思路
1 将中缀表达式转换为全括号的形式
2 将所有的操作符移动到子表达式所在的左括号(前缀)或者右括号(后缀)处,再删除其它所有的括号

def infixToPostfix(infixexpr):
    # 记录操作符的优先级
    prec = {}
    prec['*'] = 3
    prec['/'] = 3
    prec['+'] = 2
    prec['-'] = 2
    prec['('] = 1

    opStack = Stack()
    postfixList = []
    tokenList = infixexpr.split()    # 将表达式解析为列表
    # print(tokenList)

    for token in tokenList:
        if token in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' or token in '0123456789':
            postfixList.append(token)
        elif token == '(':
            opStack.push(token)
        elif token == ')':
            topToken = opStack.pop()
            while topToken != '(':
                postfixList.append(topToken)
                topToken = opStack.pop()
        else:
            while (not opStack.isEmpty()) and (prec[opStack.peek()] >= prec[token]):
                postfixList.append(opStack.pop())
            opStack.push(token)

    while not opStack.isEmpty():
        postfixList.append(opStack.pop())
    
    return " ".join(postfixList)       # 合成后缀表达式字符串

a = '( A + B ) * C'            # 输入字符必须用空格分开,不然无法split
print(infixToPostfix(a))

计算后序表达式

def compute1(s):
    operandStack = Stack()
    splitToken = s.split()

    for token in splitToken:
        if token in '0123456789':
            operandStack.push(int(token))
        else:
            a = operandStack.pop()
            b = operandStack.pop()
            operandStack.push(compute2(token, a, b))
    return operandStack.pop()
            
def compute2(token, a,b):
    if token == '+':
        c = a+b
    elif token == '-':
        c = a-b
    elif token == '*':
        c = a * b
    else:
        c = a/b
    return c

print(compute1('1 2 + 1 *'))     # 只能传入十以内数进行计算
上一篇下一篇

猜你喜欢

热点阅读