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进行基本的时间模拟;

一、栈

栈是一个项的有序集合,添加和移除项都发生在同一端,这一段称为“顶”,另一端的顶部被称为“底”。我们可以将栈同类比为一摞书(如下图),先放的书在最底下,称为栈底,最后放的书在最上面,称为栈顶,同样移除书时也是先移走顶端的书。栈的数据添加和移除也遵循这一原则,我们称之为“后进先出”原则。 放入和取出的顺序相反,这一属性也称为栈的“逆转属性”。

python 数据结构和算法效率 python常用的数据结构与算法_双端队列

1.1 栈的抽象数据类型

  • stack()创建一个新的空栈,返回空栈;
  • push(item)在栈顶添加一个新的项;
  • pop()移除栈顶的项,并返回此项;
  • peek()返回栈顶的项,不进行任何操作;
  • isEmpty()测试栈是否为空,返回布尔值;
  • size()返回栈的项目数,返回一个整数。

二、队列

队列是一系列有顺序的元素的集合,新元素加入的一端被称为“队尾”,队列的另一端称为“队首”,队列的元素添加或移除遵循“先进先出”原则,注意和栈不同!下图是由python数据对象构成的队列:

python 数据结构和算法效率 python常用的数据结构与算法_抽象数据类型_02

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()

三、双端队列

双端队列与队列相似,不过添加或删除数据可以从任意两端进行,它拥有栈和队列各自拥有的属性。

python 数据结构和算法效率 python常用的数据结构与算法_抽象数据类型_03

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]为例,看起来这些数被随机放置,但是通过以下将每个项目简单链接,将其明确显示出来。

python 数据结构和算法效率 python常用的数据结构与算法_抽象数据类型_04


第一个位置必须被明确指出,一旦知道第一项,那么其他项就可通过链接寻找出来,从外部只想的第一项通常被称为链表的“头”,同理,链表的最后一项需要告诉我们后面没有其他项,用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来作为你初始化时对下一个节点的引用是一个好主意。

python 数据结构和算法效率 python常用的数据结构与算法_链表_05

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)