线性数据结构---栈

写在前面

线性数据结构指的是元素之间的顺序是由插入和删除所决定的,一个元素被添加,那么它相对于前后项的位置就不变了。在线性数据结构这部分,我们主要了解四种类型,栈,队列,deque,列表,均用python实现,教材Problem Solving with Algorithms and Data Structures using Python http://interactivepython.org/courselib/static/pythonds/index.html

1、什么是栈

栈是项的有序集合, 它的特点是后进先出(LIFO),添加和移除总发生在一端,我们称之为'顶部',相对应的另一端称之为'底部'。

figure1.png

这种反转的属性可以用到很多地方。 比如当你在浏览网页时,你浏览的网址会被存入栈中,最先浏览的网址在底部,现在浏览的网址在顶部。当你点击网页上的返回键的时候,这些网页按相反的顺序被返回回来。

2、栈的抽象数据类型

在栈中我们定义了以下操作:
stack(): 创建了一个新的空栈
isEmpty(): 判断栈是否为空,为空返回“True”, 不为空返回“False”
push(item): 将item添加到栈顶,它没有返回项,参数为item
pop(): 删除栈顶项,返回item,没有参数
peek(): 返回栈顶项,但并不删除,没有参数
size(): 返回栈中item的数量,不需要参数,返回一个整数

我们使用了列表作为底层实现,列表中的pop()、append()可以实现添加和删除操作。

class stack:
    def __init__(self):
        self.items=[]
    def isEmpty(self): ## 简洁一点的写法return self.items ==[]
        if len(self.items)==0:
            return True
        else:
            return False
    def push(self,item): #将一个新的项添加到栈的顶部,它需要item做参数并不返回任何内容
        self.items.append(item)
    def pop(self): #删除栈顶项,返回item,不需要参数
        item = self.items.pop()
        return item
    def peek(self):#返回栈顶的item,但并不会删除它,不需要参数
        item = self.items[len(self.items)-1]
        return item
    def size(self):
        return len(self.items)
stack-test.png

3、适用问题

在这部分,我们用几个例子说明栈的应用场景。

(1)括号匹配

在使用括号的时候,左括号和右括号成对出现,我们要做的就是对于一连串的括号,判断它们是否匹配。

括号示例.png

为什么括号匹配适合用栈处理呢?
在输入的时候,我们先输入左括号,当出现第一个右括号时,最后出现的左括号与之匹配,而与最后一个右括号相匹配的是最早出现的左括号。右括号以相反的顺序匹配左括号,这是一个可以用栈解决的问题。
figure2.png

算法流程
假设输入为一个只包含括号的字符串,遇到左括号,添加到栈,遇到右括号,则删除栈顶的左括号,这样操作之后,如果括号时匹配的,当右括号使用完,那么栈应该为空。若右括号有剩余,栈为空 或者 栈不为空,右括号无剩余,那么则不匹配。

def parChecker(symbolString):
    s = stack()
    for item in symbolString:
        if item =="(":
            s.push(item)
        elif item == ")":
            if not s.isEmpty():
                s.pop()
            else:  return False # 如果栈为空,但是还有右括号剩余,证明不匹配
    if not s.isEmpty():
        return  False # 如果右括号都没有剩余,但是栈中还有左括号,证明不匹配
    return True
example1 test.png
延伸 :符号匹配

如果我们匹配的不仅仅是括号,还有 [ ],{ },这就是我们接下来要说的符号匹配了,两者的思想都差不多。唯一一点区别在于,我们遇到的不仅仅是“)”,还有"]", "}",遇到这些符号,我们要删除栈中相应的"(" "]" "}"

def parChecker(symbolString):
    s = stack()
    symbollist =[ "(","[","{"]
    symboldict ={")":0,"]":1,"}":2}
    for item in symbolString:
        if item in symbollist:
            s.push(item)
        else:
            if s.isEmpty():
                return False# 如果栈为空,但是还有右括号剩余,证明不匹配
            else:
                if symboldict[item] == symbollist.index(s.peek()): # 使用索引值和字典值匹配
                    s.pop()
                else: return False # 字符与栈中字符不匹配
    if not s.isEmpty():
       return  False # 如果右括号都没有剩余,但是栈中还有左括号,证明不匹配
    return True
example2 test.png

( 2 ) 十进制转化为二进制

为什么进制转换适合用栈处理呢?
进制转换过程中,我们不断除以进制数,得到余数,最后将余数倒序,就得到了转换后的数。这里再一次出现了反转属性,表明栈可能是解决这个问题的数据结构。

二进制.png

算法流程
假设我们要处理的数字为num, num不断除以2得到余数,添加到栈中,最后将栈中的余数弹出,得到结果。

