《Python数据结构与算法分析》读书笔记三——基本数据结构(一)

文章目录

    • 3.1 本章目标
    • 3.2 何谓线性数据结构
    • 3.3 栈
      • 3.3.1 何谓栈
      • 3.3.2 栈抽象数据类型
      • 3.3.3 用Python实现栈
      • 3.3.4 匹配括号
      • 3.3.5 普通情况:匹配符号
      • 3.3.6 将十进制数转换成二进制数
      • 3.3.7 前序、中序和后序表达式
        • 1.将中序转换为前序和后序
        • 2.从中序到后序的通用转换法
        • 3.计算后序表达式
    • 3.4 队列

3.1 本章目标

  • 理解栈、队列、双端队列、列表等抽象数据类型。
  • 能够使用Python列表实现栈、队列和双端队列。
  • 理解基础线性数据结构的性能。
  • 理解前序、中序和后序表达式。
  • 使用栈来计算后续表达式。
  • 使用栈将中序表达式转换成后续表达式。
  • 使用队列进行基本的时序模拟。
  • 理解栈、队列以及双端队列适用于解决何种问题。
  • 能够使用“节点和引用“模式将列表实现为链表。
  • 能够从性能方面比较自己的链表实现与Python的列表实现。

3.2 何谓线性数据结构

​ 栈、队列、双端队列、列表都是有序的数据集合,其元素的顺序取决于添加顺序或移除顺序。若某个元素被添加进来,其与前后元素的相对位置将保持不变,这样的数据集合通常被称为线性数据结构

3.3 栈

3.3.1 何谓栈

​ 栈也称”下堆栈“,是一种有序集合,它的添加操作和移除操作总发生在同一端,即”顶端“,另一端则为”底端“,元素离底端越近,其在栈中存在的时间则越长。

特性 排序原则——LIFO(last-in-first-out),即后进先出。

实例 厨房里堆放的盘子、浏览器的多层网页(URL被存在栈中,点击返回按钮时可以反向浏览网页)。

应用 反转特性:可以用来将元素反向重排。

3.3.2 栈抽象数据类型

​ 如前所述,栈的操作顺序时LIFO,它支持以下操作。

  • Stack() 创建一个空栈。无参数,返回一个空栈。
  • push(item) 添加一个元素到栈的顶端。参数item,无返回值。
  • pop() 移除栈顶端元素。无参数,返回顶端元素。
  • peek() 得到栈顶端元素(不移除)。无参数,返回顶端元素。
  • isEmpty() 检查栈是否为空。无参数,返回布尔值。
  • size() 得到栈中元素数目。无参数,返回整数。

3.3.3 用Python实现栈

​ 在Python中实现抽象数据类型时,就可以创建新类,其操作通过方法实现。

​ 使用Python提供的简单、强大的原生集合——列表来实现栈(假设列表的尾部是栈的顶端):

#代码3-1 Python实现栈
class Stackdef __init__(self):
        self.items = []
    
    def isEmpty(self):
        return self.items == []
    
    def push(self, item):
        self.items.append(item)
    
    def pop(self):
        return self.items.pop()
    
    def peek(self):
        return self.items[len(self.items-1)]
    
    def size(self):
        return len(self.items)

​ 另一种实现方式(假设列表的头作为栈的顶端):

#代码3-2 Python实现栈
class Stackdef __init__(self):
        self.items = []
    
    def isEmpty(self):
        return self.items == []
    
    def push(self, item):
        self.items.insert(0, item)
    
    def pop(self):
        return self.items.pop(0)
    
    def peek(self):
        return self.items[0]
    
    def size(self):
        return len(self.items)

​ 这种实现方式中不能直接用appendpop方法,而是用insertpop方法显式地访问下标为0的元素。

​ 虽然两种方法实现的功能相同,都能完成栈支持的方法,但在性能方面有差异:append()pop()方法的时间复杂度都是O(1);而insert(0)pop(0)方法的时间复杂度都是O(n)

3.3.4 匹配括号

括号匹配指每一个左括号都有与之对应的一个右括号,并且括号对有正确的嵌套关系。

​ 正确匹配的括号串:
(()()()()) (((()))) (()(()()))

​ 不匹配的括号串:
(((((()) ()))) ())()()

​ 用完成括号匹配算法:

思想:从一个空栈开始,依次扫描括号串,遇到左括号则push()压入栈中,遇到右括号则pop()从栈中取出;如果处理完匹配的括号串之后栈未空,则不匹配,反之则匹配。

#代码3-3 匹配括号
from pythonds.basic import Stack

def parChecker(str):
    s = Stack()
    balanced = True
    index = 0
    while index < len(str) and balanced:
        symbol = str[index]
        if symbol == "(":
            s.push(symbol)
        else:
            if s.isEmpty():
                balanced = False
            else:
                s.pop()
        index = index + 1
    if balanced and s.isEmpty():
        return True
    else:
        return False

3.3.5 普通情况:匹配符号

​ 将上述情况扩展到中括号、花括号等情况下(增加一个匹配方法):

#代码3-4 匹配符号
from pythonds.basic import Stack

def parChecker(symStr):
    s = Stack()
    balanced = True
    index = 0
    while index < len(symStr) and balanced:
        symbol = symStr[index]
        if symbol in "([{":
            s.push(symbol)
        else:
            if s.isEmpty():
                balanced = False
            else:
                top = s.pop()
                if not matches(top, symbol):
                    balanced = False
        index = index + 1
    if balanced and s.isEmpty():
        return True
    else:
        return False

def matches(left, right):
    lefts = "([{"
    rights = ")]}"
    return lefts.index(left) == rights.index(right)

