06 堆栈与队列算法
二者均是抽象数据类型( Abstract Data Type, ADT )
堆栈在 Python 中包含两种方式,分别是数组结构(以List仿真数组结构)和链表结构
-
用数组实现堆栈
设计算法简单。但是,如果堆栈本身大小是可以变动的,而数组大小只能事先规划和声明好,那么数组规划大了会浪费空间,小了不够用。- 判断是否为空堆栈
def is_empty(): global top if top == -1: return True else: return False
- 入栈
def push(data): global top global MAXSTACK # 堆栈最大容量 global stack # stack = [None] * MAXSTACK 声明堆栈 if top >= MAXSTACK - 1: return "堆栈已满" else: top += 1 stack[top] = data
- 出栈
def pop(): global top global stack if is_empty(): return "堆栈是空的" else: data = stack[top] top -= 1 return data
- 判断是否为空堆栈
-
用链表实现堆栈
算法复杂。动态改变链表长度,有效利用内存资源。class Node: def __init__(self): self.data = 0 self.next = None
- 是否为空
def is_empty(): global top if top is None: return True else: return False
- 入栈
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 is_empty(): return "堆栈是空的" else: ptr = top top = top.next # 从头部删除 data = ptr.data return data
- 是否为空
-
汉诺塔问题
递归(反复执行+有出口)+ 堆栈。T = 2^n - 1
(1) 将 n-1 个盘子从 1 移到 2
(2) 将第 n 个最大的盘子从 1 移到 3
(3) 将 n-1 个盘子从 2 移到 3def hanoi(n, p1=1, p2=2, p3=3): 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)
-
八皇后问题
在棋盘放入一个新皇后,且这个位置不会被就皇后吃掉(不限定走几格:直吃、横吃、对角斜吃),便将新皇后压入堆栈。
如果放置新皇后的行(或列)的 8 个位置都没有办法放置一个新皇后(放入任何一个位置都会被吃掉),就必须从堆栈中弹出前一个皇后的位置,并在该行(或该列)重新寻找另一个位置,将新位置压入堆栈(如果找不到,就回溯到前一行寻找前一个皇后的另一个新位置)。回溯( Backtracking )算法。global queen global number EIGHT = 8 # 定义堆栈的最大容量 queen = [None] * 8 # 存放8个皇后的行位置 number = 0 # 计算共有几组解 def print_table(): global number x = y = 0 number += 1 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('\n..按下任意键继续..\n') # 测试在(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 += 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 += 1 decide_position(0)
-
用数组实现队列
算法简单。缺点数组大小无法根据队列的实际需求来动态申请,只能声明固定的大小。
用 front 和 rear 两个指针来分别指向队列的前端和末尾。NAXSIZE = 4 queue = [0] * MAXSIZE front = -1 rear = -1
添加一个元素,将 rear 值加 1;
取出一个元素,将 front 值加 1。
rear = MAXSIZE - 1,表示队列已满。- 入队
def enqueue(item): global rear global MAXSIZE global queue if rear == MAXSIZE - 1: print('队列已满') else: rear += 1 queue[rear] = item # 将新数据加到队列的末尾
- 出队
def dequeue(): global rear global front global queue global MAXSIZE if front == rear: print('队列已空') else: front += 1 item = queue[front]
- 返回队首值
def front_value(): global rear global front global queue if front == rear: print('这是空队列!') else: print(queue[front])
- 入队
-
用链表实现队列
- 入队
def enqueue(item): global front global rear new_data = Node() new_data.value = item if 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: front = front.next
- 入队
-
双向队列( Double Ended Queues, DEQue )
Lfront, Lrear Rfront, Rrear- 一端加入,两端取出
def enqueue(value): global front global rear node = Node() node.data = value node.next = None if rear == None: # 检查是否为空队列 front = node else: rear.next = node # 将节点加入到队列的末尾 rear = node # 将尾指针指向新加入的节点
def dequeue(action): global front global rear # action = 1, 从前端取出数据 if not(front == None) and action == 1: if front == rear: rear = None value = front.data front = front.next return value # action = 2, 从队列末尾取出 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
- 两端加入,一端取出
- 一端加入,两端取出
-
优先队列( Priority Queue )
加入元素时可任意加入,但先输出优先级高者( HPOF, Highest Priority Out First )
当各元素按输入先后顺序为优先级时,就是普通队列。若以输入顺序的倒序为优先级,则是堆栈。
堆栈:一组相同数据类型数据的集合,所有的操作都在堆栈顶端进行,具有“先进后出”的特性。
常见的堆栈应用:
- 二叉树和森林的遍历运算,例如中序遍历Inorder、前序遍历Preorder等。
- 计算机中央处理单元的中断处理Interrupt Handling。
- 图的深度优先搜索法DFS。
- 某些所谓的堆栈计算机Stack Computer,采用空地址zero-address 指令,其指令没有操作数,大部分都通过弹出Pop和压入Push两个指令来处理程序。
- 递归程序的调用和返回。在每次递归之前,必须先把下一个指令的地址和变量值保存到堆栈中。当从递归返回时,则按序从堆栈顶端取出这些相关值,回到原来执行递归之前的状态,再往下继续执行。
- 算数表达式的转换和求值,例如中序法转换成后续法。
- 调用子程序和返回处理,例如在执行调用的子程序之前,必须先将返回地址(下一条指令的地址)压入堆栈中,然后才开始执行调用子程序的操作,等到子程序执行完毕之后,在从堆栈中弹出返回地址。
- 编译错误处理Compiler Syntax Processing。例如,当编译程发生错误或警告信息时,会将所在的地址压入堆栈中,之后才会显示出错误信息的对照表。
队列的应用:
- 图的广度优先查找BFS
- 计算机的模拟
- CPU 的作业调度
- 外部设备联机并发处理系统