5.1 认识队列
队列的基本操作 用数组实现队列 用链表实现队列
5.2 队列的应用
环形队列 -- 双向队列 -- 优先队列
队列是一种“先进先出(First In First Out,FIFO)”的数据结构,和堆栈一样都是一种有序线性表的抽象数据类型(ADT)。就好比乘坐高铁时买票的队伍,先到的人当然可以先买票,买完后就从前端离去准备进入站台。
队列同样可以使用数组或链表来建立一个队列。堆栈数据结构只需要一个top指针指向堆栈的顶端;而队列则必须使用front和rear两个指针(游标)分别指向队列的前端和末尾。
队列的应用也非常广泛:计算机的模拟,CPU的作业调度,外围设备联机并发处理系统的应用和图遍历的广度优先搜素法(BFS)
深堆广队
5.1.1 队列的基本操作
队列是一种ADT,具有以下特性:
1)具有先进先出的特性
2)拥有两种基本操作,即加入和删除;而且使用friont和rear两个指针分别指向队列的前端和末尾、
基本操作 | |
create | 创建空队列。 |
add | 将新数据加入到碎裂的末尾,返回新队列 |
delete | 删除队列前端的数据,返回新队列 |
front | 返回队列前端的值 |
empty | 如队列为空,则返回“真”,否则返回“假” |
5.1.2 用数组实现队列
优点:算法简单;与堆栈不同的是需要拥有两种基本操作(加入和删除),而且使用front和rear指针分别指向队列的前端和末尾。
缺点:数组大小无法根据队列的实际需要来动态申请,只能声明固定大小。
def enqueue(item): # 将新数据加入到队列的末尾,并返回新队列
global rear
global MAX_SIZE
global queue
if rear == MAX_SIZE -1:
print('队列已满!')
else:
rear += 1
queue[rear] = item #将数据加入到新队列中
def dequeue(item): # 删除队列前端的数据,并返回新队列
global rear
global MAX_SIZE
global front
global queue
if front == rear:
print('队列已满!')
else:
front += 1
item = queue[front] # 删除队列前端数据
def FRONT_VALUE(Queue): # 返回队列前端的值
global rear
global front
global queue
if front == rear:
print('队列已满!')
else:
print(queue[front]) # 返回队列前端值
设计一个程序来实现队列的基本操作,加入数据时输入a,要取出数据时可输入d,并直接打印输出队列前端的值,要结束时输入e.
# CH05-01.py
import sys
MAX=10 #定义队列的大小
queue=[0]*MAX
front=rear=-1
choice=''
while rearfront:
front+=1
print('[取出数值为]: [%d]' %(queue[front]))
queue[front]=0
else:
print('[队列已经空了]')
sys.exit(0)
else:
print()
print('------------------------------------------')
print('[输出队列中的所有元素]:')
if rear==MAX-1:
print('[队列已满]')
elif front>=rear:
print('没有')
print('[队列已空]')
else:
while rear>front:
front+=1
print('[%d] ' %queue[front],end='')
print()
print('------------------------------------------')
print()
# 结果
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 15
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 25
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 35
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: d
[取出数值为]: [15]
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: e
------------------------------------------
[输出队列中的所有元素]:
[25] [35]
------------------------------------------
5.1.3 用链表实现队列
队列除了能用数组实现,还可以用链表来实现。在声明队列的类中,除了和队列中相关的方法外,还必须有指向队列前端和队列末尾的指针,即front和rear指针。例如,我们以学生成绩和姓名的结构数据来建立队列的节点,加上front和rear指针。,这个类的声明如下:
class student:
def __init__(self):
self.name = ''*20
self.score = 0
self.next = None
front = student()
rear = student()
front = None
rear = None
在队列中加入新节点等于加到此队列的末端,而删除节点就是将此队列最前端的节点删除。
def enqueue(name, score): # 把数据加入队列
global front
global rear
new_data=student() # 分配内存给新元素
new_data.name=name # 给新元素赋值
new_data.score = score
if rear==None: # 如果rear为None,表示这是第一个元素
front = new_data
else:
rear.next = new_data # 将新元素连接到队列末尾
rear = new_data # 将rear指向新元素,这是新的队列末尾
new_data.next = None # 新元素之后无其他元素
def dequeue(): # 取出队列中的数据
global front
global rear
if front == None:
print('队列已空!')
else:
print('姓名:%s\t成绩:%d ....取出' %(front.name, front.score))
front = front.next # 将队列前端移到下一个元素
例子:设计一个python程序,链表中的元素节点仍为学生姓名及成绩的结构数据。本程序还包含队列数据的加入、取出、遍历等操作。
class student:
def __init__(self):
self.name=' '*20
self.score=0
self.next=None
front=student()
rear=student()
front=None
rear=None
def enqueue(name, score): # 把数据加入队列
global front
global rear
new_data=student() # 分配内存给新元素
new_data.name=name # 给新元素赋值
new_data.score = score
if rear==None: # 如果rear为None,表示这是第一个元素
front = new_data
else:
rear.next = new_data # 将新元素连接到队列末尾
rear = new_data # 将rear指向新元素,这是新的队列末尾
new_data.next = None # 新元素之后无其他元素
def dequeue(): # 取出队列中的数据
global front
global rear
if front == None:
print('队列已空!')
else:
print('姓名:%s\t成绩:%d ....取出' %(front.name, front.score))
front = front.next # 将队列前端移到下一个元素
def show(): # 显示队列中的数据
global front
global rear
ptr = front
if ptr == None:
print('队列已空!')
else:
while ptr !=None: # 从front到rear遍历队列
print('姓名:%s\t成绩:%d' %(ptr.name, ptr.score))
ptr = ptr.next
select=0
while True:
select=int(input('(1)加入 (2)取出 (3)显示 (4)离开 => '))
if select==4:
break
if select==1:
name=input('姓名: ')
score=int(input('成绩: '))
enqueue(name, score)
elif select==2:
dequeue()
else:
show()
# 结果
(1)加入 (2)取出 (3)显示 (4)离开 => 1
姓名: adam
成绩: 200
(1)加入 (2)取出 (3)显示 (4)离开 => 1
姓名: jonh
成绩: 100
(1)加入 (2)取出 (3)显示 (4)离开 => 3
姓名:adam 成绩:200
姓名:jonh 成绩:100
(1)加入 (2)取出 (3)显示 (4)离开 => 4
队列在计算机中应用广泛,例如:
1)图形遍历的广度优先查找法(BFS)就是使用队列
2)可用于计算机模拟。在模拟过程中,由于各种事件的输入时间不一定,因此可以使用队列来反映真实情况。
3)可作为CPU的作业调度,使用队列来处理可实现先到先执行的要求。‘
4)“外围设备联机并发处理系统”的应用,也就是让输入/输出的数据在高速磁盘驱动器中完成,把磁盘当成一个大型的工作缓冲区(buffer),如此可让输入/输出操作快速完成,缩短了系统响应时间,接下来就是将磁盘数据输出打印到打印机,由系统软件来负责,其中就应用了队列的工作原理。
5.2.1 环形队列
前面的队列会出现如下问题:1)当队列已满时,便使所有的元素向前(左)移动到Q(0)为止,不过,如果队列中数据过多,移动就会比较耗时。
2)利用环形队列让rear和front指针能够永远介于0和n-1之间。
环形队列:一种环形结构的队列,他仍然是Q(0:n-1)的一维数组,同时Q(0)为Q(n-1)的下一个元素,就可以解决无法判断队列是否溢出的问题。指针front永远以逆时针的方向指向队列中的第一个元素的前一个位置。
front指针指向队列第一个元素的前一个位置的原因:
如果环形队列front指针指向队列中第一个元素,此时若幻想队列为空队列和满队列时,均有front和rear指针指向同一个地方,因此无法判断是空队列还是满队列。
sososo:当rear指针指向的下一个位置是front时,就认定队列已满,无法再将数据加入到队列;;;当rear=front时,则可代表队列已空。
# 环形队列的加入算法
def enqueue(item):
if front == rear:
print('队列已满!')
else:
queue[rear] = item
# 环形队列的删除算法
def dequeue(item):
if front == rear :
print('队列已经空!')
else:
front = (front+1)% MAX_SIZE
item = queue[front]
例子:设计一个python程序来实现环形队列的操作,当要取出数据时可输入0,要结束时可输入-1.
# CH05-03.py
queue=[0]*5
front=rear=-1
val=0
while rear<5 and val!=-1:
val=int(input('请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):'))
if val==0:
if front==rear:
print('[队列已经空了]')
break
front+=1
if front==5:
front=0
print('从队列中取出值 [%d]' %queue[front])
queue[front]=0
elif val!=-1 and rear<5:
if rear+1==front or rear==4 and front<=0:
print('[队列已经满了]')
break
rear+=1
if rear==5:
rear=0
queue[rear]=val
print('队列剩余数据:')
if front==rear:
print('队列已空!!')
else:
while front!=rear:
front+=1
if front==5:
front=0
print('[%d]' %queue[front],end='')
queue[front]=0
print()
# 结果
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):98
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):95
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):86
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):0
从队列中取出值 [98]
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):82
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):76
请输入一个值加入队列,要从队列中取出值请输入0。(要结束则输入-1):-1
队列剩余数据:
[95][86][82][76]
5.2.2 双向队列
双向队列(Double Ended Queues,DEQue)是一个有序线性表,加入与删除可在队列的任一端进行。。具体来说就是,双向队列就是允许队列两端中的任意一端都具备删除或加入功能,而且无论是左右两端那一端的队列,队首与队尾指针都是朝队列中央移动的。双向队列可分为以下两种:
1)数据只能从一端加入,但可以从两端取出。
2)数据可以从两端加入,但只能从一端取出。
例子:使用链表数据结构来设计一个输入限制的双向队列的python程序,我们只能从一端加入数据,但是从队列中取出数据时,可以分别从队列的前端和末尾取出。
# CH05-04.py
class Node:
def __init__(self):
self.data=0
self.next=None
front=Node()
rear=Node()
front=None
rear=None
#方法enqueue:队列数据的加入
def enqueue(value):
global front
global rear
node=Node() #建立节点
node.data=value
node.next=None
#检查是否为空队列
if rear==None:
front=node #新建立的节点成为第1个节点
else:
rear.next=node #将节点加入到队列的末尾
rear=node #将队列的末尾指针指向新加入的节点
#方法dequeue:队列数据的取出
def dequeue(action):
global front
global rear
#从队列前端取出数据
if not(front==None) and action==1:
if front==rear:
rear=None
value=front.data #将队列数据从前端取出
front=front.next #将队列的前端指针指向下一个
return value
#从队列末尾取出数据
elif not(rear==None) and action==2:
startNode=front #先记下队列前端的指针值
value=rear.data #取出队列当前末尾的数据
#查找队列末尾节点的前一个节点
tempNode=front
while front.next!=rear and front.next!=None:
front=front.next
tempNode=front
front=startNode #记录从队列末尾取出数据后的队列前端指针
rear=tempNode #记录从队列末尾取出数据后的队列末尾指针
#下一行程序是指当队列中仅剩下最后一个节点时,
#取出数据后便将front和rear指向None
if front.next==None or rear.next==None:
front=None
rear=None
return value
else:
return -1
print('用链表来实现双向队列')
print('====================================')
ch='a'
while True:
ch=input('加入请按 a,取出请按 d,结束请按 e:')
if ch =='e':
break
elif ch=='a':
item=int(input('加入的元素值:'))
enqueue(item)
elif ch=='d':
temp=dequeue(1)
print('从双向队列前端按序取出的元素数据值为:%d' %temp)
temp=dequeue(2)
print('从双向队列末尾按序取出的元素数据值为:%d' %temp)
else:
break
# 结果
用链表来实现双向队列
====================================
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:100
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:99
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:98
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:66
加入请按 a,取出请按 d,结束请按 e:d
从双向队列前端按序取出的元素数据值为:100
从双向队列末尾按序取出的元素数据值为:66
加入请按 a,取出请按 d,结束请按 e:e
5.2.3 优先队列
优先队列为一种不必遵守队列特性FIFO(先进先出)的有序线性表,其中的每一个元素都赋予了一个优先级(Priority),加入元素时可以任意加入,但是有最高优先级者则最先输出。
例如:一般医院中的急诊室,当然以最严重的病患者优先治疗,跟进入医院的挂号顺序无关。
注:当各个元素按照输入先后次序为优先级时,就是一般的队列;假设输入先后次序的倒序作为优先级,此优先队列就是一个堆栈。
part one:
栈(Stack)和队列(Queue)是两种操作受限的线性表。
(线性表:线性表是一种线性结构,它是一个含有n≥0个结点的有限序列,同一个线性表中的数据元素数据类型相同并且满足“一对一”的逻辑关系。
“一对一”的逻辑关系指的是对于其中的结点,有且仅有一个开始结点,并且该开始节点没有前驱但有一个后继结点,有且仅有一个终端结点,并且该终端节点,没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。)
这种受限表现在:栈的插入和删除操作只允许在表的尾端进行(在栈中成为“栈顶”),满足“FIFO:First In Last Out”;队列只允许在表尾插入数据元素,在表头删除数据元素,满足“First In First Out”。
栈与队列的相同点:
1.都是线性结构。
2.插入操作都是限定在表尾进行。
3.都可以通过列表(数组)结构和链表结构实现。、
4.插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。
5.多链栈和多链队列的管理模式可以相同。
栈与队列的不同点:
1.删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。
2.应用场景不同;常见栈的应用场景包括括号问题的求解,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。
3.顺序栈能够实现多栈空间共享,而顺序队列不能。
part two:
“栈” 和 “队列” 是数据结构,与具体的语言无关。
1.队列先进先出,栈先进后出。
2. 对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。 从"数据结构"的角度看,它们都是线性结构,即数据元素之间的关系相同。但它们是完全不同的数据类型。除了它们各自的基本操作集不同外,主要区别是对插入和删除操作的"限定"。 栈和队列是在程序设计中被广泛使用的两种线性数据结构,它们的特点在于基本操作的特殊性,栈必须按"后进先出"的规则进行操作,而队列必须按"先进先出" 的规则进行操作。和线性表相比,它们的插入和删除操作受更多的约束和限定,故又称为限定性的线性表结构。
3.遍历数据速度不同。栈只能从头部取数据 也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性队列怎不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多
栈(Stack)是限定只能在表的一端进行插入和删除操作的线性表。
队列(Queue)是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
从"数据结构"的角度看,它们都是线性结构,即数据元素之间的关系相同。但它们是完全不同的数据类型。除了它们各自的基本操作集不同外,主要区别是对插入和删除操作的"限定"。
栈和队列是在程序设计中被广泛使用的两种线性数据结构,它们的特点在于基本操作的特殊性,栈必须按"后进先出"的规则进行操作,而队列必须按"先进先出"的规则进行操作。和线性表相比,它们的插入和删除操作受更多的约束和限定,故又称为限定性的线性表结构。