第六节–堆栈与队列算法
堆栈结构在计算机领域中的应用相当广泛,常用于计算机程序的运行,例如递归调用,子程序的调用。堆栈在python程序设计中包含两种方式,分别是数组结构(在python语言中是以列表List仿真数组结构)与链表结构
队列在计算机领域中的应用也相当广泛,例如计算机的模拟(simulation),cpu的作业调度(job scheduling)以及图形遍历的广度优先搜索法(BFS)
堆栈与队列都是抽象数据类型(Abstract Data Type,ADT)
一.数组实现堆栈
以数据结构来实现堆栈的好处是设计的算法相当简单,但是如果堆栈本身的大小是变动的,而数组大小只能事先规划和声明好,那么数组规划太大会浪费空间,规划调小则不够用
python的相关算法如下:
# 判断是否为空堆栈
def isEmpty():
if top==-1:
return True
else:
return False
# 将指定的数据存入堆栈
def push(data):
global top
global MAXSTACK
global stack
if top>=MAXSTACK-1:
print("堆栈已满,无法再加入")
else:
top+=1
stack[top]=data
# 从堆栈取出数据
def pop():
global top
global stack
if isEmpty():
print("堆栈是空的")
else:
print("弹出的元素为:%d" %stack[top])
top=top-1
1.功能要求
用循环来控制元素压人堆栈或弹出堆栈,并仿真堆栈的各种操作,此堆栈最多可容纳100个元素,其中必须包括压人(push)和弹出(pop)函数,在最后输出堆栈内的所有元素
2.源程序
MAXSTACK=100 #定义堆栈的最大容量
global stack
stack=[None]*MAXSTACK #堆栈的数组声明
top=-1 #堆栈的顶端
#判断是否为空堆栈
def isEmpty():
if top==-1:
return True
else:
return False
#将指定的数据压入堆栈
def push(data):
global top
global MAXSTACK
global stack
if top>=MAXSTACK-1:
print('堆栈已满,无法再加入')
else:
top +=1
stack[top]=data #将数据压入堆栈
#从堆栈弹出数据*/
def pop():
global top
global stack
if isEmpty():
print('堆栈是空')
else:
print('弹出的元素为: %d' % stack[top])
top=top-1
#主程序
i=2
count=0
while True:
i=int(input('要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: '))
if i==-1:
break
elif i==1:
value=int(input('请输入元素值:'))
push(value)
elif i==0:
pop()
print('============================')
if top <0:
print('
堆栈是空的')
else:
i=top
while i>=0:
print('堆栈弹出的顺序为:%d' %(stack[i]))
count +=1
i =i-1
print
print('============================')
3.运行结果
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:9
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:5
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:7
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 0
弹出的元素为: 7
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: -1
============================
堆栈弹出的顺序为:5
堆栈弹出的顺序为:9
============================
二.链表实现堆栈
用链表来实现堆栈的优点是随时可以动态改变链表长度,能有效利用内存资源,不过缺点是设计的算法较复杂
python的相关算法如下:
class Node:
def __init__(self):
self.data=0
self.next=None
top=None
def isEmpty():
global top
if(top==None):
return 1
else:
return 0
def push(data):
global top
new_add_node=Node()
new_add_node.data=data
new_add_node.next=top
top=new_add_node
def pop():
global top
if isEmpty():
print("===当前为空堆栈===")
return -1
else:
ptr=top # 指向堆栈的顶端
top=top.next # 将堆栈顶端的指针指向下一个节点
temp=ptr.data # 弹出堆栈的数据
return temp # 将从堆栈弹出的数据返回给主程序
1.功能要求
循环来控制元素的压人堆栈或弹出堆栈,其中必须包括压人(push)与弹出(pop)函数
2.源程序
class Node: #堆栈链结节点的声明
def __init__(self):
self.data=0 #堆栈数据的声明
self.next=None #堆栈中用来指向下一个节点
top=None
def isEmpty():
global top
if(top==None):
return 1
else:
return 0
#将指定的数据压入堆栈
def push(data):
global top
new_add_node=Node()
new_add_node.data=data #将传入的值指定为节点的内容
new_add_node.next=top #将新节点指向堆栈的顶端
top=new_add_node #新节点成为堆栈的顶端
#从堆栈弹出数据
def pop():
global top
if isEmpty():
print('===目前为空堆栈===')
return -1
else:
ptr=top #指向堆栈的顶端
top=top.next #将堆栈顶端的指针指向下一个节点
temp=ptr.data #弹出堆栈的数据
return temp #将从堆栈弹出的数据返回给主程序
#主程序
while True:
i=int(input('要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: '))
if i==-1:
break
elif i==1:
value=int(input('请输入元素值:'))
push(value)
elif i==0:
print('弹出的元素为%d' %pop())
print('============================')
while(not isEmpty()): #将数据陆续从顶端弹出
print('堆栈弹出的顺序为:%d' %pop())
print('============================')
3.运行结果
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:8
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:6
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 1
请输入元素值:7
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: 0
弹出的元素为7
要压入堆栈,请输入1,要弹出则输入0,停止操作则输入-1: -1
============================
堆栈弹出的顺序为:6
堆栈弹出的顺序为:8
============================
三.汉诺塔问题的求解算法
在古印度神庙,庙中有三根木桩,天神希望和尚们把某些数量大小不同的盘子,从第一个木桩全部移动到第三个木桩
搬动时必须遵守下列规则:
- 直径较小的盘子永远只能置于直径较大的盘子上
- 每一次只能移动一个盘子,而且只能从最上面的盘子开始移动
结论如下:例如当有n个盘子时,可将汉诺塔问题归纳成三个步骤
步骤1:将n-1个盘子从木桩1移动到木桩2
步骤2:将第n个最大盘子从木桩1移动到木桩3
步骤3:将n-1个盘子从木桩2移动到木桩3
def hanoi(n,p1,p2,p3):
if n==1:
print("盘子从%d移动%d" %(p1,p3))
else:
hanoi(n-1,p1,p3,p2)
print("盘子从%d移动%d" %(p1,p3))
hanoi(n-1,p2,p1,p3)
1.源程序
def hanoi(n, p1, p2, p3):
if n==1: # 递归出口
print('盘子从 %d 移到 %d' %(p1, p3))
else:
hanoi(n-1, p1, p3, p2)
print('盘子从 %d 移到 %d' %(p1, p3))
hanoi(n-1, p2, p1, p3)
j=int(input('请输入要移动盘子的数量:'))
hanoi(j,1, 2, 3)
2.运行结果
请输入要移动盘子的数量:5
盘子从 1 移到 3
盘子从 1 移到 2
盘子从 3 移到 2
盘子从 1 移到 3
盘子从 2 移到 1
盘子从 2 移到 3
盘子从 1 移到 3
盘子从 1 移到 2
盘子从 3 移到 2
盘子从 3 移到 1
盘子从 2 移到 1
盘子从 3 移到 2
盘子从 1 移到 3
盘子从 1 移到 2
盘子从 3 移到 2
盘子从 1 移到 3
盘子从 2 移到 1
盘子从 2 移到 3
盘子从 1 移到 3
盘子从 2 移到 1
盘子从 3 移到 2
盘子从 3 移到 1
盘子从 2 移到 1
盘子从 2 移到 3
盘子从 1 移到 3
盘子从 1 移到 2
盘子从 3 移到 2
盘子从 1 移到 3
盘子从 2 移到 1
盘子从 2 移到 3
盘子从 1 移到 3
四.八皇后问题的求解算法
如果放置新皇后的行(或列)的8个位置都没有办法放置新皇后(放入任何一个位置,都会被先前放置的就皇后吃掉),就必须从堆栈中弹出前一个皇后的位置,并在该行(或该列中重新寻找另一个新的位置来放,再将该位置压人堆栈中,而这种方式就是一种回溯(Backtracking)算法的应用
N皇后问题的解答就是结合堆栈和回溯两种数据结构,以逐行(或逐列)寻找新皇后合适的位置(如果找不到,就回溯到前一行寻找前一个皇后的另一个新位置,以此类推)的方式来寻找N皇后问题的其中一组解答
1.源程序
global queen
global number
EIGHT=8 #定义堆栈的最大容量
queen=[None]*8 #存放8个皇后的行位置
number=0 #计算总共有几组解的总数
#决定皇后存放的位置
#输出所需要的结果
def print_table():
global number
x=y=0
number+=1
print('')
print('八皇后问题的第%d组解 ' %number)
for x in range(EIGHT):
for y in range(EIGHT):
if x==queen[y]:
print('<q>',end='')
else:
print('<->',end='')
print(' ')
input('
..按下任意键继续..
')
#测试在(row,col)上的皇后是否遭受攻击
#若遭受攻击则返回值为1,否则返回0
def attack(row,col):
global queen
i=0
atk=0
offset_row=offset_col=0
while (atk!=1)and i<col:
offset_col=abs(i-col)
offset_row=abs(queen[i]-row)
#判断两皇后是否在同一行或在同一对角线上
if queen[i]==row or offset_row==offset_col:
atk=1
i=i+1
return atk
def decide_position(value):
global queen
i=0
while i<EIGHT:
if attack(i,value)!=1:
queen[value]=i
if value==7:
print_table()
else:
decide_position(value+1)
i=i+1
#主程序
decide_position(0)
2.运行结果
八皇后问题的第1组解
<q><-><-><-><-><-><-><->
<-><-><-><-><-><-><q><->
<-><-><-><-><q><-><-><->
<-><-><-><-><-><-><-><q>
<-><q><-><-><-><-><-><->
<-><-><-><q><-><-><-><->
<-><-><-><-><-><q><-><->
<-><-><q><-><-><-><-><->
..按下任意键继续..
八皇后问题的第2组解
<q><-><-><-><-><-><-><->
<-><-><-><-><-><-><q><->
<-><-><-><q><-><-><-><->
<-><-><-><-><-><q><-><->
<-><-><-><-><-><-><-><q>
<-><q><-><-><-><-><-><->
<-><-><-><-><q><-><-><->
<-><-><q><-><-><-><-><->
..按下任意键继续..
五.数组实现队列
与堆栈不同的是需要拥有两种基本操作:加入与删除,而且要使用front与rear两个指针来分别指向队列的前端与末尾,缺点是数组大小无法根据队列的实际需要来动态申请,只能说明固定的大小
MAXSIZE=4
queue=[0]*MAXSIZE
front=-1
rear=-1 # 队列为空时,front=-1,rear=-1
队列操作的过程用python语言将以数组操作队列的相关算法:
def enqueue(item): # 将新数据加入Q的末尾,返回新队列
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])
1.功能要求
打印输出队列前端的值
2.源程序
import sys
MAX=10 #定义队列的大小
queue=[0]*MAX
front=rear=-1
choice=''
while rear<MAX-1 and choice !='e':
choice=input('[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: ')
if choice=='a':
val=int(input('[请输入数值]: '))
rear+=1
queue[rear]=val
elif choice=='d':
if rear>front:
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()
3.运行结果
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 12
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 8
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: a
[请输入数值]: 1
[a]表示加入一个数值,[d]表示取出一个数值,[e]表示跳出此程序: e
------------------------------------------
[输出队列中的所有元素]:
[12] [8] [1]
------------------------------------------
六.链表实现队列
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:
front=new_data
else:
rear.next=new_data
rear=new_data
new_data.next=None
def dequeue():
global front
global rear
if front==None:
print("队列已空!")
else:
print("姓名:%s 成绩:%d...取出" %(front.name,front.score))
front=front.next
1.功能要求
链表中元素节点仍为学生姓名及成绩的结构数据
2.源程序
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 成绩:%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 成绩:%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()
3.运行结果
(1)加入 (2)取出 (3)显示 (4)离开 => 1
姓名: John
成绩: 96
(1)加入 (2)取出 (3)显示 (4)离开 => 1
姓名: SMith
成绩: 87
(1)加入 (2)取出 (3)显示 (4)离开 => 3
姓名:John 成绩:96
姓名:SMith 成绩:87
(1)加入 (2)取出 (3)显示 (4)离开 => 4
七.双向队列
双向队列(Double Ended Queues,DEQue)为一个有序线性表,加入与删除可在队列的任意一端进行
双向队列的应用可以区分为两种:第一种是数据只能从一端加入,但可从两端取出;;另一种则是可从两端加入,但从一端取出
下面我们将讨论第一种输入限制的双向队列
1.源程序
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
2.运行结果
用链表来实现双向队列
====================================
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:98
加入请按 a,取出请按 d,结束请按 e:a
加入的元素值:86
加入请按 a,取出请按 d,结束请按 e:d
从双向队列前端按序取出的元素数据值为:98
从双向队列末尾按序取出的元素数据值为:86
加入请按 a,取出请按 d,结束请按 e:e