zoukankan      html  css  js  c++  java
  • 数据结构之栈

    栈的定义

    栈(stack)是限定仅在表尾进行插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),没有任何数据元素的栈称为空栈。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

    栈元素具有线性关系,及前驱和后继的关系。关于定义中仅在表尾插入和删除的操作,这里的表尾指的是栈顶。栈元素的特殊之处在于限制了插入和删除的位置,只能在栈顶进行,所以栈底是固定的,最先进栈的只能在栈底。

     

    栈的抽象数据类型

    Data:和线性表一样,元素具有相同类型,相邻的元素具有前驱和后继

    Operation:

    InitStack(*S):初始化操作,建立一个空栈S

    DestroyStack(*S)若栈存在,则销毁它

    ClearStack(*S)将栈清空

    StackEmpty(S)若栈为空返回True.否则返回False

    GetTop(S,*e)若栈存在并且非空,用e返回S的栈顶元素

    Push(*S,e)若栈S存在,插入新元素e到栈S并称为栈顶元素

    Pop(*S,*e)删除栈顶元素,并用e返回其值

    StackLength(S)返回栈S的元素个数

    栈的顺序存储结构实现

    使用顺序结构存储的栈我们称为顺序栈,和线性表顺序存储结构的实现一样,我们采用数组的方式来实现顺序栈,使用数组中下标为0的一端作为栈底,我们定义一个Top变量来指示栈顶元素在数组中的位置,定义栈的长度为 Stacksize,则top<StackSize,当栈存在一个元素时,top为0,空栈的判断为top-1。

    现有一个栈,Stacksize=5,则栈的示意图如下:

    顺序栈的实现:

    /**
     * 基于数组实现的顺序栈
     * @param <E>
     */
    public class Stack<E> {
        private Object[] data = null;
        private int maxSize=0;   //栈容量
        private int top =-1;  //栈顶指针
        
        /**
         * 构造函数:根据给定的size初始化栈
         */
        Stack(){
            this(10);   //默认栈大小为10
        }
        
        Stack(int initialSize){
            if(initialSize >=0){
                this.maxSize = initialSize;
                data = new Object[initialSize];
                top = -1;
            }else{
                throw new RuntimeException("初始化大小不能小于0:" + initialSize);
            }
        }
        
        //判空
        public boolean empty(){
            return top==-1 ? true : false;
        }
        
        //进栈,第一个元素top=0;
        public boolean push(E e){
            if(top == maxSize -1){
                throw new RuntimeException("栈已满,无法将元素入栈!");
            }else{
                data[++top]=e;
                return true;
            }    
        }
        
        //查看栈顶元素但不移除
        public E peek(){
            if(top == -1){
                throw new RuntimeException("栈为空!");
            }else{
                return (E)data[top];
            }
        }
        
        //弹出栈顶元素
        public E pop(){
            if(top == -1){
                throw new RuntimeException("栈为空!");
            }else{
                return (E)data[top--];
            }
        }
        
        //返回对象在堆栈中的位置,以 1 为基数
        public int search(E e){
            int i=top;
            while(top != -1){
                if(peek() != e){
                    top --;
                }else{
                    break;
                }
            }
            int result = top+1;
            top = i;
            return result;      
        }
    }

    栈的链式存储结构实现

    栈的链式存储结构,称为链栈。我们把栈顶放到单链表的头部,通常对链栈来说是不需要头结点。

    对于链栈来说一般不存在栈满的情况,除非内存没有可用空间了,对于空栈来说,链表原定义的是头指针指向空,那么链栈的空就是top=NULL

    链栈的实现

    public class LinkStack<E> {
        //链栈的节点
        private class Node<E>{
            E e;
            Node<E> next;
            
            public Node(){}
            public Node(E e, Node next){
                this.e = e;
                this.next = next;
            }
        }
        
        private Node<E> top;   //栈顶元素
        private int size;  //当前栈大小
        
        public LinkStack(){
            top = null;
        }
        
        //当前栈大小
        public int length(){
            return size;
        }
        
        //判空
        public boolean empty(){
            return size==0;
        }
        
        //入栈:让top指向新创建的元素,新元素的next引用指向原来的栈顶元素
        public boolean push(E e){
            top = new Node(e,top);
            size ++;
            return true;
        }
        
        //查看栈顶元素但不删除
        public Node<E> peek(){
            if(empty()){
                throw new RuntimeException("空栈异常!");
            }else{
                return top;
            }
        }
        
        //出栈
        public Node<E> pop(){
            if(empty()){
                throw new RuntimeException("空栈异常!");
            }else{
                Node<E> value = top; //得到栈顶元素
                top = top.next; //让top引用指向原栈顶元素的下一个元素 
                value.next = null;  //释放原栈顶元素的next引用
                size --;
                return value;
            }
        }
    }

    如果在栈的使用过程中元素变化不可预料,有时大有时小,那么最好采用链栈,反之变化在可控范围内使用顺序栈。

    两栈共享空间

            如果有两个类型相同的栈,我们为它们分别开辟了数组空间。极有可能是一个栈已经满了,再入栈就溢出了,而另一个栈却还有很多存储空间。这又何必呢?我们完全可以用一个数组来存储两个栈,只不过需要一些小的技巧。
      我们的做法如下,数组有两个端点,两个栈有两个栈底。让一个栈的栈底为数组的始端,即数组下标为0的位置。让另一个栈的栈底为数组的末端,即数组下标为n-1的位置。这样如果两个栈增加元素,就是两端点向中间延伸。

     其实关键思路是:它们是在数组的两端,向中间靠拢。top1和top2是两个栈的栈顶指针。只要它们两个不见面,两个栈就可以一直使用。

    代码实现:

    /**
     * 两栈共享空间
     * @author Administrator
     *
     */
    public class BothStack <T>{
    	
    	
    	
    	private Object[] element; //存放元素的数组
    	
    	private int stackSize;  // 栈大小
    	
    	private int top1; //栈1的栈顶指针
    	
    	private int top2; //栈2的栈顶指针
    	
    	
    	/**
    	 * 初始化栈
    	 * @param size
    	 */
    	public BothStack(int size){
    		element = new Object[size];
    		stackSize = size;
    		top1 = -1;
    		top2 = stackSize;
    	}
    	
    	
    	/**
    	 * 压栈
    	 * @param i 第几个栈
    	 * @param o 入栈元素
    	 * @return 
    	 */
    	public boolean push(int i , Object o){
    		
    		if(top1 == top2 - 1)
    			throw new RuntimeException("栈满!");	
    		else if(i == 1){
    			top1++;
    			element[top1] = o;
    		}else if(i == 2){
    			top2--;
    			element[top2] = o;
    		}else
    			throw new RuntimeException("输入错误!");
    			
    		return true;
    	}
    	
    	/**
    	 * 出栈
    	 * @param i
    	 * @return
    	 */
    	@SuppressWarnings("unchecked")
    	public T pop(int i){
    		
    		if(i == 1){
    			if(top1 == -1)
    				throw new RuntimeException("栈1为空");
    			return (T)element[top1--];
    		} else if(i == 2){
    			if(top2 == stackSize)
    				throw new RuntimeException("栈2为空");
    			return (T)element[top2++];
    		} else 	
    		throw new RuntimeException("输入错误!");
    				
    	}
    	
    	
    	/**
    	 * 获取栈顶元素
    	 * @param i
    	 * @return
    	 */
    	@SuppressWarnings("unchecked")
    	public T get(int i){
    		
    		if(i == 1){
    			if(top1 == -1)
    				throw new RuntimeException("栈1为空");
    			return (T)element[top1];
    		} else if(i == 2){
    			if(top2 == stackSize)
    				throw new RuntimeException("栈2为空");
    			return (T)element[top2];
    		} else 	
    		throw new RuntimeException("输入错误!");
    	}
    	
    	
    	/**
    	 * 判断栈是否为空
    	 * @param i
    	 * @return
    	 */
    	public boolean isEmpty(int i){
    		
    		if(i == 1){
    			if(top1 == -1)
    				return true;
    			else
    				return false;
    		} else if(i == 2){
    			if(top2 == stackSize)
    				return true;
    			else
    				return false;
    		} else 	
    		throw new RuntimeException("输入错误!");
    	}
    	
    	/**
    	 * 遍历
    	 */
    	@SuppressWarnings("unchecked")
    	@Override
    	public String toString(){
    		
    		String str1 = "栈1:[";
    		String str2 = "栈2:[";
    		
    		for(int i=top1;i>=0;i--){
    			if(i == 0)
    				str1 = str1 + (T)element[i];
    			else
    				str1 = str1 + (T)element[i] + ",";
    		}
    		
    		str1 += "]";
    		
    		for(int i=top2;i<stackSize;i++){
    			if(i == stackSize-1)
    				str2 = str2 + (T)element[i];
    			else
    				str2 = str2 + (T)element[i] + ",";
    		}
    		
    		str2 += "]";
    		
    		return str1 + "
    
    " + str2;
    		
    	}
    	
    }
    

    栈的应用一:递归

    我们通常使用的递归就是栈来实现的,简单说我们递归前行阶段,我们将每一次的递归,函数的局部变量,参数值及返回地址压入栈中,在退回阶段再把压入的值给弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了之前调用的状态。

    栈的应用二:四则运算表达式(后缀表达式)

    计算机中计算带括号的四则运算采用的是后缀表达式,后缀表达式就是所有的运算符都出现在要运算数字的后面出现。

    对于9+(3-1)*3+10/2,后缀表达式的方法为“9 3 1 - 3 * + 10 2 / +” ,后缀表达式的计算方法,从左到右遍历表达式的每个符号和数字,遇到数字就进栈,遇到符号就将栈顶的两个数字出栈,进行计算,将运算结果进栈,一直到获得最终结果。

    1.初始化一个空栈,因为前三个都是数字,就将 9 3 1入栈,如图:

    2.下面是“-” ,所以将1出栈作为减数,3出栈作为被减数,运算得到结果2,将2入栈,然后后面是数字3.入栈,如图:

    3.后面是*号,将3 2 出栈相乘,得到结果6入栈,下面是+号将6和9出栈相加,得到15入栈,如图:

     4.接下来数字10和2入栈,继续遇见符号号,栈顶10 与2出栈相除得到5.将5入栈,如图:

    5.最后符号为+,将15和5出栈 相加得到20并入栈,最后20出栈得到最终结果,如图:

     下面我们推导如何将标准四则运算(中缀表达式,因为符号都在中间)转化成后缀表达式:

    从左到右遍历所有的字符和数字,若是数字就输出,成为后缀表达式的一部分,若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级低于栈顶符号(先乘除后加减)则 将栈顶元素依次出栈并输出,并将当前符号进栈,一直到最后输出后缀表达式。

    1.初始化空栈,用来对符号进行出入栈,如图:

    2.第一个字符为9,输出,后面符号为“+”,则入栈。第三个符号是“(”,因为只是左括号还未配对,依旧进栈,下面符号是数字3,输出,这是表达式为 9 3 ,接着是“-”,则进栈。如图:

    3.接下来数字1,表达式当前为 9 3 1,后面符号为“)”,此时需要匹配前面的左括号,所以栈顶依次出栈并输出,直至左括号出栈,此时左括号上只有“-”号,因此此时表达式为9 3 1 - ,接着是数字3 输出,表达式为 9 3 1 - 3 ,下面是*号,此时栈顶符号为+号,优先级低于*号,所以不输出并将*号入栈,如图:

    4.之后是符号 +,当前栈顶符号为*号,优先级比+号高,因此栈中元素出栈并输出(因为没有比+号更低的优先级,所以全部出栈),表达式为9 3 1 - 3 * + ,然后将下面的符号+入栈,下面是数字10输出,表达式为9 3 1 - 3 * + 10 ,下一个符号是/入栈,如图:

     5.最后一个数字2,输出表达式为9 3 1 - 3 * + 10 2,因为已经到最后将所以符号出栈并输出,表达式为9 3 1 - 3 * + 10 2 / +  ,如图:

     将中缀表达式转为后缀表达式(栈用来进出运算的符号),将后缀表达式运算出结果(栈用来进出运算的数字)

  • 相关阅读:
    bzoj 4012: [HNOI2015]开店
    POJ 1054 The Troublesome Frog
    POJ 3171 Cleaning Shifts
    POJ 3411 Paid Roads
    POJ 3045 Cow Acrobats
    POJ 1742 Coins
    POJ 3181 Dollar Dayz
    POJ 3040 Allowance
    POJ 3666 Making the Grade
    洛谷 P3657 [USACO17FEB]Why Did the Cow Cross the Road II P
  • 原文地址:https://www.cnblogs.com/binbinshan/p/14156586.html
Copyright © 2011-2022 走看看