Java数据结构与算法解析(二)——栈

来源:互联网 发布:数据字典怎么用 编辑:程序博客网 时间:2024/06/05 18:16

关联文章:
Java数据结构与算法解析(一)——表

栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶。对栈的基本操作有push(进栈)和pop(出栈),对空栈进行push和pop,一般被认为栈ADT的一个错误。当push时空间用尽是一个实现限制,而不是ADT错误。栈有时又叫做LIFO(后进先出)表。

基本概念

允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表
这里写图片描述

栈的顺序存储结构
栈的数序结构可以使用数组来实现,栈底是:下标为0的一端
这里写图片描述

栈的链式存储结构
这里写图片描述

栈的实现
栈的实现,一般分为两种形式,链式结构和数组。两者均简化了ArrayList和LinkedList中的逻辑。

栈的数组实现

抽象出栈的必备接口

public interface Stack<T> {    boolean isEmpty();    void push(T data);    T pop();    int size();}

栈的数组实现形式

public class ArrayStack<T> implements Stack<T>, Iterable {    private T[] mArray;    private int mStackSize;    private static final int DEFAULT_CAPACITY = 10;    public ArrayStack(int capacity) {        if (capacity < DEFAULT_CAPACITY) {            ensureCapacity(DEFAULT_CAPACITY);        } else {            ensureCapacity(capacity);        }    }    public boolean isEmpty() {        return mStackSize == 0;    }    public int size() {        return mStackSize;    }    public void push(T t) {        if (mStackSize == mArray.length) {            ensureCapacity(mStackSize * 2 + 1);        }        mArray[mStackSize++] = t;    }    public T pop() {        if (isEmpty()) {            throw new EmptyStackException();        }        T t = mArray[--mStackSize];        mArray[mStackSize] = null;        //调整数组的大小,防止不必要的内存开销        if (mStackSize > 0 && mStackSize < mArray.length / 4) {            ensureCapacity(mArray.length / 2);        }        return t;    }    private void ensureCapacity(int newCapacity) {        T[] newArray = (T[]) new Object[newCapacity];        for (int i = 0; i < mArray.length; i++) {            newArray[i] = mArray[i];        }        mArray = newArray;    }    @Override    public Iterator iterator() {        return null;    }    private class ArrayStackIterator implements Iterator<T> {        @Override        public boolean hasNext() {            return mStackSize > 0;        }        @Override        public T next() {            return                    mArray[--mStackSize];        }    }}

对象游离
Java的垃圾收集策略是回收所有无法被访问对象的内存,如果我们pop()弹出对象后,不调用如下代码,就会造成游离,因为数组中仍然持有这个对象的引用,保存一个不需要的对象的引用,叫做游离。

mArray[mStackSize] = null;

动态调整数组大小
pop()中,删除栈顶元素后,如果栈的大小小于数组的1/4,就将数组的大小减半,这样栈永远不会溢出,使用率也不会小于1/4。

栈的链表实现

采用链式存储结构的栈,由于我们操作的是栈顶一端,因此这里采用单链表(不带头结点)作为基础,直接实现栈的添加,获取,删除等主要操作即可。

链栈的出入栈操作
链栈的入栈操作:

这里写图片描述

这里写图片描述

链栈的出栈操作:
这里写图片描述

这里写图片描述

“`
public class LinkedStack implements Stack, Iterable {

private int mSize;private Node<T> endNote;private int modCount;public LinkedStack() {    init();}private void init() {    endNote = new Node<T>(null, null);    modCount++;}@Overridepublic boolean isEmpty() {    return mSize == 0;}@Overridepublic void push(T data) {    Node<T> newNote = new Node<T>(data, null);    endNote.mNext = newNote;    mSize++;    modCount++;}@Overridepublic T pop() {    if (endNote.mNext == null) {        throw new NoSuchElementException();    }    T t = endNote.mNext.mData;    endNote.mNext = endNote.mNext.mNext;    mSize--;    modCount++;    return t;}@Overridepublic int size() {    return mSize;}@Overridepublic Iterator iterator() {    return new LinkedStackIterator();}private static class Node<T> {    private Node<T> mNext;    private T mData;    public Node(T data, Node<T> next) {        mData = data;        mNext = next;    }}private class LinkedStackIterator implements Iterator<T> {    private Node<T> currentNode = endNote.mNext;    private int expectedModCount = modCount;    @Override    public boolean hasNext() {        return currentNode != null;    }    @Override    public T next() {        if (modCount != expectedModCount) {            throw new ConcurrentModificationException();        }        if (!hasNext()) {            throw new NoSuchElementException();        }        T t = currentNode.mData;        currentNode = currentNode.mNext;        return t;    }}

}

时间复杂度对比

顺序栈复杂度

操作 时间复杂度 空间复杂度(用于N次push) O(n) push() O(1) pop() O(1) isEmpty() O(1)

链式栈复杂度

操作 时间复杂度 空间复杂度(用于N次push) O(n) push() O(1) pop() O(1) isEmpty() O(1)

可知栈的主要操作都可以在常数时间内完成,这主要是因为栈只对一端进行操作,而且操作的只是栈顶元素。

栈的经典实用

逆波兰表达式法
标准四则运算表达式—中缀表达式
这里写图片描述

我们在小学学习的四则运算表达式就是中缀表达式 ,但是计算机是不认识中缀表达式的,它采用的是后缀表达式

计算机采用—后缀表达式
这里写图片描述

计算规则:

这里写图片描述

它的规则是,从头开始遍历,遇到数字进行压栈,遇到运算符号,将栈顶开始的两个元素进行运算符操作后,弹栈,结果进栈,931遇到“—”时,进行3-1=2,将2进栈,然后3进栈,遇到“*”,3*2=6进栈,遇到“+”,进行9+6=15进栈,然后10和2进栈,遇到“/”,进行10/2后结果进栈,最后是15+5=20,就完成了后缀表达式的计算操作。

中缀表达式转后缀表达式
这里写图片描述
数字输出,运算符进栈,括号匹配出栈,是当栈顶是运算符时,又压进来一个运算符,如果压进来的运算符优先级比栈顶的高,则这个压进来的运算符出栈。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

如果我们见到任何其他的符号(+,*,(),那么我们从栈中弹出栈元素直到发现优先级更低的元素为止。有一个例外:除非是在处理一个)的时候,否则我们决不从栈中移走(。对于这种操作,+的优先级最低,而(的优先级最高。当从栈弹出元素的工作完成后,我们再将操作符压入栈中。

原创粉丝点击