本专栏主要基于北大的数据结构与算法教程(Python版)进行整理,包括课程笔记和OJ作业。
1. 队列抽象数据类型及Python实现
什么是队列?
- 队列是一种有次序的数据集合,其特征是:
1)新数据项的添加总发生在一端(通常称为“尾 rear”端)
2)现存数据项的移除总发生在另一端(通常称为 “首front”端) - 当数据项加入队列,首先出现在队尾,随着队首数据项的移除,它逐渐接近队首。
- 新加入的数据项必须在数据集末尾等待, 而等待时间最长的数据项则是队首
- 这种次序安排的原则称为(FIFO:First-in first-out)先进先出 或 “先到先服务first-come first-served”
- 队列的例子出现在我们日常生活的方方面面:排队
- 队列仅有一个入口和一个出口:不允许数据项直接插入队中,也不允许从中间移除数据项。
计算机科学中队列的例子
- 打印队列:一台打印机面向多个用户/程序提供服务。打印速度比打印请求提交的速度要慢得多,有任务正在打印时,后来的打印请求就要排成队列,以FIFO的形式等待被处理。
- 进程调度:操作系统核心采用多个队列来对系统中同时运行的进程进行调度。进程数远多于CPU核心数,有些进程还要等待不同类型I/O事件。调度原则综合了“先到先服务”及“资源充分利用”两个出发点。
- 键盘缓冲:键盘敲击并不马上显示在屏幕上;需要有个队列性质的缓冲区,将尚未显示的敲击 字符暂存其中, 队列的先进先出性质则保证了字符的输入和显示次序一致性。
抽象数据类型Queue
- 抽象数据类型Queue是一个有次序的数据集合。数据项仅添加到“尾rear”端,而且仅从“首front”端移除 Queue具有FIFO的操作次序。
- 抽象数据类型Queue由如下操作定义:
1)Queue():创建一个空队列对象,返回值为 Queue对象;
2)enqueue(item):将数据项item添加到队尾, 无返回值;
3)dequeue():从队首移除数据项,返回值为队首 数据项,队列被修改;
4)isEmpty():测试是否空队列,返回值为布尔值
5)size():返回队列中数据项的个数。
上图中的队列,左端为队尾,右端为队首。
Python实现ADT Queue
- 采用 List来容纳Queue的数据项
将List首端作为队列尾端;List的末端作为队列首端;enqueue()复杂度为 O(n) (insert(0,item));dequeue()复杂度为 O(1) (pop())。 - 和堆栈不同,首尾倒过来的实现 ,复杂度也倒过来,仍然是一个O(1) (末端作为队尾,加入append()),一个O(n) (首端作为队首,移除是pop(0))。
- 代码
class Queue:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items==[]
def enqueue(self,item):
self.items.insert(0,item)
def dequeue(self):
return self.items.pop()
def size(self):
return len(self.items)
2. 队列的应用:热土豆
热土豆问题(约瑟夫问题)
- “击鼓传花”的土豆版本
- 传烫手的热土豆,鼓声停的时候,手里有 土豆的小孩就要出列。
- 如果去掉鼓,改为传过固定人数,就成了 “现代版”的约瑟夫问题
传说犹太人反叛罗马人,落到困境,约瑟夫和39 人决定殉难,坐成一圈儿,报数1~7,报到7的 人由旁边杀死,结果约瑟夫给自己安排了个位置 ,最后活了下来…
热土豆问题:算法
- 用队列来实现热土豆问题的算法,参加游 戏的人名列表,以及传土豆次数num,算法返回最后剩下的人名
- 模拟程序采用队列来存放所有参加游戏的人名, 按照传递土豆方向从队首排到队尾。 游戏时,队首始终是持有土豆的人
- 模拟游戏开始,只需要将队首的人出队,随即再 到队尾入队,算是土豆的一次传递。传递了num次后,将队首的人移除,不再入队 如此反复,直到队列中剩余1人。
from pythonds.basic.queue import Queue
def hotPotatoes(nameList,num):
queue = Queue()
for name in nameList:
queue.enqueue(name)
while queue.size()>1:
for i in range(num):
queue.enqueue((queue.dequeue())) #一次土豆传递
queue.dequeue()
return queue.dequeue()
hotPotatoes(['a','b','c','corejj','hao','sdu','bdd','knight'],7)
3. 队列的应用:打印任务
模拟算法:打印任务
- 多人共享一台打印机,采取“先到先服务 ”的队列策略来执行打印任务
- 在这种设定下,一个首要的问题就是:这种打印作业系统的容量有多大?在能够接受的等待时间内,系统能容纳多少用户以多高频率提交多少打印任务?
- 一个具体的实例配置如下:一个实验室,在任意的一个小时内,大约有10 学生在场,
这一小时中,每人会发起2次左右的打印,每次1 ~20页。 - 打印机的性能是:以草稿模式打印的话,每分钟10页;以正常模式打印的话,打印质量好,但速度下降 为每分钟5页。
- 问题是:怎么设定打印机的模式,让大家 都不会等太久的前提下尽量提高打印质量 ?
- 这是一个典型的决策支持问题,但无法通 过规则直接计算
- 我们要用一段程序来模拟这种打印任务场景,然后对程序运行结果进行分析,以支持对打印机模式设定的决策。
如何对问题建模?
- 对象:打印任务、打印队列、打印机
1)打印任务的属性:提交时间、打印页数
2)打印队列的属性:具有FIFO性质的打印任务队列
3)打印机的属性:打印速度、是否忙
- 过程:生成和提交打印任务
确定生成概率:实例为每小时会有10个学生提交的20个作业(每个学生每小时会发出2次左右打印),这样,概率是每180秒会有1个作业生成并提交,概率为每秒1/180(个作业)。
确定打印页数:实例是1-20页(每个作业1-20页不等),那么就是1- 20页之间概率相同。
- 过程:实施打印
1)当前的打印作业:正在打印的作业
2)打印结束倒计时:新作业开始打印时开始倒计时 ,回0表示打印完毕,可以处理下一个作业 - 模拟时间:
1)统一的时间框架:以最小单位(秒)均匀流逝的 时间,设定结束时间
2) 同步所有过程:在一个时间单位里,对生成打印任务和实施打印两个过程各处理一次
打印任务问题:模拟流程
- 创建打印(任务)队列对象
- 时间按照秒的单位流逝
1) 按照概率生成打印作业,加入打印队列(每秒 1/180的概率生成一个打印任务)
2)如果打印机空闲,且队列不空,则取出队首作业打印,记录此作业等待时间(开始处理时间-生成改作业的时间)
3)如果打印机忙,则按照打印速度进行1秒打印 (在当前打印速度下,打印该作业包含的页数需要多少秒,一秒一秒地打印(倒计时))。
4)如果当前作业打印完成,则打印机进入空闲 - 时间用尽,开始统计平均等待时间
- 作业的等待时间:生成作业时,记录生成的时间戳; 开始打印时,当前时间减去生成时间即可
- 作业的打印时间:生成作业时,记录作业的页数;开始打印时,页数除以打印速度(页/秒)即可。
代码
from pythonds.basic.queue import Queue
import random
class Printer:
def __init__(self,ppm):
self.pagerate = ppm #打印速度 页/分钟
self.currentTask = None #打印任务
self.timeRemaining = 0 #打印倒计时
def tick(self): #打印倒计时 打印1秒 逐秒流逝
if self.currentTask!=None:
self.timeRemaining = self.timeRemaining - 1
if self.timeRemaining <=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 #任务需要的打印时间 初始化倒计时 秒为单位
class Task:
def __init__(self,time):
self.timestamp = time #生成该打印任务的时间 生成时间戳
self.pages = random.randrange(1,21) #打印任务的页数1-20页等概率出现
def getStamp(self):
return self.timestamp
def getPages(self):
return self.pages
def waitTime(self,currenttime): #任务等待时间
return currenttime - self.timestamp
def newPrintTask():
num = random.randrange(1,181) #每秒 以 1/180的概率生成一个新任务
if num == 180: #180出现的概率是 1/180
return True
else:
return False
def simulation(numSeconds,pagesPerMinute):
labprinter = Printer(pagesPerMinute)
printQueue = Queue()
waitingtimes = [] #每个任务的等待时间
for currentSecond in range(numSeconds):
if newPrintTask():
task = Task(currentSecond)
printQueue.enqueue(task)
if (not labprinter.busy()) and (not printQueue.isEmpty()):
nexttask = printQueue.dequeue()
waitingtimes.append(nexttask.waitTime(currentSecond))
labprinter.startNext(nexttask)
labprinter.tick()
averageWait = sum(waitingtimes)/len(waitingtimes)
#%6.2f 6控制小数点前数字宽度 2是小数点后保留的位数
#printQueue.size() 一小时过后 任务队列中还有多少未处理的任务
print("Average Wait %6.2f secs %3d tasks remaining."%(averageWait,printQueue.size()))
打印任务问题:运行和分析
- 第一种打印模式:按5PPM(一分钟打印5页,打印质量高)、1小时的设定,模拟运行10次
1)总平均等待时间96.256秒,最长的平均等待253.54秒 ,最短的平均等待28.94秒
2)有3次模拟,还有作业没开始打印
for i in range(10): #模拟10次
simulation(3600,5)
- 第二种打印模式:提升打印速度到10PPM(一分钟打印10页,打印质量低)、1小时的设定
1)总平均等待时间18.118秒,最长的平均等待37.32秒,最短的平均等待0秒,就是一提交就打印了
2)而且,所有作业都打印了
for i in range(10): #模拟10次
simulation(3600,10)
打印任务讨论
- 为了对打印模式设置进行决策,我们用模拟程序来评估任务等待时间
通过两种情况模拟仿真结果的分析,我们认识到如果有那么多学生要拿着打印好的程序源代码赶去上课的话;那么,必须得牺牲打印质量,提高打印速度。 - 模拟系统对现实的仿真
在不耗费现实资源的情况下——有时候真实的实验 是无法进行的; 可以以不同的设定,反复多次模拟来帮助我们进行决策。 - 打印任务模拟程序还可以加进不同设定, 来进行更丰富的模拟:
1) 学生数量加倍了会怎么样?
2)如果在周末,学生不需要赶去上课,能接受更长等待时间,会怎么样?
3)如果改用Python编程,源代码大大减少,打印的页数减少了,会怎么样? - 更真实的模拟,来源于对问题的更精细建模,以及以真实数据进行设定和运行
- 也可以扩展到其它类似决策支持问题
如:饭馆的餐桌设置,使得顾客排队时间变短
4. 双端队列抽象数据类型及Python实现
双端队列Deque:什么是Deque?
- 双端队列Deque是一种有次序的数据集。跟队列相似,其两端可以称作“首”“尾”端,但deque中数据项既可以从队首加入,也可以从队尾加入;数据项也可以从两端移除。 某种意义上说,双端队列集成了栈和队列的能力
- 但双端队列并不具有内在的LIFO或者 FIFO特性。如果用双端队列来模拟栈或队列需要由使用者自行维护操作的一致性。
抽象数据类型Deque
- deque定义的操作如下
1)Deque():创建一个空双端队列
2)addFront(item):将item加入队首
3)addRear(item):将item加入队尾
4)removeFront():从队首移除数据项,返回值为 移除的数据项
5)removeRear():从队尾移除数据项,返回值为 移除的数据项
6)isEmpty():返回deque是否为空
7)size():返回deque中包含数据项的个数
上图所示的双端队列,左边为队尾,右边为队首。
Python实现ADT Deque
- 采用List实现 List下标0作为 deque的尾端,List下标-1作为 deque的首端。
- 操作复杂度:addFront/removeFront O(1);addRear/removeRear O(n)
- 代码
class Dequeue():
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def addFront(self,item):
self.items.append(item)
def addRear(self,item):
self.items.insert(0,item)
def removeFront(self):
return self.items.pop()
def removeFront(self):
return self.items.pop(0)
def size(self):
return len(self.items)
“回文词”判定
- 回文词”指正读和反读都一样的词
如radar、madam、toot; 中文“上海自来水来自海上” “山东落花生花落东山”
- 用双端队列很容易解决“回文词”问题
先将需要判定的词(逐字符)从队尾加入deque;再从两端同时移除字符判定是否相同,直到 deque中剩下0个或1个字符。
- “回文词”判定:代码
from pythonds.basic.deque import Deque
def palchecker(aString):
deque = Deque()
for ch in aString:
deque.addRear(ch)
still = True
while dequeue.size()>1 and still:
first = deque.removeFront()
last = deque.removeRear()
if first != last:
still = False
return still
5. 无序表抽象数据类型及Python实现
列表List:什么是列表?
- 在前面基本数据结构的讨论中,我们采用 Python List来实现了多种线性数据结构
- 列表List是一种简单强大的数据集结构, 提供了丰富的操作接口;但并不是所有的编程语言都提供了List数据类型 ,有时候需要程序员自己实现。
- 一种数据项按照相对位置存放的数据集/结构。特别的,被称为“无序表unordered list”
其中数据项只按照存放位置来索引,如第1个、 第2个…、最后一个等。(为了简单起见,假设表中不存在重复数据项) - 如一个考试分数的集合“54, 26, 93, 17, 77和31”
- 如果用无序表来表示,就是[54, 26, 93, 17, 77, 31]
抽象数据类型:无序表List
- 无序表List的操作如下:
1)List():创建一个空列表
2)add(item):添加一个数据项到列表中,假设 item原先不存在于列表中
3)remove(item):从列表中移除item,列表被修 改,item原先应存在于表中
4)search(item):在列表中查找item,返回布尔类型值
5)isEmpty():返回列表是否为空
6)size():返回列表包含了多少数据项
7)append(item):添加一个数据项到表末尾,假 设item原先不存在于列表中
8)index(item):返回数据项在表中的位置
9)insert(pos, item):将数据项插入到位置pos ,假设item原先不存在与列表中,同时原列表具 有足够多个数据项,能让item占据位置pos
10)pop():从列表末尾移除数据项,假设原列表至 少有1个数据项
11)pop(pos):移除位置为pos的数据项,假设原列 表存在位置pos
采用链表实现无序表
- 为了实现无序表数据结构,可以采用链接表的方案。
- 虽然列表数据结构要求保持数据项的前后相对位置,但这种前后位置的保持,并不要求数据项依次存放在连续的存储空间。
- 如下图,数据项存放位置(存储空间)并没有规则,但如果在数据项之间建立链接指向,就可以保持其前后相对位置。第一个和最后一个数据项需要显式标记出来,一个是队首,一个是队尾,后面再无数据了:
链表实现:节点Node
- 链表实现的最基本元素是节点Node
1)每个节点至少要包含2个信息:数据项本身,以 及指向下一个节点的引用信息
2)注意next为None的意义是没有下一个节点了, 这个很重要
- 代码
class Node:
def __init__(self,initdata):
self.data = initdata
self.next = None
def getData(self):
return self.data
def getNext(self):
return self.next
def setData(self,newdata):
self.data = newdata
def setNext(self,newnext):
self.next = newnext
temp = Node(93)
temp.getData()
链表实现:无序表UnorderedList
- 可以采用链接节点的方式构建数据集/结构来实现无序表
- 链表的第一个和最后一个节点最重要。如果想访问到链表中的所有节点,就必须从第一 个节点开始沿着链接遍历下去。
- 所以无序表必须要有对第一个节点的引用信息
设立一个属性head,保存对第一个节点的引用 空表的head为None。
class UnorderedList:
def __init__(self):
self.head = None
mylist = UnorderedList()
print(mylist.head)
- 随着数据项的加入,无序表的head始终指向链条中的第一个节点.
- 注意!无序表mylist对象本身并不包含数据项 (数据项在节点中)
- 其中包含的head只是对首个节点Node的引用
- 判断空表的isEmpty()很容易实现
return self.head == None
6. 无序表的链表实现
链表实现:无序表UnorderedList
- 接下来,考虑如何实现向无序表中添加数据项,实现add方法。
- 由于无序表并没有限定数据项之间的顺序
- 新数据项可以加入到原表的任何位置
- 按照实现的性能考虑,应添加到最容易加入的位置上。
- 由链表结构我们知道,要访问到整条链上的所有数据项,都必须从表头head开始沿着next链接逐 个向后查找,所以添加新数据项最快捷的位置是表头, 整个链表的首位置。
链表实现:add方法实现
- 按照下面的代码调用,形成的链表如下图
mylist.add(31)
mylist.add(77)
mylist.add(17)
mylist.add(93)
mylist.add(26)
mylist.add(54)
31是最先被加入的数据项,所以成为链表中最后 一个项;而54是最后被加入的,是链表第一个数据项(每次都从链表头加入)。
- 链接次序很重要!
def add(item):
temp = Node(item) #创建一个新节点
temp.setNext(self.head) #让新节点 指向 所来head指向的节点
self.head = temp #再让head指向 新节点
链表实现:size
- size:从链表头head开始遍历到表尾同时用变量累加经过的节点个数。
def size(self):
current = self.head
count = 0
while current != None:
count = count + 1
current = current.getNext()
return count
链表实现:search
- 从链表头head开始遍历到表尾,同时判断当前节点的数据项是否为目标
def search(self,item):
current = self.head
found = False
while current != None and not found:
if current.getData() == item:
found = True #假设链表中没有重复元素 或 只找第一个满足要求的元素
else:
current = current.getNext()
return found
链表实现:remove(item)方法
- 首先要找到item,这个过程跟search一 样,但在删除节点时,需要特别的技巧
- current指向的是当前匹配数据项的节点
- 而删除需要把前一个节点的next指向current的 下一个节点
- 所以我们在search current的同时,还要维护 前一个(previous)节点的引用
- 找到item之后,current指向item节点,previous指向前一个节点,开始执行删除
,需要区分两种情况:current是首个节点;或者是位于链条中间的节点。
def remove(self,item):
current = self.head
previous = None
found = False
while not found:
if current.getData() == item:
found = True
else:
previous = current
current = current.getNext()
if previous == None:
self.head = current.getNext()
else:
previous.setNext(current.getNext())
- 完整代码
class UnorderedList:
def __init__(self):
self.head = None
def isEmpty(self):
return self.head == None
def add(self,item):
temp = Node(item) #创建一个新节点
temp.setNext(self.head) #让新节点 指向 所来head指向的节点
self.head = temp #再让head指向 新节点
def size(self):
current = self.head
count = 0
while current != None:
count = count + 1
current = current.getNext()
return count
def search(self,item):
current = self.head
found = False
while current != None and not found:
if current.getData() == item:
found = True #假设链表中没有重复元素 或 只找第一个满足要求的元素
else:
current = current.getNext()
return found
def remove(self,item):
current = self.head
previous = None
found = False
while not found:
if current.getData() == item:
found = True
else:
previous = current
current = current.getNext()
if previous == None:
self.head = current.getNext()
else:
previous.setNext(current.getNext())
7. 有序表抽象数据类型及Python实现
抽象数据类型:有序表OrderedList
- 有序表是一种数据项依照其某可比性质( 如整数大小、字母表先后)来决定在列表中的位置
- 越“小”的数据项越靠近列表的头,越靠 “前”
抽象数据类型:有序表OrderedList
- OrderedList所定义的操作如下:
1)OrderedList():创建一个空的有序表
2)add(item):在表中添加一个数据项,并保持整体顺序, 此项原不存在(假设没有重复元素,简单起见)
3)remove(item):从有序表中移除一个数据项,此项应存在,有序表被修改
4)search(item):在有序表中查找数据项,返回是否存在
5)isEmpty():是否空表
6)size():返回表中数据项的个数
7)index(item):返回数据项在表中的位置,此项应存在
8)pop():移除并返回有序表中最后一项,表中应至少存在 一项
9)pop(pos):移除并返回有序表中指定位置的数据项,此位 置应存在
有序表OrderedList实现
- 在实现有序表的时候,需要记住的是,数据项的相对位置,取决于它们之间的“大小”比较
由于Python的扩展性,下面对数据项的讨论并不仅适用于整数,可适用于所有定义了__gt__ 方法(即’>'操作符)的数据类型。 - 以整数数据项为例,(17, 26, 31, 54, 77, 93)的链表形式如图
- 同样采用链表方法实现;Node定义相同;OrderedList也设置一个head来保存链表表头的引用。
class OrderedList:
def __init__(self):
self.head = None
- 对于isEmpty/size/remove这些方法, 与节点的次序无关,所以其实现跟 UnorderedList是一样的。
- search/add方法则需要有修改
有序表实现:search方法
- 在无序表的search中,如果需要查找的数 据项不存在,则会搜遍整个链表,直到表尾
- 对于有序表来说,可以利用链表节点有序排列的特性,来为search节省不存在数据项的查找时间:一旦当前节点的数据项大于所要查找的数据项,则说明链表(假设从小到大排)后面已经不可能再有要查找的数据项 ,可以直接返回False。
- 如我们要在下图查找数据项45
- 代码
def search(self,item):
current = self.head
found = False
stop = False
while current != None and not found and not stop:
if current.getData() == item:
found = True #假设链表中没有重复元素 或 只找第一个满足要求的元素;而且链表从小到大排序
else:
if current.getData() > item:
stop = True
else:
current = current.getNext()
return found
有序表OrderedList实现:add方法
- 相比无序表,改变最大的方法是add,因为add方法必须保证加入的数据项添加在合适的位置,以维护整个链表的有序性。
比如在(17, 26, 54, 77, 93)的有序表中,加 入数据项31,我们需要沿着链表,找到第一个比 31大的数据项54,将31插入到54的前面。
- 由于涉及到的插入位置是当前节点之前,而链表无法得到“前驱”节点的引用
- 所以要跟remove方法类似,引入一个previous 的引用,跟随当前节点current。
- 一旦找到首个比31大的数据项,previous就派 上用场了
- 代码
def add(self,item):
current = self.head
previous = None
stop = False
while current != None and not stop:
if current.getData() > item:
stop =True
else:
previous = current
current = current.getNext()
temp = Node(item)
if previous == None: #在头部插入
temp.setNext(self.head)
self.head = temp
else:
temp.setNext(current)
previous.setNext(temp)
- 完整代码:
class OrderedList:
def __init__(self):
self.head = None
def isEmpty(self):
return self.head == None
def add(self,item):
current = self.head
previous = None
stop = False
while current != None and not stop:
if current.getData() > item:
stop =True
else:
previous = current
current = current.getNext()
temp = Node(item)
if previous == None: #在头部插入
temp.setNext(self.head)
self.head = temp
else:
temp.setNext(current)
previous.setNext(temp)
def size(self):
current = self.head
count = 0
while current != None:
count = count + 1
current = current.getNext()
return count
def search(self,item):
current = self.head
found = False
stop = False
while current != None and not found and not stop:
if current.getData() == item:
found = True #假设链表中没有重复元素 或 只找第一个满足要求的元素;而且链表从小到大排序
else:
if current.getData() > item:
stop = True
else:
current = current.getNext()
return found
def remove(self,item):
current = self.head
previous = None
found = False
while not found:
if current.getData() == item:
found = True
else:
previous = current
current = current.getNext()
if previous == None:
self.head = current.getNext()
else:
previous.setNext(current.getNext())
链表实现的算法分析
- 对于链表复杂度的分析,主要是看相应的方 法是否涉及到链表的遍历
- 对于一个包含节点数为n的链表
- isEmpty是O(1),因为仅需要检查head是否为None
- size是O(n),因为除了遍历到表尾,没有其它办法 得知节点的数量
- search/remove以及有序表的add方法,则是O(n), 因为涉及到链表的遍历,按照概率其平均操作的次数 是n/2
- 无序表的add方法是O(1),因为仅需要插入到表头
- 链表实现的List,跟Python内置的列表数据类型,在有些相同方法的实现上的时 间复杂度不同
- 主要是因为Python内置的列表数据类型是基于顺序存储来实现的,并进行了优化。
8. 线性结构小结
- 线性数据结构Linear DS将数据项以某种 线性的次序组织起来
- 栈Stack维持了数据项后进先出LIFO的次序.stack的基本操作包括push, pop, isEmpty
- 队列Queue维持了数据项先进先出FIFO 的次序.queue的基本操作包括enqueue, dequeue, isEmpty
- 书写表达式的方法有前缀prefix、中缀 infix和后缀postfix三种:由于栈结构具有次序反转的特性,所以栈结构适 合用于开发表达式求值和转换的算法.
- “模拟系统”可以通过一个对现实世界问 题进行抽象建模,并加入随机数动态运行 ,为复杂问题的决策提供各种情况的参考:队列queue可以用来进行模拟系统的开发.
- 双端队列Deque可以同时具备栈和队列的功能:deque的主要操作包括addFront, addRear, removeFront, removeRear, isEmpty。
- 列表List是数据项能够维持相对位置的数据集。
- 链表的实现,可以保持列表维持相对位置的特点,而不需要连续的存储空间。
- 链表实现时,其各种方法,对链表头部 head需要特别的处理。