线性数据结构---队列,栈 随笔
我们从四个简单但重要的概念开始研究数据结构。栈,队列,deques(双向队列), 列表是一类数据的容器,它们数据元素之间的顺序由添加或删除的顺序决定。一旦一个数据元素被添加,它相对于前后元素一直保持该位置不变。诸如此类的数据结构被称为线性数据结构。
线性数据结构有两端,有时被称为左右,某些情况被称为前后。你也可以称为顶部和底部,名字都不重要。将两个线性数据结构区分开的方法是添加和移除元素的方式,特别是添加和移除元素的位置。例如一些结构允许从一端添加元素,另一些允许从另一端移除元素。
栈
概念:栈(有时称为“后进先出栈”)是一个元素的有序集合,其中添加移除新元素总发生在同一端。这一端通常称为“顶部”。与顶部对应的端称为“底部”。栈的底部很重要,因为在栈中靠近底部的元素是存储时间最长的。最近添加的元素是最先会被移除的。这种排序原则有时被称为 LIFO,后进先出。它基于在集合内的时间长度做排序。较新的项靠近顶部,较旧的项靠近底部。
案例:栈的例子很常见。几乎所有的自助餐厅都有一堆托盘或盘子,你从顶部拿一个,就会有一个新的托盘给下一个客人。想象桌上有一堆书, 只有顶部的那本书封面可见,要看到其他书的封面,只有先移除他们上面的书。
栈的分析与应用:
- 分析:和栈相关的最有用的想法之一来自对它的观察。假设从一个干净的桌面开始,现在把书一本本叠起来,你在构造一个栈。考虑下移除一本书会发生什么。移除的顺序跟刚刚被放置的顺序相反。栈之所以重要是因为它能反转项的顺序。插入跟删除顺序相反。
- 应用:每个 web 浏览器都有一个返回按钮。当你浏览网页时,这些网页被放置在一个栈中(实际是网页的网址)。你现在查看的网页在顶部,你第一个查看的网页在底部。如果按‘返回’按钮,将按相反的顺序浏览刚才的页面。
Python实现栈
栈的抽象数据类型应该由以下结构和操作定义。栈操作如下:
- Stack() 创建一个空的新栈。 它不需要参数,并返回一个空栈。
- push(item)将一个新项添加到栈的顶部。它需要 item 做参数并不返回任何内容。
- pop() 从栈中删除顶部项。它不需要参数并返回 item 。栈被修改。
- peek() 从栈返回顶部项,但不会删除它。不需要参数。 不修改栈。
- isEmpty() 测试栈是否为空。不需要参数,并返回布尔值。
- size() 返回栈中的 item 数量。不需要参数,并返回一个整数。
代码实现:
Python 中的列表类提供了有序集合机制和一组方法。例如,如果我们有列表 [2,5,3,6,7,4],我们只需要确定列表的哪一端将被认为是栈的顶部。一旦确定,可以使用诸如 append 和 pop 的列表方法来实现操作。
# 创建栈
class Stack():
def __init__(self):
self.items = []
# 入栈(压栈)
def push(self,item):
self.items.append(item)
# 出栈
def pop(self):
return self.items.pop()
# 栈指针回到栈顶部
def peek(self):
return len(self.items) - 1
# 判断栈是否为空,空返回Ture,否则为False
def isEmpty(self):
return self.items == []
# 获取栈长度
def size(self):
return len(self.items)
测试应用:
from basic.stack import Stack s=Stack() print(s.isEmpty()) s.push(4) s.push('dog') print(s.peek()) s.push(True) print(s.size()) print(s.isEmpty()) s.push(8.4) print(s.pop()) print(s.pop()) print(s.size())
栈的应用实例(URL存放机制)
模拟网站url地址存放机制:
s = Stack()
# 点击进入一个新的网页
def getRequest(url):
s.push(url)
# 查看当前网页
def showCurenrUrl():
print('当前页面展示的url:'+s.pop())
# 回退到前一个网页
def back():
print('回退按钮点击后显示的url:',s.pop())
getRequest('www.1.com')
getRequest('www.2.com')
getRequest('www.3.com')
showCurenrUrl()
back()
back()
# 结果:>>>
当前页面展示的url:www.3.com 回退按钮点击后显示的url: www.2.com 回退按钮点击后显示的url: www.1.com
双端队列(Deque)
概念:deque(也称为双端队列)是与队列类似的项的有序集合。它有两个端部,首部和尾部,并且项在集合中保持不变。
特性:deque 特殊之处在于添加和删除项是非限制性的。可以在前面或后面添加新项。同样,可以从任一端移除现有项。在某种意义上,这种混合线性结构提供了单个数据结构中的栈和队列的所有能力。
注意:即使 deque 可以拥有栈和队列的许多特性,它不需要由那些数据结构强制的 LIFO 和 FIFO 排序。这取决于你如何持续添加和删除操作。
Python实现Deque
Deque的抽象数据类型应该由以下结构和操作定义。其中元素可以从首部或尾部的任一端添加和移除。Deque操作如下:
-
- Deque() 创建一个空的新 deque。它不需要参数,并返回空的 deque。
- addFront(item) 将一个新项添加到 deque 的首部。它需要 item 参数 并不返回任何内容。
- addRear(item) 将一个新项添加到 deque 的尾部。它需要 item 参数并不返回任何内容。
- removeFront() 从 deque 中删除首项。它不需要参数并返回 item。deque 被修改。
- removeRear() 从 deque 中删除尾项。它不需要参数并返回 item。deque 被修改。
- isEmpty() 测试 deque 是否为空。它不需要参数,并返回布尔值。
- size() 返回 deque 中的项数。它不需要参数,并返回一个整数。
创建双端队列:
# 创建双端队列
class Dequeue():
def __init__(self):
self.items = []
# 从队列头部插入数据
def addFont(self,item):
self.items.insert(0,item)
# 从队列尾部插入数据
def addRear(self,item):
self.items.append(item)
# 队头取出元素
def removeFont(self):
return self.items.pop()
# 队尾取元素
def removeRear(self):
return self.items.pop(0)
# 获取队列长度
def size(self):
return len(self.items)
队列测试:
q = Dequeue()
q.addFont(1)
q.addFont(2)
q.addFont(3)
# print(q.removeFont())
# print(q.removeFont())
# print(q.removeFont())
print(q.removeRear())
print(q.removeRear())
print(q.removeRear())
# 结果>>>
3
2
1
双端队列的应用案例(回文检查)
回文检测:设计程序,检测一个字符串是否为回文。
回文:回文是一个字符串,读取首尾相同的字符,例如,radar toot madam
。
分析:该问题的解决方案将使用 deque 来存储字符串的字符。我们从左到右处理字符串,并将每个字符添加到 deque 的尾部。在这一点上,deque 像一个普通的队列。然而,我们现在可以利用 deque 的双重功能。 deque 的首部保存字符串的第一个字符,deque 的尾部保存最后一个字符。我们可以直接删除并比较首尾字符,只有当它们匹配时才继续。如果可以持续匹配首尾字符,我们最终要么用完字符,要么留出大小为 1 的deque,取决于原始字符串的长度是偶数还是奇数。在任一情况下,字符串都是回文。
class Dequeue(): def __init__(self): self.items = [] def addFront(self,item): self.items.insert(0,item) def addRear(self,item): self.items.append(item) def removeFront(self): return self.items.pop() def removeRear(self): return self.items.pop(0) def size(self): return len(self.items) def isHuiWen(s): de = Dequeue() ex = True for sr in s: de.addFront(sr) while de.size() > 1: if de.removeFront() != de.removeRear(): ex = False break return ex print(isHuiWen("abcba"))
队列的应用实例(烫手的山芋)
实验规则:烫手山芋游戏介绍:6个孩子围城一个圈,排列顺序孩子们自己指定。第一个孩子手里有一个烫手的山芋,需要在计时器计时1秒后将山芋传递给下一个孩子,依次类推。规则是,在计时器每计时7秒时,手里有山芋的孩子退出游戏。该游戏直到剩下一个孩子时结束,最后剩下的孩子获胜。请使用队列实现该游戏策略,排在第几个位置最终会获胜。
分析:
- 为了模拟这个圈,我们可以使用队列。假设游戏开始时,排在队列中的第一个(队首)的孩子手里拿着山芋。游戏开始后,拿着山芋的孩子出队列然后再入队列,将山芋传递给下一个孩子。每当山芋到队首孩子手里后,队首的孩子先出队列再入队列,依次类推。当传递六次后,手里有山芋的孩子淘汰,游戏继续,继续传递山芋。
- 手里有山芋的孩子淘汰后,队列指针指向下一个孩子,保证手里有山芋的孩子永远站在队列的头部
代码实现:
q = Queue()
kids = ['A','B','C','D','E','F']
# 进队列(循环队列)
for kid in kids:
q.enqueue(kid)
# 队列中剩最后一个孩子则跳出循环,孩子获胜
while q.size() > 1:
# 内层循环是用来将手里有山芋的孩子排在队头
# 每次计时器到时,删除当前孩子,则指针指向的下一个孩子位置
# 每次计时,循环队列
for i in range(6):
# 删除队列最后一个孩子
kid = q.dequeue()
# 删除的孩子重新添加在队首位置,实现队列循环
q.enqueue(kid)
# 6s计时到时,删除最后一个孩子,指针指向下一个孩子
q.dequeue()
# 打印最后获胜的孩子
print(q.dequeue())
# 孩子E获胜,留下的最后一个孩子
# 结果>>>E
Python实现队列
队列的抽象数据类型应该由以下结构和操作定义。队列操作如下:
-
- Queue() 创建一个空的新队列。 它不需要参数,并返回一个空队列。
- enqueue(item) 将新项添加到队尾。 它需要 item 作为参数,并不返回任何内容。
- dequeue() 从队首移除项。它不需要参数并返回 item。 队列被修改。
- isEmpty() 查看队列是否为空。它不需要参数,并返回布尔值。
- size() 返回队列中的项数。它不需要参数,并返回一个整数。
class Queue():
def __init__(self):
self.items = []
# 队列插入元素(左端)
def enqueue(self,item):
self.items.insert(0,item)
# 队列删除元素(右端)
def dequeue(self):
return self.items.pop()
# 判断队列是否为空,空返回True,否则返回False
def isEmpty(self):
return self.items == []
# 返回队列长度
def size(self):
return len(self.items)
队列测试:
>>> q.size()
3
>>> q.isEmpty()
False
>>> q.enqueue(8.4)
>>> q.dequeue()
4
>>> q.dequeue()
'dog'
>>> q.size()
2