在Java中的栈是继承Vector来实现的,Vector与ArrayList类似,不过是线程安全的。
可以继承ArrayList或者LinkedList来实现栈,但是这样会多出很多不必要的方法,栈数据结构只对外提高如下接口
push:往栈顶部添加元素pop:弹出栈顶部的元素peep:查看栈顶部的元素size:查看栈中元素的数量isEmpty:查看栈是否为空clear:清空栈可以让栈has a ArrayList来实现栈数据结构。
public class Stack<E> { private ArrayList<E> list = new ArrayList<>(); /** * 往栈里存放元素,也就是往数组的最后一个位置添加元素 * @param element */ public void push(E element){ list.add(element); } /** * 弹出栈顶的元素,也就是删除数组末尾的元素 * @return */ public E pop(){ return list.remove(list.size() - 1); } public int size(){ return list.size(); } public E peep(){ return list.get(list.size() - 1); } public boolean isEmpty(){ return list.isEmpty(); } public void clear(){ list.clear(); } }思路:
需要准备两个栈,一个用于入队,一个用于出队。这里成为入队栈和出队栈。
不管任何元素入队时加入入队栈。
当出队时,将入队栈中的所有元素全部弹出,依次放入出队栈。然后弹出出队栈栈顶的元素,也就是入队栈栈底部的元素,也就是第一个入队的元素。
因为队列遵循的是先进先出原则,栈遵循的是先进后出原则,只需要将入队栈中的元素弹出到出队栈中,这个顺序就刚好与队列一样了。
这里要注意:当出队栈为空时才能让入队栈中的元素进入出队栈。
public class _232_用栈实现队列 { /** * 用于入队的栈 */ private Stack<Integer> inStack; /** * 用于出队的栈 */ private Stack<Integer> outStack; /** Initialize your data structure here. */ public _232_用栈实现队列() { inStack = new Stack<>(); outStack = new Stack<>(); } /** Push element x to the back of queue. */ public void push(int x) { inStack.push(x); } /** Removes the element from in front of queue and returns that element. */ public int pop() { checkOutStack(); return outStack.pop(); } /** Get the front element. */ public int peek() { checkOutStack(); return outStack.peek(); } /** * 当pop或者peek时,需要判断出队的栈是否为空,如果为空,则需要将入队栈中的所有元素放入出队栈 */ private void checkOutStack(){ if (outStack.isEmpty()){ while(!inStack.isEmpty()){ outStack.push(inStack.pop()); } } } /** Returns whether the queue is empty. */ public boolean empty() { return inStack.isEmpty() && outStack.isEmpty(); } }循环队列是用数组实现,并且经过了一定优化之后的队列
优化了哪些:
在数组中,如果向首位置添加或删除元素,会让所有的元素都向后或者向前移动一位,效率低。为了解决这个问题,可以添加一个指针front指向数组的头元素,front永远指向数组第一个元素,当头元素被删除后,只需要让指针指向第二个元素即可。当数组容量不足时,如果front指针的前面位置还有元素,就将即将添加的元素添加到前面空余的位置即可,这样可以很好地利用内存空间,并且提高删除添加的效率。如图所示:
当删除a之后,让front指向b
添加元素
当后面没有容量时,下一个数据将添加到第一个位置。
方法与队列一样,不过这里获取队头元素时是获取front指向的元素。
最核心的问题是索引问题:
入队时,往队尾添加元素,如果数组后面已经没有位置了,此时如果front前面有位置,则应该加入到front前面空缺的位置
如何得出该位置的索引呢?
这里就要通过式子来计算出该往什么位置添加索引。
private int index(int index){ index += front; return index >= elements.length ? index-elements.length : index; } 给该方法传入一个索引,比如传入size,就是往数组的末尾添加元素,该队列中有四个元素,size为4。实际上数组的长度为6,front为2。此时如果往队列尾部添加元素,应该用front + size对length取模,这样可以得出真实添加元素的位置。在这里对取模进行了优化(取模效率较低)。不过前提条件是取模的数不能大与被取模数的2倍。这是因为当小于2倍时,取模实际上就是减去这个数。