前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java数据结构与算法解析(二)——栈

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

作者头像
老马的编程之旅
发布2022-06-22 12:41:09
2980
发布2022-06-22 12:41:09
举报
文章被收录于专栏:深入理解Android

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

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

基本概念

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

这里写图片描述
这里写图片描述

栈的顺序存储结构 栈的数序结构可以使用数组来实现,栈底是:下标为0的一端

这里写图片描述
这里写图片描述

栈的链式存储结构

这里写图片描述
这里写图片描述

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

栈的数组实现

抽象出栈的必备接口

代码语言:javascript
复制
public interface Stack<T> {

    boolean isEmpty();

    void push(T data);

    T pop();

    int size();
}

栈的数组实现形式

代码语言:javascript
复制
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()弹出对象后,不调用如下代码,就会造成游离,因为数组中仍然持有这个对象的引用,保存一个不需要的对象的引用,叫做游离。

代码语言:javascript
复制
mArray[mStackSize] = null;

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

栈的链表实现

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

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

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

链栈的出栈操作:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

“` public class LinkedStack implements Stack, Iterable {

代码语言:javascript
复制
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,就完成了后缀表达式的计算操作。

中缀表达式转后缀表达式

这里写图片描述
这里写图片描述

数字输出,运算符进栈,括号匹配出栈,是当栈顶是运算符时,又压进来一个运算符,如果压进来的运算符优先级比栈顶的高,则这个压进来的运算符出栈。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本概念
  • 栈的数组实现
  • 栈的链表实现
  • 时间复杂度对比
  • 栈的经典实用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档