3.3.6 将十进制数转换成二进制数

​ 将一个十进制数不停除以2,余数记录下来,反过来就是其二进制的数值,因此可以用栈来解决该问题。例如将十进制数(233)10转换为二进制数(11101001)2的过程:

​ 将十进制数转换成任意进制数(基数小于等于16):

#代码3-5 转换进制
from pythonds.basic import Stack
def baseConvert(num, base):
    digits = "0123456789ABCDEF"
    remstack = Stack()
    while num > 0:
        rem = num % base
        remstack.push(rem)
        num = num // base
        
    newStr = ""
    while not remstack.isEmpty():
        newStr = newStr + digits[remstack.pop()]
    
    return newStr

3.3.7 前序、中序和后序表达式

中序表达式 前序表达式 后序表达式
A+B +AB AB+
A+B*C +A*BC ABC*+
(A+B)*(C+D) *+AB +CD AB+CD+*

​ 由于前序和后序表达式中的运算符所对应的操作数是明确的,表达式的运算顺序完全由运动算符的位置来决定,只有中序表达式需要额外的括号来消除歧义。因此,中序表达式是最不理想的算式表达法

1.将中序转换为前序和后序

​ 若要将任意复杂的中序表达式转换成前序或后序表达式,只需先将中序表达式写作完全括号表达式,然后将括号内的运算符移到左括号处(前序表达式)或者右括号处(后序表达式)。

2.从中序到后序的通用转换法

​ 从左往右扫描中序表达式时,用栈来保存运算符。这样可以提供反转特性,让中序表达式中先低级后高级的运算变成先高级运算、后低级运算。每当遇到一个新的运算符时,都需要对比其与栈中运算符的优先级:“*”,“/”为最高级,“+”,“-”次之,“(”左括号的优先级最低。

​ 假设中序表达式中的运算符标记有*、/、+和-,括号标记符有(和),操作数标记有A、B、C等。下面的步骤会生成一个后序标记串

  1. 创建用于保存运算符的空栈opstack,以及一个用于保存结果的空列表。
  2. 使用字符串方法split将输入的中序表达式转换成一个列表。
  3. 从左到右扫描这个标记列表。
    • 如果标记是操作数,则将其添加到结果列表的末尾。
    • 如果标记是左括号,将其压入opstack栈中。
    • 如果标记是右括号,反复从opstack栈中移除元素,直到移除对应的左括号。将从栈中取出的每一个运算符都添加到结果列表的末尾。
    • 如果标记是运算符,将其压入opstack栈中。但是,在这之前,需要先从栈中取出优先级更高或相同的运算符,并将它们添加到结果列表的末尾。
  4. 当处理完输入表达式后,检查opstack,将其中所有残留的运算符全都添加到结果列表的末尾。

​ 在Python中实现这一算法:

#代码3-6 中序表达式转换后序表达式
from pythonds.basic import Stack
import string

def midTopost(midExpr):
    prec = {}
    prec["*"] = 3
    prec["/"] = 3
    prec["+"] = 2
    prec["-"] = 2
    prec["("] = 1

    opStack = Stack()
    postExprList = []
    midExprList = midExpr.split()

    for ch in midExprList:
        if ch in string.ascii_uppercase:
            postExprList.append(ch)
        elif ch == "(":
            opStack.push(ch)
        elif ch == ")":
            topch = opStack.pop()
            while topch != "(":
                postExprList.append(topch)
                topch = opStack.pop()
        else:
            while (not opStack.isEmpty()) and \
                (prec[opStack.peek()] >= prec[ch]):
                postExprList.append(opStack.pop())
            opStack.push(ch)
    
    while not opStack.isEmpty():
        postExprList.append(opStack.pop())

    return "".join(postExprList)

3.计算后序表达式

​ 上述操作将中序表达式转换成后序表达式后,接下来要考虑的就是如何计算后序表达式。同样是利用栈的反转特性完成算法,不过当扫描后序表达式时,需要保存操作数,而不是运算符。

​ 每当遇到运算符时,意味着需要将最近遇到的两个操作数相乘。通过执行两次出栈操作,可以得到相应的操作数,然后进行运算后将运算结果再压入栈中,作为后续运算的操作数。当处理完最后一个运算符后,栈中只剩下一个值,将其取出并作为表达式运算的返回值。

​ 假设后序表达式中的运算符标记有*、/、+和-,操作数标记是一位的整数值。下面的步骤会计算结果,并返回一个整数值。

  1. 创建空栈opStack。
  2. 使用字符串方法split将输入的后序表达式转换成一个列表。
  3. 从左往右扫描这个标记列表。
    • 如果标记是操作数,将其转换成整数并压入opStack栈中。
    • 如果标记是运算符,从opStack栈中取出两个操作数。第一次取出右操作数,第二次取出左操作数,进行相应的算术运算,再将结果压入opStack栈中。
  4. 当处理完输入表达式后,栈中的值就是结果,将其作为返回值。

在Python中实现这一算法:

#代码3-7 计算后序表达式
from pythonds.basic import Stack
def postExprCal(postExpr):
    opStack = Stack()
    postExprList = postExpr.split()

    for ch in postExprList:
        if ch in "0123456789":
            opStack.push(int(ch))
        else:
            op2 = opStack.pop()
            op1 = opStack.pop()
            result = doMath(ch, op1, op2)
            opStack.push(result)
    return opStack.pop()

def doMath(op, op1, op2):
    if op =="*":
        return op1*op2
    elif op == "/":
        return op1/op2
    elif op == "+":
        return op1+op2
    elif op == "-":
        return op1-op2

3.4 队列

待续。

你可能感兴趣的:(学习笔记)