• 数据结构之栈


    栈的定义

    栈(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 / +  ,如图:

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

  • 相关阅读:
    关于可控进程数的PHP多进程以及信号控制
    编译安装PHP遇到的问题
    MySQL数据的导入导出
    关于svn的dump和load命令
    Nginx多域名的泛解析
    kafka
    JavaScript
    失误与经验
    Hadoop
    Storm
  • 原文地址:https://www.cnblogs.com/binbinshan/p/14156586.html
Copyright © 2020-2023  润新知