• 数据结构之栈的介绍以及一些简单应用(上)


    线性结构之栈

    在说栈的前提下,我们先来说说什么是线性结构,线性结构是一种有序数据项的集合,其中每个数据项都有唯一的前驱和后继,除了第一个没有前驱,最后一个没有后继,新的数据项加入到数据集中时,只会加入到原有某个数据项之前或者之后,具有这种性质的数据集,就成为线性结构。

    线性结构总有两端,在不同的情况下,两端的称呼也不同,有时候称为"左"、“右”端,有时候称为“前”、“后”端、有时候称为为“顶”、"底"端。

    两端的称呼并不是关键,不同线性结构的关键区别在于数据项的增减方式。有的结构只允许数据从一端添加,而由的结构则允许数据项从两端移除。

    栈Stack,队列Queue,双端队列Deque和列表List,这些数据集的共同点在于,数据项之间只存在先后的次序关系,都是线性结构。

    栈Stack

    什么是栈

    一种有次序的数据项集合,在栈中,数据项的加入和移除都仅仅发生在同一端,这一端叫栈“顶top”,另一端叫栈“底base”。

    日常生活中有很多栈的应用,比如盘子,书堆等等。

    距离栈底最近的数据项,留在栈中的时间就越长,而最新加入栈的数据项会被最先移除。

    这种次序通常称为“后进先出“,这是一种基于数据项保存时间的次序,时间越短的离栈顶越近,而时间越长的离栈底越近。

    栈的特性:反转次序

    我们观察一个由混合的Python原生数据对象形成的栈,如下图所示,进栈和出栈的次序正好相反。

    这种访问次序反转的特性,我们在某些计算机操作上碰到过,浏览器的“后退”按钮,最先后退的是最近访问的网页。

    抽象数据类型Stack

    抽象数据类型“栈”是一个有次序的数据集,每个数据项仅从“栈顶”一端加入到数据集中,从数据集中移除,栈具有后进先出的特性。

    • 抽象数据类型“栈”定义为如下的操作:

      Stack():创建一个空栈,不包含任何数据项

      push(item):将item加入栈顶,无返回值

      pop():将栈顶数据项移除,并返回,栈被修改

      peek():"窥视"栈顶数据项,返回栈顶的数据项但不移除,栈不被修改

      isEmpty():返回栈是否为空栈

      size():返回栈中有多少个数据项

    • 操作样例

      Stack Operation Stack Contents Return Value
      s=Stack() [] Stack object
      s.isEmpty() [] True
      s.push(4) [4]
      s.push("dog") [4,"dog"]
      s.peek() [4,"dog"] "dog"
      s.push(True) [4,"dog",True]
      s.size() [4,"dog",True] 3
      s.isEmpty() [4,"dog",True] False
      s.push(8.4) [4,"dog",True,8.4]
      s.pop [4,"dog",True] 8.4
      s.pop [4,"dog"] True
      s.size() [4,"dog"] 2

    用Python实现ADT Stack

    在清楚地定义了抽象数据类型Stack之后,我们看看如何用Python来实现它,Python的面向对象机制,可以用来实现用户自定义类型。

    将Stack实现为Python的一个Class

    将Stack的操作实现为Class的方法

    由于Stack是一个数据集,所以可以采用Python的原生数据集来实现,我们选用最常用的数据集List来实现

    一个细节:Stack的两端对应List的设置。可以将List的任意一端(index=0或-1)设置为栈顶,我们选用List的末端(index=-1)作为栈顶,这样栈的操作就可以通过list的append和pop来实现。

    class Stack:
        def __init__(self):
            self.items = []
    
        def isEmpty(self):
            return self.items == []
    
        def push(self, item):
            return 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)
    
    
    s = Stack()
    print(s.isEmpty())  # True
    s.push(4)
    s.push("dog")
    print(s.peek())  # dog
    s.push(True)
    print(s.size())  # 3
    print(s.isEmpty()) # False
    s.push(8.4)  
    print(s.pop())  # 8.4
    print(s.pop())  # True
    print(s.size())  # 2
    

    ADT Stack的另一种实现

    如果我们把List的另一端(首端index=0)作为Stack的栈顶,同样也可以实现Stack。

    不同的实现方案保持了ADT接口的稳定性,但性能有所不同,栈顶首端的版本其push/pop的复杂度为O(n),而栈顶尾端的实现,其push/pop的复杂度为O(1)。

    class Stack:
        def __init__(self):
            self.items = []
    
        def isEmpty(self):
            return self.items == []
    
        def push(self, item):
            return self.items.insert(0, item)
    
        def pop(self):
            return self.items.pop(0)
    
        def peek(self):
            return self.items[len(self.items)-1]
    
        def size(self):
            return len(self.items)
    

    栈的应用

    简单括号匹配

    我们都写过这样的表达式:(5+6)*(7+8)/ (4+3)

    当然,括号的使用必须遵循“平衡”规则,首先,每个开括号要恰好对应一个闭括号;其次,每对开括号要正确的嵌套。对括号是否正确匹配的识别,是很多语言编译器的基础语法。

    下面我们看看如何构造括号匹配识别算法:

    从左到右扫描括号串,最新打开的左括号,应该匹配最先遇到的右括号,第一个左括号(最早打开),就应该匹配一个右括号(最后遇到),次序反转的识别,正好符合栈的特性。

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

    更多种括号匹配

    在实际的应用中,我们会碰到更多的括号,如Python中列表所用的方括号“[]”,字典所用的花括号“{}”,元祖和表达式所用的圆括号"()"。

    这些不同的括号可能会混合在一起使用,因此就要注意各自的开闭匹配情况。

    那么我们可以把上面的代码改成通用匹配不?

    我们依旧是碰到各种左括号仍然入栈,碰到各种右括号的时候需要判断栈顶的左括号是否跟它是同一类?

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

    十进制转二进制

    我们经常需要将整数在二进制和十进制之间转换,十进制转换为二进制,采用的是“除以2求余数”的算法,将整数不断除以2,每次得到的余数就是由低到高的二进制位。

    “除以2”的过程,得到的余数是从低到高的次序,而输出则是从高到低,所以需要一个栈来反转次序。

    def divideBy2(number):
        s = Stack()
        while number > 0:
            rem = number % 2  # 求余
            s.push(rem)
            number //= 2  # 整除2
    
        binString = ""
        while not s.isEmpty():
            binString += str(s.pop())
        return binString
    

    扩展到更多进制转换

    十进制转换为二进制的算法,很容易可以扩展到转换为任意N进制,只需要将"除以2求余数"算法改为“除以N求余数”算法就可以了。

    def baseConverter(number,base):
        digits = "0123456789ABCDEF"
        s = Stack()
        while number > 0:
            rem = number % base
            s.push(rem)
            number //= base
    
        newString = ""
        while not s.isEmpty():
            newString += digits[s.pop()]
    
        return newString
    
  • 相关阅读:
    karto 资料
    底盘运动学
    QT中搜索文件列表
    Qt中引入boost库
    AGV调度理论链接
    qt开发杂记
    C++代码读取ping的网络延时
    结构体解析数据
    QJson 的简单使用
    Vue中问题总结 与未解决问题总结
  • 原文地址:https://www.cnblogs.com/huiyichanmian/p/12961186.html
Copyright © 2020-2023  润新知