栈:是一个线性表,特殊的链表或者数组,遵循先进后出原则(FILO).
应用场景:浏览器的“前进”“后退”导航功能
栈和数组/链表的区别:数组和链表暴露很多接口,实现上更灵活;栈限制了操作的可能性,在某些场景下,是更适合的数据结构。(栈的基本方法:push, pop, size(), isEmpty())
栈的分类:
1. 基于数组的栈:以数组为底层数据结构,通常以数组头为栈底,数组头到尾为栈顶的生长方向
2. 基于单链表的栈:以链表为底层的数据结构,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一只出现在链表的头部
区别:扩容,链表天然支持冬天扩容,但也容易出现栈溢出。
//接口类:
public interface MyStack<Item>{
MyStack<Item> push(Item item); //入栈Item为泛型
Item pop(); //出栈
int size();
boolean isEmpty();
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//实现一个栈:
public ArrayStack<Item> implements MyStack<Item>{
private Item[] itemArray = (<Item>) new Object[1]; //初始化栈的容量
private int n=0; //栈内元素个数
//构造方法
public ArrayStack(int cap){
this.itemArray= (Item[]) new Object[cap];
}
public MyStack<Item> push(Item item){
judgeSize(); //先判断是否需要扩容
itemArray[n++] = item; //在数组结尾处压入一个数据,并使n+1
return null;
}
public Item pop(){
if(isEmpty()){
return null;
}
Item item = itemArray[--n]; //先有push操作,再有pop,因为push时n已经是+1状态了,因此需要先-1才能读取到最新压入栈的item
itemArray[n] = null; //且同时将pop出去的数据设为null,释放空间
return item;
}
public int size(){
return this.n;
}
public boolean isEmpty(){
return n==0;
}
//判断栈是否需要扩容:当站内元素个数大于等于栈的容量,则进行扩容
private void judgeSize()
{
if(itemArray.length <= n){
resize(2* itemArray.length);
}else if( n>0 && n<=itemArray.length/2){
//扩容的优化:缩容,当栈内元素个数少于栈容积的1/2时,进行缩容
resize(itemArray.length/2);
}
}
//栈扩容
private void resize(int size){
//临时数组
Item[] temp=(Item[]) new Object[size];
for(int i=0;i<n;i++){
temp[i]=itemArray[i];
}
itemArray=temp;
}
}
Q1:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。
例如:
输入字符串:{}[]() -->true
{[]()} -->true
[()]{} -->true
{(})[] --> false
[[{{]} --> false
A: 思考过程:括号依次匹配
public class BracketStatck{
public static boolean isOk(String s){ //s表示待匹配的字符串
MyStack<Character> brackets= new ArrayStack<Character>(20); //利用之前实践过的栈类
char[] c= s.toCharArray();
Character top;
for(char x:c){
swtich(x){
//如果是左括号,则压栈存储
case '{':
case '(':
case '[':
brackets.push(x); //压栈
break;
//如果是右括号,则需进行匹配
//如果括号匹配成功,则继续匹配,且需要将已匹配的括号弹出,因此用栈的pop;若匹配不成功,后面的字符也无须匹配了,直接返回false
case '}':
top = brackets.pop(); //brackets可能是空的
if(top == null) return false;
if(top =='{'){ break;} // 基础类型数据没有equals方法,只能使用==
else return false;
case ']':
top = brackets.pop();
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));
}
}
}
时间复杂度:
栈的push和pop的实践复杂度都是 O(1)
字符串s的遍历比较时间复杂度是O(n)
整个算法的时间复杂度是O(n)
应用场景:
1. 代码函数中的调用,代码运行过程中,函数的运行机制,即是栈
2. 数学表达式求值:3+11*2+8-15/5,用栈来实现这个算数表达式
需要用到2个栈,一个用来存储数字,一个用来存储运算符;
a)当前是数字,直接入栈到数字栈中;
b)当前是符号,就把符号栈的栈顶数据pop出来进行比较,如果当前符号的优先级高于栈顶符号,则入栈;如果当前符号的优先级低于或者小于栈顶符号,则pop出栈顶符号进行计算(从数字栈中取栈顶的2个数字,并将计算完的结果压栈到数字栈中)
3. 浏览器前进后退的实现机制: