栈
可以先来看看栈的应用,上图是程序调用的系统栈,箭头方向指明了程序运行顺序,我们运行到A2时把它压入栈,接着跳到B,执行到B2时再把它压入栈,再执行C,执行完C,我们可以看栈顶,是B2,我们跳回B2继续执行,B执行完后再看栈顶,是A2,跳回A2继续执行,A执行完后,栈空,程序结束,从这可以看出栈在程序调用问题上给出了很好的解决方法。
栈的基本实现
我们要如何实现一个栈呢,其实很简单,只要实现以下几个方法
栈有推入,拿出操作,peek操作是查询并获取栈顶元素,还有获取栈内元素个数和判断是否为空,所以我们可以利用之前自己封装的数组,实现这个栈接口,
public class ArrayStack <E> implements Stack<E> {
Array<E> array;
public ArrayStack(int capacity){
array=new Array<>(capacity);
}
public ArrayStack(){
array=new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
@Override
public void push(E e){//栈是先进后出,所以要往后添加元素
array.addLast(e);
}
@Override
public E pop(){
return array.removeLast();
}
@Override
public E peek(){
return array.getLast();
}
public int getCapicity(){
return array.getCapacity();
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("Stack:");
res.append("[");
for(int i=0;i<array.getSize();i++){
res.append(array.get(i));
if (i!=array.getSize()-1){
res.append(",");
}
}
res.append("] top");
return res.toString();
}
}
测试用例:
ArrayStack<Integer> arr=new ArrayStack<>();
for(int i=0;i<10;i++){
arr.push(i);
System.out.println(arr);
}
栈的另一个应用–括号匹配
这也是LeetCode第20题,
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
这里可以利用栈来实现,遍历字符串,遇到三种左括号则压入栈,否则,判断栈是否为空,为空则返回false;接着再判断栈顶元素是否为左括号对应的右括号,最后返回栈是否为空,不为空表明字符串有效
public boolean isVaild(String s) {
Stack<Character> stack = new Stack<>();
for (int i=0;i<s.length();i++){
char c=s.charAt(i);
if(c == '(' ||c =='['||c=='{'){
stack.push(c);
}
else{
if (stack.isEmpty()){
return false;
}
char topchar=stack.peek();
if (c == ')'&&topchar=='('||c == ']'&&topchar=='['||c=='}'&&topchar=='{'){
stack.pop();
}
else return false;
}
}
return stack.isEmpty();
}
数组队列
队列与栈比较相似,但队列是先进先出的结构,我们可以同样实现Queue接口中的五个方法,以此实现一个基本的数组队列,
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
public class ArrayQueue<E> implements Queue<E>{
private Array<E> array;
public ArrayQueue(int capacity){
array=new Array<>(capacity);
}
public ArrayQueue(){
array=new Array<>();
}
@Override
public int getSize(){
return array.getSize();
}
@Override
public boolean isEmpty(){
return array.isEmpty();
}
@Override
public void enqueue(E e){
array.addLast(e);
}
@Override
public E dequeue(){
return array.removeFirst();
}
@Override
public E getFront(){
return array.getFirst();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("Queue:");
res.append("Front [");
for(int i=0;i<array.getSize();i++){
res.append(array.get(i));
if (i!=array.getSize()-1){
res.append(",");
}
}
res.append("] Tail");
return res.toString();
}
}
循环队列
循环队列在方法上的实现与数组队列不同,所以需要重新自己写,需要定义两个头front和尾tail,其中tail指的是最后一个元素后面一个位置
循环队列队满和队空的要求分别为
front =tail,表示队空
(tail+1)%data.length=front, 表示队满,在这里有意识的浪费一个capacity,tail所指位置不放元素;
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front,tail;
private int size;
public LoopQueue(int capacity){
data=(E[])new Object[capacity+1];
front=0;
tail=0;
size=0;
}
public LoopQueue(){
this(10);
}
@Override
public int getSize(){
return size;
}
public int getCapacity(){//获取容量大小为队列长度-1;因为有一个位置为空
return data.length-1;
}
@Override
public boolean isEmpty(){
return front==tail;
}
入队操作,这里的扩容与动态数组相似
@Override
public void enqueue(E e){
if ((tail+1)%data.length==front){//判断是否队满,若满,则需扩容
resize(2*getCapacity());
}
data[tail]=e;
tail=(tail+1)%data.length;
size++;
}
扩容操作,将原数组内容复制到新数组中,不过要注意复制过程,新数组的0号位对应原数组的front位置,还要采用循环方式
public void resize(int newCapacity){
E [] newData=(E [])new Object[newCapacity+1];
for (int i=0;i<size;i++){
newData[i]=data[(i+front)%data.length];
}
data=newData;
front=0;
tail=size;
}
出队,这里要先判断是否为空,同样这里出队只需将front的值变为null,front往后移一位,如果队列元素过少,需减小容量,提高性能这一步的时间复杂度为O(1),而数组队列在这一步时间复杂度为O(n)
@Override
public E dequeue(){
if (isEmpty()){
throw new IllegalArgumentException("dequeue fail");
}
E ret=data[front];
data[front]=null;
front=(front+1)%data.length;
size--;
if (size==getCapacity()/4){
resize(getCapacity()/2);
}
return ret;
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append(String.format("Queue:size=%d,capacity=%d\n",size,getCapacity()));
res.append("front [");
for (int i=front;i!=tail;i=(i+1)%data.length){
res.append(data[i]);
if ((i+1)%data.length!=tail){
res.append(",");
}
}
res.append("] tail");
return res.toString();
}