数据结构之——栈

栈抽象数据类型

栈(Stack)是一种特殊的线性表,其插入和删除只允许在线性表的一端进行。允许操作的一端称为栈顶(Top),不允许操作的一端称为栈底(Bottom)。栈中插入元素的操作称为入栈(Push),删除元素的操作称为出栈(Pop)。没有元素的栈称为空栈。

由于栈的插入和删除只能在栈顶进行,每次入栈元素即成为栈顶元素,每次出栈元素总是最后一个入栈元素,因此栈的特点就是先进后出。栈的基本操作包括创建栈、判断栈是否为空、入栈、出栈和取出栈顶元素等。栈不支持对指定位置的插入、删除等操作。现在声明栈接口如下,描述栈抽象数据类型。

package stack;

public interface Stack<T> {//栈接口,描述抽象数据类型
	public abstract boolean isEmpty();
	public abstract void push(T x);//元素x入栈
	public abstract T peek();//返回栈顶元素,未出栈
	public abstract T pop();//出栈,返回栈顶元素
	

}

接下来我们会用顺序栈以及链式栈来实现stack栈接口。

顺序栈

采用顺序存储结构的栈称为顺序栈。声明顺序栈的泛型类如下,实现栈泛型接口,使用顺序表作为成员变量实现栈,约定null不能入栈。

package stack;
import dataStructure.SeqList;
public class SeqStack<T> implements Stack<T>{
	private SeqList<T> list;
	public SeqStack(int length) {
		this.list=new SeqList<T>(length);
	}
	public SeqStack() {
		this(64);
	}
	public boolean isEmpty() {
		return this.list.isEmpty();
	}

	public void push(T x) {
		this.list.insert(x);
		
	}

	public T peek() {
		return this.list.get(list.size()-1);
	}

	public T pop() {
		return list.remove(list.size()-1);
	}
	

}

其中入栈和出栈实现为顺序表尾插入和顺序表尾删除,时间复杂度为O(1),顺序表的插入方法已实现自动扩充数组功能,当需要扩容的时候,入栈时间复杂度为O(n)。

注意:栈线性表是不同的抽象数据类型,上述栈泛型类只是使用已有的顺序表进行设计,也可声明数组作为成员变量实现栈,我们也可以使用Java自带的Deque来实现栈。

链式栈

采用链式存储结构的栈称其为链式栈。设单链表(不带头结点)头指针top指向第一个元素结点(栈顶),入栈操作是头插入,在栈顶结点之前插入结点,出栈操作是头删除,删除栈顶结点并返回栈顶元素,再让top指向新的栈顶结点。

package stack;
import dataStructure.SinglyList;
public final class LinkedStack<T> implements Stack<T> {
	private SinglyList<T> list;
	public LinkedStack() {
		this.list=new SinglyList<T>();
	}
	public boolean isEmpty() {
		return this.list.isEmpty();
	}
	public void push(T x) {
		this.list.insert(0,x);
	}
	public T peek() {
		return this.list.get(0);
	}
	public T pop() {
		return this.list.remove(0);
	}
}

栈的应用

一.栈是嵌套调用机制的实现基础

在一个函数体中调用另一个函数,称为函数的嵌套调用。例如,在main()函数中调用LinkedStack< T>,在LinkedStack< T>中调用SinglyList< T >,此时三个函数都在执行。根据嵌套规则,每个函数在执行完以后都要返回调用语句。此时最先返回的是SinglyList< T >,故而其为栈顶。

二.使用栈以非递归方式实现算法

1.括号匹配的语法检查

程序中出现的圆括号()以及其他括号都是左右匹配的,如果不匹配,就不能通过编译,编译器将给出错误信息。

以判断表达式中的圆括号是否匹配为例,使用栈判断括号匹配的问题的算法描述如下:

(1)设infix是一个表达式字符串,从左到右依次对infix的每个字符ch进行语法检查。若ch是左括号,则入栈;若ch是右括号,则栈里左括号出栈。若出栈为左括号,表示这一对括号匹配,如果栈空或者出栈字符不是左括号,表示缺少与ch匹配的左括号。

(2)重复执行(1),当infix检查结束,如果栈空,则全部匹配,如果还有左括号,表示缺少右括号。

