3.线性表之栈(手工实现)

栈(Stack)是限定仅在表尾进行插入和删除的线性表

我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为是后进先出(Last In First Out)的线性表,简称LIFO结构。


理解栈的时候要注意:
首先它是一个线性表,即栈元素具有线性关系(前驱后继)关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾指栈顶,而不是栈底。
它的特殊之处在于限制了这个线性表的插入和删除位置,它时钟只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底
栈的插入操作,叫做进栈,也称压栈、入栈。 栈的删除操作,叫做出栈,也有的叫做弹栈,如下图所示:

由于栈本身是一个线性表,所以对于存储方式来说有顺序存储和链式存储两种类型。

顺序栈

类结构图

静态顺序栈封装类

package com.company.datastructure;

/**
 * 静态栈的顺序存储
 * @param <E> 泛型
 */
public class MyStack<E> {
    private Object[] elements;  //使用数组存储栈
    private int top;  //栈顶标识
    private int size;  //当前栈的大小
    private final int DEFAULT_CAPACITY = 30;  //默认初始化长度为30
    /**
     * 初始化一个空栈
     */
    public MyStack(){
        elements = new Object[DEFAULT_CAPACITY];
        top = -1;  //top=-1时表示空栈
    }

    /**
     * 入栈
     * @param element 元素值
     */
    public void push(E element) {
        if (size<DEFAULT_CAPACITY) {
            elements[top + 1] = element;
            top++;
        }else{
            throw new OutOfMemoryError("栈内存不足");
        }
    }

    /**
     * 出栈
     */
    public void pop(){
        if(top==-1){
            throw new NullPointerException("栈为空栈");
        }else{
            top--;
        }
    }

    /**
     * 获取栈顶元素
     * @return 栈顶元素值
     */
    public E peek(){
        if(top==-1){
            throw new NullPointerException("栈为空栈");
        }else{
            return (E)elements[top];
        }
    }

    /**
     * 栈实际大小
     * @return
     */
    public int size(){
        return top+1;
    }

    /**
     * 清空栈
     */
    public void clear(){
        top = -1;
    }
}

栈测试类

public class TestMyStack {
    public static void main(String[] args) {
        MyStack<String> ms = new MyStack<>();
        ms.push("chen0");
        ms.push("chen1");
        ms.push("chen2");
        System.out.println(ms.size());
        int size = ms.size();
        for (int i = 0; i < size; i++) {
            System.out.println(ms.peek());
            ms.pop();
        }
    }
}

对于两个相同类型的栈,可以考虑两个栈共用同一空间,如下图所示:

关键思路是:两个栈在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以想象,只要他们两个不碰面,两个栈就可以一直使用。那么什么时候栈满呢?

两个栈顶见面时即:top1+1=top2时,栈为满栈


链栈

由于栈只有栈顶能够进行插入和删除操作,而且单链表有头指针,所以栈顶在单链表头部,并且不需要单链表的头结点。

如下图1所示,

对于链栈来说,由于是在单链表头部进行插入和删除,所以也就不存在栈满的问题了。

入栈:如图2所示,首先将新结点next指针指向当前top结点,然后将新结点赋值给top结点即可

出栈:如图3所示,首先将栈顶元素下移,然后将原栈顶结点指针设置为null;

类结构图

链栈封装类

package com.company.datastructure;

/**
 * 静态栈的链式存储
 * @param <E> 泛型
 */
public class MyLinkStack<E> {
    private class Node<E>{
        public E element;
        private Node<E> next;
        public Node(E element){
            this.element = element;
        }
    }
    private Node<E> top;
    private int size;
    public MyLinkStack(){
        top = null;
    }

    /**
     * 入栈
     * @param element 栈元素值
     */
    public void push(E element){
        Node<E> newNode = new Node<>(element);
        newNode.next = top;
        top = newNode;
        size++;
    }

    /**
     * 出栈
     * @return 出栈元素
     */
    public E pop() {
        if(top==null){
            throw new NullPointerException("栈为空栈");
        }else{
            Node<E> temp = top;
            top = top.next;
            temp.next = null;
            size--;
            return temp.element;
        }
    }

    /**
     * 取得栈底元素
     * @return 栈顶元素值
     */
    public E peek(){
        if(top==null){
            throw new NullPointerException("栈为空栈");
        }else{
            return top.element;
        }
    }

    /**
     * 获得链栈长度
     * @return 链栈长度
     */
    public int getSize(){
        return size;
    }

    /**
     * 判断空栈
     * @return 空返回true否则返回false
     */
    public boolean isEmpty(){
        return size==0?true:false;
    }
}

链栈测试类

package com.company.datastructure;

public class TestMyLinkStack {
    public static void main(String[] args) {
        MyLinkStack<String> mls = new MyLinkStack<>();
        mls.push("chen0");
        mls.push("chen1");
        mls.push("chen2");
        System.out.println(mls.getSize());
        int size = mls.getSize();
        for (int i = 0; i < size; i++) {
            System.out.println(mls.peek());
            mls.pop();
        }
    }
}

对比一下顺序栈和链栈可以看到,在时间性能上,顺序栈和链栈的时间复杂度都是O(1),在空间性能上 ,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的情况,但优势是存取和定位都十分方便。而链栈需要每个结点都有一个指针域,在一定程度上增加了内存开销,但是链栈的长度没有限制。所以如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值