我是野猪。
栈的概念:栈作为一种数据结构,是一种只能在一端插入和删除操作的特殊线性表。它按照新进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹数据。
关键点:1.先入后出 2.访问限制,在特定的时刻,只允许一个元素被读取或删除(栈顶的元素)。3.栈操作:栈空、栈满、弹栈、入栈等
栈相关知识:
1.数据结构栈和队列更多的是作为程序员的工具来运用。他们主要作为构思算法的辅助工具,而不是完全的数据存储工具。这些数据结构的生命周期比那些数据库类型的结构要短暂的多。在程序执行期间他们才被创建,通常用他们去执行某项特殊的任务,当任务完成之后,他们就被销毁。
2.栈的主要机制可以通过数组来实现,也可以通过链表来实现。优先级队列的内部实现可以通过数组或堆来实现。
3.实际计算机系统中,大部分微处理器运用栈的体系结构。栈在程序的运行中有举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧。堆栈帧一般包含下面几个信息:①函数的返回地址和参数 ②临时变量:包含函数的非静态局部变量以及编译器自动生成的其他临时变量。当调用一个方法时,把他的返回地址和参数压入栈,当方法结束返回时,那些数据出栈。栈操作就嵌入在微处理器中。
4.栈通常很小,是临时的数据结构。
为了更好理解数据结构栈,我们根据栈的属性,编写自己的栈,并且根据栈先入后出的原则实现了两个最基本的例子:字符串反转和分隔符匹配问题。
根据性质编写自己的栈:
/**
* 创建一个自己的栈
* 1.先入后出的原则
* 2.访问权限 在特定时刻只有一个数据项可以读取或者被删除 只能操作位于栈顶的元素
* 组织数据的方式 入栈 出栈 栈空 栈满
*/
public class YezhuStack {
//类型为Object的数组
private final Object[] oStack;
//数组的长度
private int size;
//栈顶标志
private int top = -1;
public YezhuStack(int max) {
this.size = max;
oStack = new Object[max];
}
/**
* 入栈
* 先判断是不是栈满的
*
* @param object
*/
public void push(Object object) {
if (!isFull()) {
oStack[++top] = object;
} else {
throw new IndexOutOfBoundsException("the stack is full");
}
}
/**
* 栈满否
*
* @return
*/
private boolean isFull() {
return top == size - 1;
}
/**
* 出栈
* 出栈先判断栈是不是为空
* 访问权限:在特定时刻只有一个数据项被允许读取或删除
*
* @return
*/
public Object pop() {
if (!isEmpty()) {
return oStack[top--];
} else {
throw new IndexOutOfBoundsException("the stack is empty");
}
}
/**
* 栈是不是空
*
* @return
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 查看栈顶的元素
*/
public Object peek() {
if (!isEmpty()) {
return oStack[top];
} else {
throw new IndexOutOfBoundsException("the stack is empty");
}
}
}
注意点:初始值top=-1,是根据代码做的修改。push时oStack[++top],++top =>先+1后赋值;pop时,oStack[top--],top-- => 先赋值后-1;还有判断栈是否为空,条件是top==-1而不是size=0,因为一开始初始化的时候根据要操作的数据的长度创建的栈,而在压栈和弹栈期间,其size并没有变化。
例子1:字符串反转;字符串反转就是输入一字符串,把字符串中的一个个元素从头压入栈,然后弹出栈时就是倒序。
/**
* 测试字符串反转
*/
public class ReverseStrTest {
private static YezhuStack yezhuStack;
public static void main(String[] args) throws IOException {
System.out.println("please import str:");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(inputStreamReader);
String str = br.readLine();
System.out.println("print reversed str:" + reverseStr(str));
}
/**
* 字符串反转后
*
* @param str
*/
private static String reverseStr(String str) {
String out = "";
if (str != null) {
yezhuStack = new YezhuStack(str.length());
}
//1.字符串放入栈中
for (int i = 0; i < str.length(); i++) {
yezhuStack.push(str.charAt(i));
}
//2.栈直接输出即可
for (int i = 0; i < str.length(); i++) {
out = out + yezhuStack.pop();
}
return out;
}
}
运行效果:
例子2:分隔符匹配;分隔符匹配程序从字符串中不断的读取字符,每次读取一个字符。若发现他是左分隔符,将他压入栈。当从输入中读取一个右分隔符,弹出栈顶的左分隔符,并且查看是否和右分隔符相匹配。比如eclipes编译器,当你少一个“}”时,会报错,其原理就是如此。
/**
* 分隔符匹配问题
* 用栈结构模拟的分隔符匹配;
* 编译器报错的 比如当少{[( )]} 左分隔符 压入栈,遇到右分隔符时 与栈顶元素进行匹配, 不匹配则编译器报错
*/
public class SeparatorMatchTest {
private static YezhuStack yezhuStack;
public static void main(String[] args) throws IOException {
System.out.println("please import str:");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(inputStreamReader);
String str = br.readLine();
checkSeparator(str);
}
/**
* 检查输入的字符串
*
* @param str
*/
private static void checkSeparator(String str) {
if (str != null) {
yezhuStack = new YezhuStack(str.length());
}
//1.整个字符串中遇到左匹配符号就入栈 因此循环入栈的时候要匹配条件
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
switch (ch) {
case '{':
case '(':
case '[':
//以上是左匹配符 遇到左匹配符入栈
yezhuStack.push(ch);
break;
case '}':
case ')':
case ']':
//遇到右匹配符然后出栈 做对比
if (!yezhuStack.isEmpty()) {
char chRight = (char) yezhuStack.pop();
if ((ch == '}' && chRight != '{')
|| (ch == '(' && chRight != ')')
|| (ch == ']' && chRight != '[')) {
//ch是循环拿字符串中匹配符 chRight是栈中的左匹配符
System.out.println("Error: " + ch + " at " + i);
}
}
break;
default:
break;
}
}
//最后做一次判断 正常来说要是左右分隔符能全部匹配成功的话 此时栈中已经空了 如果不为空 说明右分隔符少了
if (!yezhuStack.isEmpty()) {
System.out.println("输入含有左右分隔符的字符串缺少右分隔符");
}
}
}
运行效果:
以上就是两个基本使用栈的例子,如果有问题可以私信我。
补充在java基础讲过的栈和堆内存的概念:
1.栈内存用来存储局部变量和方法调用,堆内存用来存储java中new出来的对象。
2.栈内存属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见;而堆内存中的对象对所有线程可见,可以被所有线程访问。