当infix=“((1+2)*3+4)”的时候,对其分析,i=0时,‘(’入栈,当i=1时,‘(’入栈。当i=5时,ch=‘)’,出栈‘(’,匹配。当i=10时,ch=‘)’,出栈‘(’,匹配。此时栈空,全部匹配。

package stack;

public class Bracket {
	//检查infix表达式中的圆括号是否匹配,若匹配,返回空串;否则返回错误信息
	public static String isMatched(String infix) {
		//声明接口对象stack,引用实现Stack<T>接口的顺序栈类的实例,创建空栈
		Stack<String> stack=new SeqStack<String>(infix.length());
		for(int i=0;i<infix.length();i++) {
			char ch=infix.charAt(i);
			switch(ch) 
			{
			case'(':
				stack.push(ch+"");
				break;
			case')':
				if(!stack.isEmpty()) {
					stack.pop();
					
				}else {
					return "期望(";
				}
					
			}
		}
		return (stack.isEmpty())?"无出错":"期望)";  //返回空串表示没有错误
		}
	public static void main(String[] args) {
	String infix="((1+2)*3+4)";
	System.out.println(infix+",编译错误:"+Bracket.isMatched(infix));
	}
}

程序运行结果如下:
在这里插入图片描述
2.使用栈计算算术表达式的值

将运算符写在两个操作数中间的表达式,称为中缀表达式。中缀表达式中,运算符具有不同的优先级,将运算符写在两个操作数之前/后的表达式,分别称为前/后缀表达式。例如,将中缀表达式“1+2*(3-4)+5”转换为后缀表达式:1 2 3 4 - * + 5 +。后缀表达式中,运算符没有优先级,求值过程按照左到右进行。

(1)将中缀表达式转换为后缀表达式

将中缀表达式infix转换为后缀表达式字符串postfix,设置一个运算符栈,算法从左到右依次对中缀表达式中每个字符ch分别进行以下处理。

1.若ch是数字,则将其数字序列添加到postfix,添加空格作为分隔符。
2.若ch是运算符,将ch入栈。入栈之前,需将ch与栈顶运算符比较,如果栈顶运算符优先级较高或与ch相等,则出栈,添加到postfix,比较多次,当‘(’运算符在栈中,它的优先级最低。
3.若ch是左括号,入栈
4.若ch是右括号‘)’,则若干运算符出栈,直到出栈的是左括号,表示一对括号匹配。
5.表达式结束时,将栈中运算符全部出栈,添加到postfix。

我们以“1+2*(3-4)+5”为例子进行分析。

i=1时,‘+’入栈,后缀:1;i=3时, '* '入栈,后缀:1 2;i=4时,‘(’入栈,后缀:1 2;i=6时,‘-’入栈,后缀:1 2 3;i=8时,ch=‘)’,出栈‘-’、‘(’,后缀:1 2 3 4 -;i=9时,出栈 ‘*’、‘+’,‘+’入栈,后缀:1 2 3 4 - * +

(2)后缀表达式求值

后缀表达式求值算法描述如下,设置一个操作数栈,从左到右依次对postfix后缀表达式字符串中每个字符ch进行处理。

1.若ch是数字,先将其后数字序列转化为整数,再将该整数入栈。
2.若ch是运算符,出栈两个操作数进行运算,运算结果再入栈。
3.重复以上步骤,直至后缀表达式结束,栈中最后一个元素就是所求表达式的结果。

我们以“1 2 3 4 - * + 5 +”例子进行分析。

首先1,2,3,4直接入栈;当i=4时,ch=‘-’,4,3出栈,3-4=-1入栈;当i=5时,ch=‘*’,-1,2出栈,2 *(-1)=-2入栈;i=6时,ch=‘+’,-2,1出栈,1+(-2)=-1入栈;接着5入栈;i=8时,ch=‘+’,5,-1出栈,-1+5=4入栈。

程序如下,toPostfix()方法将中缀表达式转换为后缀表达式后返回;toValue()方法计算后缀表达式的值。两个栈中的数据类型不同,分别使用顺序栈和链式栈实现。

package stack;

public class Expression {
	public static StringBuffer toPostfix(String infix) {     //返回将infix中缀表达式转换成的后缀表达式
		Stack<String> stack=new SeqStack<String>(infix.length());      //运算符栈,顺序栈
		StringBuffer postfix=new StringBuffer(infix.length()*2);       //后缀表达式字符串
		int i=0;
		while(i<infix.length()) {
			char ch=infix.charAt(i);
			switch(ch) {
			case'+':case'-':       //遇到+、-运算符
				while(!stack.isEmpty()&&!stack.peek().equals("("))   //与栈顶运算符相比较
					postfix.append(stack.pop());     //出栈运算符添加到后缀表达式串
				stack.push(ch+"");       //当前运算符入栈
				i++;
				break;
				
			case'*':case'/':   //遇到*、/运算符
				while(!stack.isEmpty()&&(stack.peek().equals("*")||stack.peek().equals("/")))
					postfix.append(stack.pop());        //栈顶优先级高的运算符出栈
				stack.push(ch+"");
				i++;
				break;
			case'(':    //遇到左括号,直接入栈
				stack.push(ch+"");
				i++;
				break;
			case')':
				String out=stack.pop();      //遇到右括号,出栈,若栈空返回null
				while(out!=null&&!out.equals("(")) {
					postfix.append(out);
					out=stack.pop();
				}
				i++;
				break;
			default:        //遇到数字,添加到后缀表达式
				while(i<infix.length()&&ch>='0'&&ch<='9') {
					postfix.append(ch);
					i++;
					if(i<infix.length())
						ch=infix.charAt(i);
					}
			}
			
		}
		while(!stack.isEmpty())
			postfix.append(stack.pop());
		return postfix;
	}
	
	//计算后缀表达式的值
	public static int toValue(StringBuffer postfix) {
		Stack<Integer> stack=new LinkedStack<Integer>(); //操作数栈,链式栈
		int value=0;
		for(int i=0;i<postfix.length();i++) {
			char ch=postfix.charAt(i);
			if(ch>='0'&&ch<='9') {    //遇到数字字符
				value=0;
				String s=String.valueOf(ch);
				value=Integer.parseInt(s);
				stack.push(value);
			}else {
					int y=stack.pop(),x=stack.pop();     //出栈两个操作数,注意出栈顺序
					switch(ch) {
					case'+':value=x+y; break;
					case'-':value=x-y; break;
					case'*':value=x*y; break;
					case'/':value=x/y; break;
					}
					System.out.println(x+(ch+"")+y+"="+value+", ");   //显示运算过程
					stack.push(value);
				}
		}
		return stack.pop();    //返回运算结果
	}
	public static void main(String[] args) {
		String infix="1+2*(3-4)+5";
		StringBuffer postfix=toPostfix(infix);
		System.out.println("infix="+infix+"\npostfix="+postfix+"\nvalue="+toValue(postfix));
	}

}

运行结果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值