栈:
英文名stack,特点是只允许访问最后插入的那个元素,也就是LIFO(后进先出)
jdk中的stack源码:
public
class Stack<E> extends Vector<E> { //继承Vector,Vector和ArrayList几乎相同,都是通过数组保存数据,只不过方法有Synchronized修饰
public Stack() {
}
public E push(E item) { //push,也就是add,把数据保存得add到数组的末尾
addElement(item);
return item;
}
public synchronized E pop() { //pop,也就是removeLast(),并且返回末尾值
E obj;
int len = size(); //得到数组长度
obj = peek(); //得到数组末尾值
removeElementAt(len - 1); //删除末尾元素
return obj;
}
public synchronized E peek() { //peek,返回末尾元素,只是查询,不会删除
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
public boolean empty() { //empty,是否为空
return size() == 0;
}
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
}
从上面源码看到:
JDK源码中的stack通过集成Vector实现,通过数组保存数据,一般使用push()、pop()、peek()来实现stack基本功能LIFO
stack有栈顶、栈底的概念,栈底是封闭的,数据只能从栈顶进出
自定义实现stack:
public class MyStack<E> {
private int maxDepth; //栈深度
private Object[] elementData; //保存数据的数组
private int top; //栈顶的位置,空栈为-1
public MyStack() {
this.maxDepth = 10;
this.elementData = new Object[10];
this.top = -1;
}
public MyStack(int initialCapacity) {
this.maxDepth = initialCapacity;
this.elementData = new Object[initialCapacity];
this.top = -1;
}
public E push(E e) {
ensureExplicitCapacity(top +1);
elementData[++top] = e;
return e;
}
public E pop(){
E e = peek();
elementData[top] = null;
this.top--;
return e;
}
public E peek() {
return (E)elementData[top];
}
private void ensureExplicitCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) { //扩容
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - Integer.MAX_VALUE > 0)
newCapacity = Integer.MAX_VALUE;
this.maxDepth = newCapacity;
elementData = Arrays.copyOf(elementData, maxDepth);
}
public int size() {
return top + 1;
}
public boolean isEmpty() {
return top == -1;
}
}
public static void main(String[] args) {
MyStack<Integer> stack = new MyStack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
int size = stack.size();
for (int i = 0; i < size; i++) {
System.out.print(stack.peek() + " ");
}
System.out.println("");
for (int i = 0; i < size; i++) {
System.out.print(stack.pop() + " ");
}
}
输出结果:
3 3 3
3 2 1
这是使用ArrayList的思路实现Stack:
1、通过Object[]保存数据
2、扩展支持泛型
3、初始容量设置为10,扩容之后为原来容量的1.5倍
4、实现了push()、pop()、peek()和grow()
5、stack不适合使用for、foreach、Iterator,所以这里没实现Iterable接口,因为peek()永远只是栈顶的值,或者用pop()
PS:for循环的size,如果for循环使用pop(),不要写成:
for (int i = 0; i < stack.size(); i++) {
System.out.print(stack.pop() + " ");
}
因为弹出元素,size会变化,无法弹出所有数据,可以使用测试代码中那样,或者while循环:
while (!stack.isEmpty()) {
System.out.println(stack.pop());;
}
到此为止一个stack的功能已经实现,从输出结果看使用时没问题的
实践1:实现输入字符串,逆序输出
public static void main(String[] args) {
MyStack<Character> stack = new MyStack<Character>();
char[] chars = getInput().toCharArray();
for (char c : chars) {
stack.push(c);
}
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");;
}
}
public static String getInput() {
String s = null;
try {
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(reader);
s = bufferedReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return s;
}
输出结果:
实践2:分隔符匹配
栈通常用于解析xml标签或者HTML标签,例如'{'要有个'}'进行匹配,通过把读取字符,发现左分隔符push,发现右分隔符pop,然后对比是否
匹配,不匹配就报错,如果栈中没有左分隔符和右分隔符匹配,或者有左分隔符一直没有被匹配,这些情况,也会报错。
例如:a{12(u<d>1)f3}
代码实现:
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(3);
String str = "a{12(u<d>1)f3}";
char[] cha = str.toCharArray();
for(char c : cha){
switch (c) {
case '{':
case '<':
case '(':
stack.push(c);
break;
case '}':
case '>':
case ')':
if(!stack.isEmpty()){
char ch = stack.pop().toString().toCharArray()[0];
if(c=='}' && ch != '{'
|| c==']' && ch != '['
|| c=='>' && ch != '<'
|| c==')' && ch != '('){
System.out.println("解析出现错误: "+ch+"-"+c);
}
}
break;
default:
break;
}
}
}
总结:
stack是概念上的工具,我们已经实现了两个功能,能够实现什么功能看个人实现,甚至Ctrl+Z撤销的实现就是用stack实现的
stack入栈和出栈的时间复杂度都是O(1),栈的操作时间和栈中数据的个数没有关系,不需要移动和比较,只不过我们这里实现了扩容,如果
发生扩容,会稍微影响一点
内容参考:
<Java数据结构和算法>