• 第六节--堆栈与队列算法


    第六节–堆栈与队列算法

    堆栈结构在计算机领域中的应用相当广泛,常用于计算机程序的运行,例如递归调用,子程序的调用。堆栈在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
    ============================
    

    三.汉诺塔问题的求解算法

    在古印度神庙,庙中有三根木桩,天神希望和尚们把某些数量大小不同的盘子,从第一个木桩全部移动到第三个木桩

    搬动时必须遵守下列规则:

    1. 直径较小的盘子永远只能置于直径较大的盘子上
    2. 每一次只能移动一个盘子,而且只能从最上面的盘子开始移动

    结论如下:例如当有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)为一个有序线性表,加入与删除可在队列的任意一端进行
    未命名文件.png

    双向队列的应用可以区分为两种:第一种是数据只能从一端加入,但可从两端取出;;另一种则是可从两端加入,但从一端取出

    下面我们将讨论第一种输入限制的双向队列

    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
    

    八.优先队列

  • 相关阅读:
    面试题
    学习Javascript闭包(Closure)
    git命令
    css3 3d翻转效果
    meta 如何写
    Java String equals和==的比较
    MySQL Explain详解
    MySQL explain,type分析(转)
    python的内存分析和处理
    python lambda的使用
  • 原文地址:https://www.cnblogs.com/LQ6H/p/10346665.html
Copyright © 2020-2023  润新知