栈的定义
栈是一种操作受限的(先进后出)线性结构,只允许从一端进出,这是栈的特点,一般数据进出的一端我们称为栈顶,另一端则为栈底,栈的基本操作包含进栈、出栈、判空、取栈顶元素等等。
如何实现一个栈
栈可以使用数组来实现,主要包含进栈和出栈两个基本方法,在Java中为我们提供的Stack类,就是最基本的栈结构。
public
class Stack<E> extends Vector<E> {
/**
* Creates an empty Stack.
*/
public Stack() {
}
/**
* Pushes an item onto the top of this stack. This has exactly
* the same effect as:
* <blockquote><pre>
* addElement(item)</pre></blockquote>
*
* @param item the item to be pushed onto this stack.
* @return the <code>item</code> argument.
* @see java.util.Vector#addElement
*/
public E push(E item) {
addElement(item);
return item;
}
/**
* Removes the object at the top of this stack and returns that
* object as the value of this function.
*
* @return The object at the top of this stack (the last item
* of the <tt>Vector</tt> object).
* @throws EmptyStackException if this stack is empty.
*/
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
/**
* Looks at the object at the top of this stack without removing it
* from the stack.
*
* @return the object at the top of this stack (the last item
* of the <tt>Vector</tt> object).
* @throws EmptyStackException if this stack is empty.
*/
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
/**
* Tests if this stack is empty.
*
* @return <code>true</code> if and only if this stack contains
* no items; <code>false</code> otherwise.
*/
public boolean empty() {
return size() == 0;
}
/**
* Returns the 1-based position where an object is on this stack.
* If the object <tt>o</tt> occurs as an item in this stack, this
* method returns the distance from the top of the stack of the
* occurrence nearest the top of the stack; the topmost item on the
* stack is considered to be at distance <tt>1</tt>. The <tt>equals</tt>
* method is used to compare <tt>o</tt> to the
* items in this stack.
*
* @param o the desired object.
* @return the 1-based position from the top of the stack where
* the object is located; the return value <code>-1</code>
* indicates that the object is not on the stack.
*/
public synchronized int search(Object o) {
int i = lastIndexOf(o);
if (i >= 0) {
return size() - i;
}
return -1;
}
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = 1224463164541339165L;
}
自己用数组实现一个栈
public class MyStack {
// 用来保存栈中数据
private int[] elementData;
// 栈中当前元素的数量
private int elementCount;
// 栈的大小
private int size;
public MyStack(int size) {
this.size = size;
this.elementCount = 0;
this.elementData = new int[size];
}
public void push(int val) throws Exception {
if (elementCount == size) {
throw new Exception("栈已经满了");
}
elementData[elementCount] = val;
elementCount++;
}
public int pop() throws Exception {
if (elementCount == 0) {
throw new Exception("栈是空的");
}
return elementData[--elementCount];
}
}
可以看出实现一个栈还是非常简单的,JDK提供的栈额外支持动态扩容,实际上也是数组的基本操作。
栈的时间复杂度
很明显在固定大小的栈中,出栈和入栈都是O(1)的时间复杂度,而对于支持动态扩容的栈来说,入栈可能会涉及到数组的扩容,所以也是O(n)的时间复杂度,但是大多数的操作都不会涉及到数组的扩容,所以从平均时间复杂度来看,也是趋近于O(1)的。
栈的常见题目
来自leetcode上的两题
1、最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
/**
* 使用两个栈,一个栈完成正常的进出操作,另一个栈的栈顶始终存放着当前栈中的最小元素
*/
public class MinStack {
Stack<Integer> stack = new Stack();
Stack<Integer> minStack = new Stack();
/**
* 如果minStack栈为空,或者栈顶的元素小于val,则重复添加一个栈顶的元素
* @param val
*/
public void push(int val) {
stack.push(val);
if (!minStack.isEmpty() && val > minStack.peek()) {
minStack.push(minStack.peek());
} else {
minStack.push(val);
}
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
2、有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
public class ValidParentheses {
static Map<Character, Character> map = new HashMap<Character, Character>() {
{
put(')', '(');
put(']', '[');
put('}', '{');
}};
public boolean isValid(String s) {
Stack<Character> stack = new Stack();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (map.containsKey(c)) {
if (stack.isEmpty() || stack.peek() != map.get(c)) {
return false;
} else {
stack.pop();
}
}else{
stack.push(c);
}
}
return stack.isEmpty();
}
}
3、用栈实现队列
一个栈是无法完成队列的,本题我们可以通过两个栈来实现,一个栈专门用来添加元素,另一个栈专门用来取出元素。
假设分别设添加元素的栈为:A,取元素的栈为:B。
每次数据都push到A栈中,当需要pop时,如果B栈为空,则一次性把用来A栈数据全部弹出并添加到B栈,否则直接从B栈弹出,这样即实现了队列的操作。
假设现在要添加了3个元素,分别为:1,2,3
现在需要弹出一个元素,首先判断B栈是否为空,此时B栈是为空的,那么则一次性把A栈数据全部弹出到B栈。
然后从B栈弹出一个元素,就是1(1是先进的,现在也先出了,符合队列结构)。
此时如果又需要添加一个元素4,则继续向A栈添加。
当再又元素要弹出时,因为B栈不为空,所以直接从B栈弹出。
import java.util.Stack;
public class Code_StackImplQueue<E> {
Stack<E> stack_a = new Stack<>();
Stack<E> stack_b = new Stack<>();
/**
* 每次push元素时都push到stack_a栈中
*
* @param e
*/
public void push(E e) {
stack_a.push(e);
}
public E pop() {
/**
* 如果stack_b为空,则需要把stack_a的元素全部弹出并push到stack_b中
*/
if (stack_b.empty()) {
while (!stack_a.empty()) {
stack_b.push(stack_a.pop());
}
}
//如果stack_b还为空,则表示stack_a也为空,则直接返回null
if (stack_b.empty()) {
return null;
}
//从stack_b取出元素
return stack_b.pop();
}
}