def divideby2 (num):
    s=stack()
    binstring=""
    while num:
        res=num%2
        s.push(res)
        num = num//2
    while not s.isEmpty():
        binstring+=str(s.pop())
    return binstring
延伸: 任意进制
def dividebybase(num,base):
    s=stack()
    binstring=""
    digits="0123456789ABCDEF"
    while num:
        res=num%base
        s.push(res)
        num = num//base
    while not s.isEmpty():
        binstring+=str(digits[s.pop()])
    return binstring

(3) 中缀表达式转后缀表达式

我们平时所写的表达式称为中缀表达式,表示运算符号在两个操作数的中间。同时我们也可以将其写为后缀表达式和前缀表达式,前缀表达式 将所有的运算符都放置在操作数之前,后缀表达式将所有的运算符放在操作数之后。


后缀表达式.png

前缀表达式和后缀表达式.png

为什么中缀表达式转为后缀表达式适合用栈处理?
在书中的解释为,因为运算符号存在优先级,所以转换成后缀表达式会出现反转,考虑可以使用栈来处理这样的情况。若只考虑一般的情况,后缀表达式的运算符号顺序是与中缀表达式相反的,从某种程度上说可以用栈来处理。
算法流程
1、新建一个栈s用来保存运算符号和括号,新建一个空列表postfixlist用来保存转换后的中缀表达式
2、将要转换的中缀字符串用字符串拆分方法转换为标记列表
3、遍历标记列表中的每个元素:
          若为操作数,则直接添加到postfixlist
          若为"("左括号,则直接添加到栈s中
          若为操作符,添加到栈s,但要先比较操作符与栈中操作符的优先级,弹出栈中具有更高优先级的操作符到postfixlist,再添加到栈s
          若为")",则弹出栈中的操作符直至删除左括号
4、当输入表达式被完全处理时,检查栈中是否还有操作符,若有的话则全部弹出添加到postfixlist

def infixtopostfix(infixexpr):
    infixlist = infixexpr.split()
    s = stack()
    prec={"*":2,"/":2,"+":1,"-":1,"(":0}
    postfixlist=[]
    for item in infixlist:
        if item =="(":
            s.push(item)
        elif item == ")":
            flag=True
            while not s.isEmpty() and flag:
                if s.peek()!="(":
                    postfixlist.append(s.pop())
                else:
                    flag = False
                    s.pop() #当时写程序的时候忘记弹出左括号了
        elif item in ["+", "-" ,"*","/"]:
            sign = True
            while not s.isEmpty() and sign:
                if prec[s.peek()]>prec[item]:
                    postfixlist.append(s.pop())
                else: sign = False
            s.push(item)
        else:
            postfixlist.append(item) #若是操作符则加入到列表中
    while not s.isEmpty():
        postfixlist.append(s.pop())
    return " ".join(postfixlist)
test infixtopostfix.png

str.join(sequence) 指的是用字符str 链接序列sequence中的元素新生成的字符串,sequence是一个对象,如果有多个元素,那么就是元组或者列表,字符串也是可以的。
join.png
延伸:后缀表达式求值

因为运算符需要两个操作数,它只与最近的两个操作数有关,而与之前进入的数无关,有点反转的感觉,用栈处理时合适的。
给出一个后缀表达式如何求值呢? 我们可以将操作数添加到栈中,待遇到了操作符,就从栈中弹出两个操作数,在进行运算,运算后的结果再添加到栈中,以便后续运算。最后栈中剩余的数字,就是所求的结果。

def postfixeval(postfixexpr):
    postfixlist = postfixexpr.split()
    s = stack()
    for item in postfixlist:
        if item in ["+","-","*","/"]:
            op2=s.pop()
            op1=s.pop()
            res=calculation(op1,op2,item)
            s.push(res)
        else:
            s.push(int(item))
    return s.pop()
def calculation(op1,op2,item):
    if item=="+":
        return op1+op2
    elif item=="-":
        return op1-op2
    elif item=="*":
        return op1*op2
    else: return op1/op2
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • 栈的规则 先进后出。如:依次入栈顺序为:A,B,C,D;怎出栈顺序为:D,C,B,A . 二叉树和表达式 表达式的...
    zhangivon阅读 829评论 0 0
  • 基础知识 基本概念 常见数据结构 栈和队列 栈Stack 队列Queue 树和堆 树的定义 树(tree)是包含n...
    passwd_阅读 1,460评论 0 2
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,226评论 0 4
  • 栈模型## 栈(Stack)是限制插入和删除只能在一个位子上进行的表,该位子是表的末端,叫做栈的顶(top)。对栈...
    kylinxiang阅读 1,653评论 0 1
  • 春风细雨催, 只等春来归; 山河看雾里, 雾里藏着谁。
    他说这不是诗阅读 141评论 0 0