基于python的数据结构与算法(北京大学)课程中的代码实现(栈部分)

本文主要是对于北京大学的基于python的数据结构和算法课程中代码的实现。结构为首先自写代码,然后进行debug并和标准代码对比,分析所写不足。以加深对python特有数据结构的理解。
python中的栈的实现如下。

class Stack():
    def __init__(self):
        self.items = []
    def push(self,x):
        self.items.append(x)
    def pop(self):
        popthing = self.items.pop()
        return popthing
    def peek(self):
        return self.items[len(self.items) - 1]
    def isempty(self):
        return self.items == []
    def size(self):
        return len(self.items)
  • 栈的应用一(括号匹配问题):

    基础思想为:括号匹配:
    1.循环检查输入的字符串:
    检查到左括号则入栈,检查到右括号则判断栈顶是否为空,若为空则右括号多余,栈顶不为空则pop栈顶元素,若栈顶元素为非左括号,则丢弃,若为左括号则匹配,然后继续按此规则检查字符串
    2.循环结束后:
    检查栈是否为空,若为空则匹配过程成功,若不为空则存在多余左括号

自写代码如下

def Bracketchecker(alist):
    BracketStack = Stack()

    for item in alist:
        if item == '(':
            BracketStack.push(item)
        if item == ')':
            if BracketStack.isempty:
                print('exist additional right bracket')
                break
            else:
                BracketStack.pop()

    if BracketStack.isempty():
        return True
    else:
        print('exist additional left bracket')
        return False

a = '(())'
Bracketchecker(a)

产生bug:
在这里插入图片描述
和标准代码对比后分析原因:
对python中的列表(list)的for循环理解不足。for循环非类C语言中的简单循环,而是在循环中同步对所循环列表做出的“更新”。
在遍历的过程中,删除了其中一个元素,导致后面的元素整体前移,故有个元素成了漏网之鱼。同样的,在遍历过程中,使用插入操作,也会导致类似的错误。
因此通常不在for循环中修改循环元素(修改操作包括列表元素的插入和删除)如果非要进行类似操作,可以采用提前进行切片或复制的for循环:
例如。

for i in black_list[:]:
    black_list.pop(0)
    print(black_list)

修改方法为用while循环代替for循环,最后附上标准代码:

