JAVA在程序运行时,在内存中划分5片空间进行数据的存储。分别是:1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。因此,栈对数据的存储是在java运行时在内存中进行的,当程序退出或者执行完毕,它们就会销毁。因此通常只是作为一种程序构思数据结构的辅助工具,后面要讲到的队列也是如此。
栈的定义和特性
一、概念:
栈是一种只允许在一端进行插入或删除的线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。栈的操作端通常被称为栈顶,另一端被称为栈底。栈的插入操作称为进栈(压栈|push);栈删除操作称为出栈(弹栈|pop)。
二、特性:
栈就像一个容器,我们只能从容器口放和取,因而按照后进先出(LIFO, Last In First Out)的原理运作。栈也称为后进先出表。
三、存储结构(物理结构)
1.顺序存储的栈称为顺序栈,结构如图1;
图[1]
2.链式存储的栈称为链式栈,结构如图2。
图[2]
Java栈实现
1.顺序栈的实现:
/**
*
* 顺序栈(SqeStack)
*/
public class SqeStack<T>{
private T data[];//用数组表示栈元素
private int maxSize;//栈空间大小(常量)
private int top;//栈顶指针(指向栈顶元素)
public SqStack(int maxSize){
this.maxSize = maxSize;
this.data = (T[])new Object[maxSize];//需要使用Object来创建
this.top = -1;//初始化栈顶指针位置
}
//判断栈是否为空
public boolean isNull(){
return this.top == -1 ? true : false;
}
//判断是否栈满
public boolean isFull(){
return this.top == this.maxSize-1 ?true:false;
}
//获取栈顶元素
public T peek(){
if(isNull()){
return null;
}
return data[top-1];
}
//压栈
public boolean push(T vaule){
if(isFull()){ //栈满
return false;
}else{
data[++top] = vaule;//栈顶指针加1并赋值
return true;
}
}
//弹栈
public T pop(){
if(isNull()){
//栈为空
return null;
}else{
T value = data[top];//取出栈顶元素
--top;//栈顶指针-1
return value;
}
}
}
这个栈是用数组实现的,内部定义了一个数组,一个表示最大容量的值以及一个指向栈顶元素的top变量。构造方法根据参数规定的容量创建一个新栈,push()方法是向栈中压入元素,指向栈顶的变量top加一,使它指向原顶端数据项上面的一个位置,并在这个位置上存储一个数据。pop()方法返回top变量指向的元素,然后将top变量减一,便移除了数据项。要知道 top 变量指向的始终是栈顶的元素。
以上我们虽然现实了一个用数组连续存储顺序栈,但是我们发现一个局限,就是栈需要初始化容量,那么能不能不初始化容量呢?
2.链表栈的实现
针对以上问题,我们可以用链表的存储方式来解决,看下面代码:
/*
* 链表元素
*/
class Node<E>{
Node<T>next=null;
T data;
public Node(T data){
this.data=data;
}
}
/*
* 用链表来实现栈
*/
public class LinkedStack<T> {
Node<T>top=null; //头结点设置为空
//判断是否为空
public boolean isEmpty(){
return top==null;
}
//入栈
public void push(T data){
Node<T>newNode=new Node<T>(data);
newNode.next=top;//将现在栈顶的元素的值放在第二个
top=newNode; //新的栈顶元素的值放在top中
}
//出栈
public T pop(){
if(this.isEmpty()){
return null;
}
T data= top.data;
top=top.next;
return data;
}
public T peek(){
if(this.isEmpty()){
return null;
}
T data=top.data;
return data;
}
}
以上的代码是用链表的形式实现的一个链表栈,比较难懂的在于设计链表的节点元素,大家可以阅读理解下。
栈的应用实例
栈结构是很基本的一种数据结构,所以栈的应用也很常见,根据栈结构“先进后出”的特点,我们可以在很多场景中使用栈,下面我们就是使用上面我们已经实现的栈进行一些常见的应用:十进制转N进制、行编辑器、校验括号是否匹配、中缀表达式转后缀表达式、表达式求值等。
1.十进制转16进制实现
class StackUtil{
private static SqeStack<?> stack;
//弹栈出所有元素
public static Object[] popAll(SqeStack<?> s){
stack = s;
if(stack.isNull()){
return null;
}else{
Object[] array = new Object[stack.peek()+1];
int i = 0;
while(!stack.isNull()){
array[i]=stack.pop();
i++;
}
return array;
}
}
//转换 hex进制
public String int2hex(Integer num,int hex){
if(hex<=0||hex>36){
return "无效的进制";
}
if(num == 0){
return "0"
} else if(num>0){//正数
return "-".convert(num,hex);
}else{//负数
num = -num;//先去负号
return "-".convert(num,hex);
}
}
//转换进制
public static String convert(Integer num,int hex){
SqeStack<Integer> stack = new SqeStack<Integer>(hex);
int tmp = num;
while(num!=0){
num = num / hex ;
int mod = tmp % hex;//取余压栈
stack.push(mod);
tmp = num;
}
Object[] o = popAll(stack);//弹栈取出余数
StringBuilder sb = new StringBuilder();
for(Object i : o){
int in = (int)i;
//取出的数字如果>=10需要用字母代替
if(in>=10){
char c = (char) ('A'+in-10);
sb.append(c);
}else{
sb.append(i);
}
}
return sb.toString();
}
}
2.校验符合是否匹配
public class StackUtils{
//表达式括号是否匹配()[]{}
public static boolean isMatch(String str) {
SqeStack<Character> stack = new SqeStack<Character>(str.length()+1);
char[] arr = str.toCharArray();
for (char c : arr) {
//遇到左括号进栈
if(c=='('||c=='{'){
stack.push(c);
}
//遇到右括号匹配栈顶符号
else if(c==')'){
if(stack.isNull()){
return false;//栈为空,匹配失败
}else if(stack.pop()=='('){
continue;//匹配成功继续下一次循环
}else{
return false;//匹配不成功代表该表达式不符合规则
}
} else if(c=='}'){
if(stack.isNull()){
return false;//栈为空,匹配失败
}else if(stack.pop()=='{'){
continue;//匹配成功继续下一次循环
}else{
return false;//匹配不成功代表该表达式不符合规则
}
}
}
//如果最后没有右括号但是还存在左括号表示不匹配
return stack.isNull();
}
栈总结
栈通过提供限制性的访问方法push()和pop(),使得程序不容易出错。根据栈的先入后出特性,我们做了匹配字符和进制转换的例子,从这里可以看出,栈不是真正用来存储数据的,只是用来辅助完成一些特定的工作。
我们稍微分析也可以知道,栈的工作就是push和pop,每一步完成即销毁,因此他的时间复杂度是O(1),而且他并不受栈中元素个数的影响,因此栈的运行速度是非常快的,这也是和Java虚拟机的内存分配是相吻合的。