基础数据结构-栈
基本概念
栈:后入先出(LIFO),特殊的线性表
栈如何实现
其实它是一个仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对的另一端是栈底。向一个栈插入新元素又称为进栈、入栈、压栈。它是把新元素放到栈顶元素上边使其成为新的栈顶元素:从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈其实是一个特殊的链表或者数组
既然栈也是一个线性表,那么我们肯定会想到数组和链表,而且栈还有很多限制,为什么还要使用栈,而不直接用数组和链表。
数组和链表暴露太多的接口,实现上更灵活,有些技术理解不到位的人员可能出错。所以在某些特定场合下最好选用栈这个数据结构。
栈的分类:
1.基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,通常以数组头到数组尾为栈顶的生长方向。
2.基于单链表的栈——以链表为底层的数据结构时:以链表头为栈顶,便于插入和删除,压栈产生的新节点将一直出现在链表的头部。
两种类型的最大区别就是扩容,链表天然支持动态扩容,使用栈多使用数组,防止栈溢出
栈的基本操作:
1.入栈(push)
2.出栈(pop)
代码实现
package Stack;
public interface Mystack<Item> {
//入栈
Mystack<Item> push(Item item);
Item pop ();//出栈
int size();//大小
boolean isEmpty();
}
package Stack;
public class ArrayStack<Item> implements Mystack<Item>{
//<>指的是范型就是指的广泛的、普通的类型。在java中是指把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型
private Item []a=(Item[]) new Object[1]; //最好就是开始就设置大小
private int n=0; //大小初始的元素个数
public ArrayStack(int cap){
a=(Item[]) new Object [cap];
}
public Mystack<Item> push(Item item) {//入栈时间复杂度O(1)
JudgeSize();
a[n++]=item;
return null;
}
//判断
private void JudgeSize(){
if (n>=a.length){ //元素个数超出数组的个数
resize(2*a.length); //
}else if (n>0 && n<a.length/2){ //小于二分之一就缩小数组释放空间,避免内存占用
resize(a.length/2);
}
}
//扩容
private void resize(int size){ //时间复杂度O(n)
Item []temp = (Item[]) new Object[size];
for (int i=0;i<n;i++){
temp [i]=a[i];
}
a=temp;
}
public Item pop() {//出栈 //时间复杂度O(1)
if(isEmpty()){ //表示已经出栈完了
return null;
}
Item item = a[--n];//注意不是n--; --n是先减了再用;n--是先用了再减
a[n]=null; //释放掉原来存储的元素
return item;
}
public int size() { //返回n就是大小
return n;
}
public boolean isEmpty() { //判断n为0就是空栈
return 0==n;
}
}
例题
数学表达式求值 用栈实现一个简单的四则运算:3+11*2+8-15/5
思路:用两个栈来实现:一个放数字,一个放符号
我们从头开始遍历这个算术表达式:
1.遇到的是数字我们直接入栈到数字栈里
2.遇到的是符号就把符号栈的栈顶拿出来做比较,如果说他比栈顶符号的优先级高就直接入栈,如果比符号栈栈顶的符号优先级低或者相同,就从符号栈中取栈顶符号进行计算(从数字栈中取栈顶的2个数),计算完的结果还要再放入数字栈中
面试经典:
1.如何设计一个括号匹配功能?比如给你一串括号让你判断是否符合我们的括号原则,如下所示: [(){()}{}] 符合 [][]{(}) 不符合
package Stack;
import java.util.Scanner;
public class BracketsStack { //整体时间复杂度O(n)
public static boolean isOK(String s){ //s是待匹配的括号串,[{}]用字符来表示
Mystack<Character> brackets = new ArrayStack<Character>(20);
char c[]=s.toCharArray();
Character top;
for (char x:c){ //时间复杂度O(n)
switch (x) {
case '{':
case '(':
case '[':
brackets.push(x);//时间复杂度O(1)
break;
case'}':
top = brackets.pop();
if(top==null)return false;
if (top=='{'){
break;
}else{
return false;
}
case']':
top = brackets.pop();//时间复杂度O(1)
if(top==null)return false;
if (top=='['){
break;
}else{
return false;
}
case')':
top = brackets.pop();
if(top==null)return false;
if (top=='('){
break;
}else{
return false;
}
default:
break;
}
}
return brackets.isEmpty();
}
public static void main(String[] args) {
Scanner scanner =new Scanner(System.in);
while (scanner.hasNext()){
String s= scanner.next();
System.out.println("s匹配的结果是:"+isOK(s));
}
}
}
2.如何设计一个浏览器的前进和后退功能?
用两个栈,一个前进栈一个后退栈
点击前进把后退栈出栈进入到前进栈,反之点后退就前进栈出栈进入到后退栈。点击新页面将后退栈清空
如图