python数据结构与算法-基本数据结构类型
- 学习目标
- 一、栈
- 1.1 栈的抽象数据类型
- 二、队列
- 2.1抽象数据类型QUEUE
- 2.2用python实现Queue
- 2.3模拟算法:打印任务
- python实现打印任务
- 三、双端队列
- 3.1抽象数据类型
- 四、列表LIST
- 4.1无序列表
- 4.1.1抽象数据类型
- 4.1.2节点Node
- 4.2有序列表
- 4.3链表实现算法分析
学习目标
- 了解抽象数据类型:栈stack,队列queue,双端队列deque,列表list;
- 用python列表数据结构,实现stack/queue/dequeue抽象数据类型的构建;
- 采用队列queue进行基本的时间模拟;
一、栈
栈是一个项的有序集合,添加和移除项都发生在同一端,这一段称为“顶”,另一端的顶部被称为“底”。我们可以将栈同类比为一摞书(如下图),先放的书在最底下,称为栈底,最后放的书在最上面,称为栈顶,同样移除书时也是先移走顶端的书。栈的数据添加和移除也遵循这一原则,我们称之为“后进先出”原则。 放入和取出的顺序相反,这一属性也称为栈的“逆转属性”。
1.1 栈的抽象数据类型
- stack()创建一个新的空栈,返回空栈;
- push(item)在栈顶添加一个新的项;
- pop()移除栈顶的项,并返回此项;
- peek()返回栈顶的项,不进行任何操作;
- isEmpty()测试栈是否为空,返回布尔值;
- size()返回栈的项目数,返回一个整数。
二、队列
队列是一系列有顺序的元素的集合,新元素加入的一端被称为“队尾”,队列的另一端称为“队首”,队列的元素添加或移除遵循“先进先出”原则,注意和栈不同!下图是由python数据对象构成的队列:
2.1抽象数据类型QUEUE
- queue()创建一个空队列对象,返回空的队列;
- enqueue(item)将数添加到队尾;
- dequeue()从队首移除数据项,并返回此项;
- isEmpty()测试是否为空队列,并返回一个布尔值;
- size()返回队列中的数据项的个数。
2.2用python实现Queue
class Queue:
def __init__(self):
self.items=[]
def isEmpty(self):
return self.items==[]
def enqueue(self,items):
self.items.insert(0,items)
def dequeue(self):
return self.items.pop()
def size(self):
return len(self.items)
2.3模拟算法:打印任务
以下为主要模拟过程:
1.创建一个打印任务队列,每个任务在生成时被赋予一个“时间邮戳”,队列在开始时是空的。
2.对于每一秒(打印过程的当前秒(currentSecond)
(1)是有由新的打印任务生成?如果有,把它加入打印队列,并把当前秒作为其“时间邮戳”
(2)如果打印机空闲并且有任务正在等候队列中:
1从打印列表中移除下一个打印任务并且将其交给打印机;
2从当前秒中减去“时间邮戳”值,计算得到该任务的等待时间;
3将该任务的等待时间添加到一个列表中,用于后续操作;
4基于打印的页数,求出需要多长的打印时间。
(3)如果此时打印机再工作中,对于打印机而言,就工作了一秒钟;对于打印任务而言,它离打印结束又近了一秒钟(剩余打印时间减1)
(4)如果此时打印任务已经完成,也就是剩余打印时间为0时,打印机进入空闲状态。
3.在整个模拟算法完成后,依据生成的等待时间列表中的数据,计算平均打印时间。
python实现打印任务
我们需要为描述前述的三个真实世界的对象: 打印机(Printer)、 打印任务(Task)和打印队列(PrintQueue)来自定义一些类。
- 模拟打印机的类 Printer需要实时监测是否正在执行打印任务。如果是,则表示打印机正忙,需要的等待时间可以由当前任务的打印张数求得。同时初始构造函数还要能完成单位时间打印张数的初始化设置。
- 方法 tick 用于减去内设任务的完成所需时间,并在一次任务结束后将打印机设为闲置。
class Printer:
def __init__(self,ppm):
self.pagerate = ppm
self.currentTask = None
self.timeRemaining = 0
def tick(self):
if self.currentTask!=None:
self.timeRemaining = self.Remaining-1
if self.timenRemaining<=0:
self.currentTask=None
def busy(self):
if self.currentTask!=None:
return True
else:
return False
def startNext(self,newtask):
self.currentTask=newtask
self.timeRemaining = newtask.getPages()*60/self.pagerate
类Task代表一个单独的打印任务,当一个任务被创建时,随机生成器生成一个1到20之间的一个随机数代表打印的页数,我们选择了random模块中的ranrange方法。
每个打印任务还需要定义一个用于计算等待时间的对象timestamp,timestamp会记录下任务被创建和被放入打印队列时的时间,方法waitTime用于检索任务开始打印前在队列中的等待时长。
import random
class Task:
def __init__(self,item):
self.timestamp = time
self.pages = random.randrange(1,21)
def getStamp(self):
return self.timestamp
def getPages(self):
return self.pages
def waitTime(self,currenttime):
return currenttime-self.timestamp
主函数simulation可以实现上述算法:
import Queue
import random
def simulation(numSeconds,pagesPerMinute):
labprinter = printer(pagesPerMinute)
printQueue = Queue()
waitingtimes = []
for currentSeconds in range(numSeconds):
if newPrinteTask():
task = Task(currentSecond)
printerQueue.enqueue(task)
if (not labprinter.busy()) and (not printQueue.isEmpty()):
nexttask = printQueue.dequeue()
waintingtimes.append(nexttask.waitTime(currentSecond))
labprinter.starNext(nexttask)
labprinter.tick()
三、双端队列
双端队列与队列相似,不过添加或删除数据可以从任意两端进行,它拥有栈和队列各自拥有的属性。
3.1抽象数据类型
- Deque()创建一个空双端队列;
- addFront(item)在队首插入一个元素;
- addRear(item)在队尾插入一个元素;
- removeFront()在队首移除一个元素,并返回改元素;
- removeRear()在队尾移除一个元素,并返回改元素;
- isEmpty()判断双端队列是否为空,返回布尔值;
- size ()返回双端队列中数据项的个数。
四、列表LIST
列表是一些元素的集合,每个元素拥有一个与其它元素不同的相对位置。例如列表[54,26,93,17,77,31]
4.1无序列表
我们通过构建一个链表来实现列表,以上面的列表[54,26,93,17,77,31]为例,看起来这些数被随机放置,但是通过以下将每个项目简单链接,将其明确显示出来。
第一个位置必须被明确指出,一旦知道第一项,那么其他项就可通过链接寻找出来,从外部只想的第一项通常被称为链表的“头”,同理,链表的最后一项需要告诉我们后面没有其他项,用None链接。
4.1.1抽象数据类型
- list() 创建一个新的空列表。它不需要参数,而返回一个空列表。
*add(item) 将新项添加到列表,没有返回值。假设元素不在列表中。
*remove(item) 从列表中删除元素。需要一个参数,并会修改列表。此处假设元
素在列表中。
*search(item) 搜索列表中的元素。需要一个参数,并返回一个布尔值。
*isEmpty() 判断列表是否为空。不需要参数,并返回一个布尔值。
*size() 返回列表的元素数。不需要参数,并返回一个整数。
*append(item) 在列表末端添加一个新的元素。它需要一个参数,没有返回值。
假设该项目不在列表中。
*index(item) 返回元素在列表中的位置。它需要一个参数,并返回位置索引值。
此处假设该元素原本在列表中。
*insert(pos,item) 在指定的位置添加一个新元素。它需要两个参数,没有返回值。
假设该元素在列表中并不存在,并且列表有足够的长度满足参数提供的索引需要。
*pop() 从列表末端移除一个元素并返回它。它不需要参数,返回一个元素。假
设列表至少有一个元素。
*pop(pos) 从指定的位置移除列表元素并返回它。它需要一个位置参数,并返回
一个元素。假设该元素在列表中。
4.1.2节点Node
用链表实现的基本模块是节点。每个节点对象必须持有至少两条信息。首先,节点必须包含列表元素本身。我们将这称为该节点的“数据区”。此外,每个节点必须保持到下一个节
点的引用。Python 的特殊值 None 将在节点类和之后的链表类中发挥重要的作用。引用 None 意味着没有下一个节点。
所以用None来作为你初始化时对下一个节点的引用是一个好主意。
4.2有序列表
有序列表的结构是一个数据的集合体,在集合体中,每个元素相对其他元素有一个基于元素的某些基本性质的位置。假设我们已经在列表元素中定义了一个有意义的比较大小的操作,则排序通常是升序或降序。有序列表的许多方法和无序表是一样的。
4.3链表实现算法分析
考虑一个有 n 个节点的链表, isEmpty 方法复杂度是 O(1),因为它只需要检查链表的头指针是否为 None。对于方法 size,则总需要 n 个步骤,因为除了遍历整个链表以外,没有办法知道链表的节点数。因此, size 方法的复杂度是 O(n)。无序列表的 add 方法的复杂度是 O(1),因为我们永远只需要在链表的头部简单地添加一个新的节点。但是, search、 remove 和在有序列表中的 add 方法,需要遍历。尽管在平均情况下,它们可能只需要遍历一半的节点,但这些方法的复杂度都是 O(n),因为在最糟糕的情况下
需要遍历整个链表。
方法 | 链表复杂度 | 有序列表复杂度 |
isEmpty() | O(1) | O(1) |
size() | O(n) | O(n) |
add() | O(1) | O(n) |
search() | O(n) | O(n) |
remove() | O(1) | O(n) |