一、栈的定义和运算


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();
	}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值