一、栈的定义和运算
1.1 栈的定义
栈又称为堆栈,它是一种运算受限的线性表,其限制是仅允许在线性表的固定一端(表尾)进行插入、删除和读取元素等运算,不允许在其他任何位置进行运算,比一般线性表运算更加简单和方便。人们把对栈进行运算的一端称为栈顶,栈顶位置元素被称为栈顶元素,相对地,把另一端称为栈底。向一个栈插入新元素称为进栈,压栈或入栈,它是把新元素放到当前栈顶元素的上面位置,使之成为新的栈顶元素;从一个栈删除元素又称为出栈或退栈,它是把当前栈顶元素删掉,使其下面位置的元素成为新的栈顶元素。(先进后出)
由于栈操作只限定在一端进行,对它做插入和删除等运算时不需要比较和移动任何元素,所以其时间复杂度均为O(1)。
1.2 栈的抽象数据类型
栈接口Stack的具体定义如下:
public interface Stack {
//栈的抽象接口定义
void push(Object obj); //向栈顶插入一个新元素obj
Object pop(); //从栈中删除栈顶元素并返回
Object peek(); //返回栈顶元素的值
boolean isEmpty(); //判断栈是否为空,若是则返回true,否则返回false
}
二、栈的顺序存储结构和操作实现
与线性表的情况相同,栈的顺序存储结构同样需要使用一个数组和一个整型变量,利用数组来顺序存储栈的中所有元素,利用整型变量来存储栈顶元素的下标位置。假如存储栈的数组用stackArray[mimSize]表示,指示栈顶元素位置的整型变量用top表示,通常又称top为栈顶指针,这些对象的具体定义形式为:
final int minSize=10; //假定存储栈的一维数组的初始长度为10
private Object[] stackArray; //定义存储栈的数组引用
private int top; //定义数组中所保存栈的栈顶元素的下标位置
假定采用顺序存储结构实现的栈类命名为SequenceStack,该类的具体定义格式如下,每个方法的具体定义如下:
public class SequenceStack implements Stack {
final int minSize=10; //假定存储栈的一维数组的初始长度为10
private Object[] stackArray; //定义存储栈的数组引用
private int top; //定义数组中所保存栈的栈顶元素的下标位置
//初始化顺序栈为空
public SequenceStack() {
top=-1; //栈的初始为空,置top值为-1
stackArray=new Object[minSize]; //数组初始长度为minSize的值10
}
public SequenceStack(int n)
{
if(n<minSize) //条件成立时将n修改为固定值minSize
{
n=minSize;
}
top=-1;
stackArray=new Object[n]; //创建具有n大小的存储空间
}
//向栈顶插入元素
public void push(Object obj) {
if(top==stackArray.length-1) //如果栈已满,进行再分配空间
{
Object []p=new Object[top*2]; //空间大小为原来的两倍
for(int i=0;i<=top;i++) //复制原内容
{
p[i]=stackArray[i];
}
stackArray=p; //指向新数组空间
}
top++;
stackArray[top]=obj; //将元素写入到栈顶位置
}
//删除栈顶元素
public Object pop() {
if(top==-1)
{
return null; //栈顶为空时,返回空值
}
top--; //栈顶指针减1,表示退栈
return stackArray[top+1]; //返回原栈顶元素的值
}
//读取栈顶元素的值
public Object peek() {
if(top==-1)
{
return null; //栈为空时,返回空值
}
return stackArray[top]; //返回栈顶元素的值
}
//判断栈是否为空
public boolean isEmpty() {
return top==-1;
}
//清除栈中的所有内容使之为空
public void clear() {
top=-1;
}
}
三、栈的链接存储结构和操作实现
栈的链接存储结构与线性表的链接存储结构完全相同,是通过由结点构成的单链表实现的,此时表头指针称为栈顶指针,由栈顶指针指向的元素结点被称为栈顶结点,整个单链表被称为栈链,即链接存储的栈。当向一个链栈插入新元素时,是把它插入到栈顶,即使新元素结点的指针域指向原来的栈顶结点,而栈顶指针则修改为指向此新元素结点,使之成为新的栈顶结点。当从一个链栈中删除元素时,是把栈顶元素结点删除掉,即取出栈顶元素后,使栈顶指针指向其后继结点。由此可知,对链栈的插入和删除操作是在单链表的表头进行的,其时间复杂度为O(1)。
注:其类型为结点类型Node(不懂看集合一文)
public class Node {
Object element; //数值域
Node next; //指针域
public Node(Node nt) //只对next赋值
{
next=nt;
}
public Node(Object obj,Node nt) //对两个域同时赋值
{
element=obj;
next=nt;
}
}
链栈类的定义和每个操作方法的具体定义如下:
public class LinkStack implements Stack {
//实现stack接口的链接栈类的定义
private Node top; //定义top为栈顶指针(引用)
public LinkStack() {
//无参构造函数的定义
top=null; //栈的初始为空,即把空值null赋给top
}
//向栈顶插入一个新元素obj
public void push(Object obj) {
top=new Node(obj,top); //相当于在单链表的表头插入结点
}
//从栈中删除栈顶元素并返回
public Object pop() {
if(top==null)
{
return null; //返回空表示栈已空,无元素删除
}
Node p=top; //栈顶结点的引用暂存到p中
top=top.next; //栈顶指针指向下一个结点,即删除栈顶结点
return p.element; //返回原栈顶结点的值
}
//返回栈顶元素的值
public Object peek() {
if(top==null)
{
return null; //栈空时返回空值
}
return top.element; //返回栈顶结点的值
}
//判断栈是否为空栈,若为空栈返回true,否则返回false
public boolean isEmpty() {
return top==null;
}
//清除栈中的所有元素,使之成为一个空栈
public void clear() {
top=null;
}
}
四、栈的简单应用
1. 用键盘输入一批整数,然后按照相反的次序打印出来
根据题意可知,后输入的整数将先被打印出来,这正好符合栈的先进后出的特点。所以此题很容易通过使用栈来解决。
public static void reverse()
{
//把键盘输入的若干行整数利用栈的功能按照相反次序输出
Stack sck=new SequenceStack(); //定义并创建一个空的顺序栈,链栈也可以
BufferedReader fin; //定义fin为带缓冲的文件读入器引用对象
try
{
//fin指向待缓冲的键盘读入器对象,通过它实现用键盘输入数据
fin=new BufferedReader(new InputStreamReader(System.in));
//当读到新的一行数据只是文件结束符时才结束for循环
for(String s=fin.readLine();s!=null;s=fin.readLine())
{
//每次循环从键盘读入一行数据并赋值给s字符串中,当读入
//文件结束符(Ctrl+Z)时s为空指针,从而结束循环处理过程
StringTokenizer r=new StringTokenizer(s);
//通过字符串类String的对象s来创建对象r,r中保存的字符串
//值与s中完全相同,但能够利用特定方法对r进行更有效的处理
while(r.hasMoreTokens())//此循环能够把一行中用空格分隔的所有整数依次保存到栈中
{
//当r中存在被分隔符隔开的数据是,此循环条件为真
String t=r.nextToken(); //取出r中的一个整数字串赋给t
int x=Integer.parseInt(t); //将t的内容转换为整型数赋给x
Object o = (Object)(new Integer(x));
sck.push(o); //将一个整数值x压入栈
}
}
fin.close(); //关闭与键盘输入相关联的fin流
}
catch(IOException e) //对文件操作异常进行处理
{
System.out.print("发生文件访问异常");
e.printStackTrace(); //显示出发生异常错误的跟踪信息,以便参考
}
while(!sck.isEmpty()) //依次输出键盘中的数据,它与键盘输入次序相反
{
System.out.print(sck.pop()+" ");
}
System.out.println();
}
public static void main(String [] args)
{
reverse();
}
注:输入数字,以空格为分隔符,输入结束,回车换行,输入结束符:(ctrl+z)
2. 堆栈在计算机语言的编译过程中用来进行语法检查,编写一个算法,用来检查一个Java语言程序中的花括号、方括号和圆括号是否配对,若能够全部各自配对则返回逻辑真,否则返回逻辑假。
分析:在这个算法中,需要扫描待检查程序中的每一个字符,当扫描到每个花、中、圆左括号时,令其进栈,当扫描到每个花、中、圆右括号时,则检查栈顶是否为相应的左括号,若是则作退栈处理,若不是则表明出现了语法错误,应返回假。当扫描到程序文件结尾后,若栈为空则表明没有发现括号配对错误,应返回真,否则表明栈中还有为配对的括号,应返回假。
根据分析,编写算法如下:
//对由字符串类对象fname的内容为文件名的文本文件进行括号配对检查
public static boolean bracketsCheck(String fname)
{
Stack sck=new SequenceStack(); //定义并创建一个暂存左括号的顺序栈或链栈
try
{
BufferedReader br=new BufferedReader(new FileReader(fname));
int ch; //建立带缓存的读入器对象br,用于打开文本文件fname
//从头开始依次读取文件中的每个字符并进行处理,-1为文件尾标志
for(ch=br.read();ch!=-1;ch=br.read())
{
if(ch==39)
{
//对单引号内字符不做配对检查
while(true) //循环查找后一个单引号字符
{
if((ch=br.read())==-1)
{
return false; //文件结束返回假
}
if(ch==39) //找到配对的单引号,读取下一字符并退出循环
{
if((ch=br.read())==-1)
{
return false;
}
break;
}
}
}
else if(ch==34) //对双引号内字符不做配对检查
{
while(true) //循环查找后一个双引号字符
{
if((ch=br.read())==-1) //文件结束返回假
{
return false;
}
if(ch==34) //若找到配对的双引号,读取下一字符并退出循环
{
if((ch=br.read())==-1)
{
return false;
}
break;
}
}
}
switch((char)ch) //对扫描到的当前字符进行分类处理
{
case '{':
case '[':
case '(':
{
String str = String.valueOf(ch);
sck.push(str); //出现以上三种左括号则进栈
break;
}
case '}':
{ //读到右花括号时的处理情况
if(sck.peek()==null)
{
return false; //若栈为空则返回假
}
Object c=sck.peek();
String ss=(String)c;
if(ss.equals("{"))
{
sck.pop(); //栈顶的左花括号出栈
}
else
{
return false; //若不配对则返回假
}
break;
}
case ')': //读到右圆括号时的处理情况
{
if(sck.peek()==null)
{
return false; //若栈为空则返回假
}
Object c=sck.peek();
String ss=(String)c;
if(ss.equals("("))
{
sck.pop(); //栈顶的左圆括号出栈
}
else
{
return false; //若不配对则返回假
} //对其他字符不做任何处理,for循环结束
}
}
} //for循环结束
br.close();
}
catch(IOException e) //对文件操作异常进行处理
{
System.out.print("发生文件访问异常:!"+e);
e.printStackTrace();
}
if(sck.isEmpty()) //括号配对成功返回真
{
return true;
}
else
{
return false; //存在不配对的括号返回假
}
}
3. 把十进制整数转换为二至九之间的任一进制数并输出。
分析:把一个十进制整数x转换为任一种r进制数得到的是一个r进制的整数,假定为y,转换方法是逐次除基数r取余法。具体做法是:首先用十进制整数x除以基数r,得到的整余数是r进制数y的最低位y0,接着以x除以r的整数商作为被除数,用它除以r得到的整余数是y的次最低位y1,依次类推,直到商为0时得到的整余数是y的最高位ym,假定y共有m+1位。这样得到的y与x等值,y的按权展开式为:
y=y0+y1r+y2r^2+...ymr^m
从十进制整数转换为r进制数的过程中,由低到高依次得到r进制数中的每一位数字,而输出时又需要由高到低依次输出每一位。所以此问题适合利用栈来解决,具体算法如下:
//把一个长整型数num转换为一个r进制数输出
public static void transform(long num,int r)
{
Stack sck=new LinkStack(); //定义并创建一个暂存余数的栈链或顺序栈
while(num!=0) //由低到高求出r进制数的每一位并入栈
{
int k=(int)(num % r); //求出r进制中的一位赋给k
Object o = new Object();
o = (Object)(new Integer(k)); //将int类型转换为Object类型
sck.push(o); //k的值进栈
num/=r; //num对r的整数商仍保存在num中
}
while(!sck.isEmpty()) //由高到低输出r进制数的每一位数字
{
System.out.print(sck.pop());
}
System.out.println();
}