数据结构之栈

栈的定义

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

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

原文地址:https://www.cnblogs.com/binbinshan/p/14156586.html