def parChecker(symbolString):
    s = Stack()
    balanced = True
    index = 0
    while index < len(symbolString) and balanced:
        symbol = symbolString[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
  • 括号匹配加强版(匹配所有种类的括号):
class Bracketchecker():
    def match(self,open,close):
        opens = '([{'
        closes = ')]}'
        return opens.index(open) == closes.index(close)

    def parChecker(self,symbolString):
        s = Stack()
        balanced = True
        index = 0
        while index < len(symbolString) and balanced:
            symbol = symbolString[index]
            if symbol in "([{":
                s.push(symbol)
            else:
                if s.isempty():
                    balanced = False
                else:
                    topthing =s.pop()
                    if not self.match(topthing,symbol):
                        banlanced = False
            index = index + 1

        if balanced and s.isempty():
            return True
        else:
            return False

b = Bracketchecker()
print(b.parChecker('{[()]}'))

此处的巧妙为方法match,即如何将不同括号的匹配过程分开匹配。

  • 进制转换(课程未提供标准代码):
    基本思想:
    1.二进制转为十进制:
    将整个二进制字符串入栈,入栈后从栈顶取出,乘以相应的2的index次方后,进行求和,最后得到的即为相应的十进制数
    2.十进制转换为二进制:
    若输入十进制数为0则直接输出0,若输入的数不为0,则进行递归计算,通过递归进行反向输出。
    首次代码忽略了十进制转换时为0的特殊情况,也没有考虑递归,造成了复杂的列表存储。因此直接附上修改后。
class BinaryBewteenDecimal():
    def BtoD(self,blist):
        result = 0
        index = 0
        s = Stack()
        for item in blist:
            s.push(item)
        while not s.isempty():
            result += int(s.pop())*2**(index)
            index += 1
        return result
    def DtoB(self,dec):
        result = ''
        if dec:
            result = self.DtoB(dec // 2)
            return result + str(dec % 2)
        else:
            return result

b = BinaryBewteenDecimal()
print(b.BtoD('110011'))
print(b.DtoB(51))

  • 十进制转换为十六进制
    整体思路跟二进制相同,但是需要在前面加上一个十六进制基数数组。
def baseConverter(decNumber,base):
    digits = "0123456789ABCDEF"
    s = Stack()

    while decNumber > 0:
        rem = decNumber % base
        s.push(rem)
        decNumber = decNumber // base

    newString = ""
    while not s.isempty():
        newString = newString + digits[s.pop()]

    return newString

print(baseConverter(25,16))
  • 表达式转换:
    我们通常看到的表达式如:BC,很容易知道表示B乘以C
    这种操作符(operator)介于操作数(operand)中间的表示法,称为”中缀“表示法
    但是有时候中缀表达式会引起混淆,如”A+B
    C"无法确定是A+B然后乘以C还是BC然后再加上A?
    为此引入了“优先级”的概念来消除混淆。规定高优先级的操作符先计算,相同优先级的操作符从左到右依次计算,这样A+B
    C就没有异议是A加上B与C的乘积。
    但是计算机不便处理如此复杂的规则,因此可以移动操作符的位置,得到了两种表示法“前缀”和“后缀”表示法。
    例如 A+BC可变为前缀的“ +ABC “,和后缀的” ABC*+”

算法思想:
1.从左到右扫描中缀表达式单词列表,如果单词是操作数,则直接添加到后缀表达式列表的末尾;如果单词是左括号,则压入opstack栈顶;如果单词是右括号,则反复弹出opstack栈顶操作符,加入到输出列表末尾,直到碰到左括号;如果单词是操作符,则压入opstack栈顶,但在压入栈顶之前,要比较其与栈顶操作符的优先级,如果栈顶的高于或等于它,就要反复弹出栈顶操作符,加入到输出列表末尾;直到栈顶的操作符优先级低于它。
2.中缀表达式单词列表扫描结束后,把opstack栈中的所有剩余操作符
依次弹出,添加到输出列表末尾。
3.把输出列表再用join方法合并成后缀表达式字符串,算法结束。

    def InfixToPostfix(infix):
        pre = {}
        pre['*'] = 3
        pre['/'] = 3
        pre['+'] = 2
        pre['-'] = 2
        pre['('] = 1
        pre[')'] = 1

        s = Stack()
        postinfix = []
        tokenlist = infix.split()
        for item in tokenlist:
            if item in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' or item in "01234567890":
                postinfix.append(item)
            elif item == '(':
                s.push(item)
            elif item == ')':
                while not s.peek() == '(':
                    postinfix.append(s.pop())
                s.pop()
            else:
                while pre[s.peek()] >= pre[item]:
                    postinfix.append(s.pop())
                s.push(item)

        while not s.isempty():
            postinfix.append(s.pop())
        result = "".join(postinfix)

开始未想到用字典的方式便利储存操作符的优先级,在括号的处理上不够巧妙,没有在‘)’情况中反复弹出栈顶元素时,考虑栈空的条件。借鉴了标准代码对操作数的 if。
下面借鉴标准代码,写出中缀表达式转后缀表达式的过程和后缀表达式计算的过程,并在标准代码基础上写成一个类

后缀表达式计算的思想:
1.创建空栈operandStack用于暂存操作数。
2.将后缀表达式用split方法解析为单词(token)的列表。
3.从左到右扫描单词列表,如果单词是一个操作数,将单词转换为整数int,压入operandStack栈顶;如果单词是一个操作符,就开始求值,从栈顶弹出两个操作数,先弹出的是右操作数,后弹出的是左操作数,计算后将值重新压入栈顶。
4.单词列表扫描结束后,表达式的值就在栈顶。
5.弹出栈顶的值,返回。

class Postfix():
    def InfixToPostfix(self,infix):
        pre = {}
        pre['*'] = 3
        pre['/'] = 3
        pre['+'] = 2
        pre['-'] = 2
        pre['('] = 1
        pre[')'] = 1

        s = Stack()
        postinfix = []
        tokenlist = infix.split()
        for item in tokenlist:
            if item in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' or item in "01234567890":
                postinfix.append(item)
            elif item == '(':
                s.push(item)
            elif item == ')':
                topthing = s.pop()
                while topthing != '(':
                    postinfix.append(topthing)
                    topthing = s.pop()
            else:
                while (not s.isempty())and([s.peek()] >= pre[item]):
                    postinfix.append(s.pop())
                s.push(item)

        while not s.isempty():
            postinfix.append(s.pop())
        result = "".join(postinfix)

    def CaculatePostfix(self,postinfix):
        operandStack = Stack()
        tokenList = postinfix.split()

        for token in tokenList:
            if token in "0123456789":
                operandStack.push(int(token))
            else:
                operand2 = operandStack.pop()
                operand1 = operandStack.pop()
                result = self.doMath(token, operand1, operand2)
                operandStack.push(result)
        return operandStack.pop()

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

你可能感兴趣的:(基于python的数据结构与算法(北京大学)课程中的代码实现(栈部分))