Java数据结构和算法(一)--栈

栈:

  英文名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 boolean isValid(String s) {
MyStack<Character> stack = new MyStack<>();
char[] chars = s.toCharArray();
for (char aChar : chars) {
switch (aChar) {
case '{':
stack.push('}');
continue;
case '<':
stack.push('>');
continue;
case '(':
stack.push(')');
continue;
default:
if (stack.isEmpty() || stack.pop() != aChar) {
return false;
}

}
}
return stack.isEmpty();
}

总结:

stack是概念上的工具,我们已经实现了两个功能,能够实现什么功能看个人实现,甚至Ctrl+Z撤销的实现就是用stack实现的

stack入栈和出栈的时间复杂度都是O(1),栈的操作时间和栈中数据的个数没有关系,不需要移动和比较,只不过我们这里实现了扩容,如果

发生扩容,会稍微影响一点

 

内容参考:

<Java数据结构和算法>

Java数据结构和算法(四)——栈

原文地址:https://www.cnblogs.com/huigelaile/p/11063990.html