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

关联文章:
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++;
}


@Override
public boolean isEmpty() {
    return mSize == 0;
}

@Override
public void push(T data) {
    Node<T> newNote = new Node<T>(data, null);
    endNote.mNext = newNote;
    mSize++;
    modCount++;
}

@Override
public T pop() {
    if (endNote.mNext == null) {
        throw new NoSuchElementException();
    }
    T t = endNote.mNext.mData;
    endNote.mNext = endNote.mNext.mNext;
    mSize--;
    modCount++;
    return t;
}

@Override
public int size() {
    return mSize;
}

@Override
public 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,就完成了后缀表达式的计算操作。

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

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值