数据结构-栈

【知识框架】

栈介绍

        栈(Stack)是一个线性表,线性存储结构,插入和删除位置限制在线性表的尾部进行插入,只能插入在最后一个元素的后面,也只能删除最后一个元素,栈是插入和删除受到限制的线性表,通常将允许删除,插入操作的一端称为栈顶,另一端称为栈底限定只能在栈顶进行插入和删除操作。

        栈是一种先进后出(First In Last Out)的线性表,简称 FILO。

栈的相关概念

        假设某个栈S = { a1, a2,  … , an },如上图所示,则 a1 为栈底元素,an 为栈顶元素。由于只能在栈顶进行插入和删除操作,故进栈顺序为 a1, a2,  … , an,出栈顺序为 an,  … , a2, a1。

        假如,栈中存放了 4 个数据元素,进栈的顺序是 A 先进栈,然后 B 进,然后 C 进,最后 D 进栈;当需要调取 A 时,首先 D 出栈,然后 C 出栈,然后 B 出栈,最后 A 才能出栈被调用。

        栈顶(Top):线性表允许进行插入和删除的一端。

        栈底(Bottom):固定的,不允许进行插入和删除的另一端。

        空栈: 不含任何一个元素的栈称为空栈。

        压栈:栈的插入操作,叫做进栈,也称压栈、入栈,压栈通常命名为push。

                   进行入栈操作的时候,每次放入一个数据后,栈顶指针依次向上移动一位即可,如图所示:

        弹栈:栈的删除操作,也叫做出栈、退栈、弹出,弹栈通常命名为pop。

                   进行出栈操作时,取出栈顶元素后,栈顶指针依次向下移动一位,如下所示:

    

核心类库中的栈结构有 Stack 和 LinkedList。

  • Stack就是顺序栈,它是 Vector 的子类。
  • LinkedList 是链式栈。

体现栈结构的操作方法:

  • peek()方法:查看栈顶元素,不弹出
  • pop()方法:弹出栈
  • push(E e)方法:压入栈

时间复杂度:

  • 索引: O(n)
  • 搜索: O(n)
  • 插入: O(1)
  • 移除: O(1)

栈的常见分类

(1)基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,数组尾为栈顶,数组头到数组尾为栈顶的生长方向;

(2)基于单链表的栈——以链表为底层的数据结构时,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部。

栈的“上溢”和“下溢”

栈存储结构调取栈中数据元素时,要避免出现“上溢”和“下溢”的情况:

“上溢”:在栈已经存满数据元素的情况下,如果继续向栈内存入数据,栈存储就会出错。

“下溢”:在栈内为空的状态下,如果对栈继续进行取数据的操作,就会出错。

对于栈的两种表示方式来说:

  • 顺序栈两种情况都有可能发生;
  • 而链栈由于“随时需要,随时申请空间”的存储结构,不会出现“上溢”的情况。

提醒:

入栈:先判断栈是否满了,没满,top再加1,之后再入栈。

出栈:先判断栈是否空了,没空,再出栈,之后top再减1。

栈的顺序存储结构及实现

1、确定用数组的哪一端表示栈底;

2、定义一个变量top来指示栈顶元素在数组中的位置。

        top可以定义为指针类型也可以定义为整型。

        如果定义为整型类型,我们可以让top的初始值为-1,第一个元素进栈后,top的值再加1,变为0。从而top就指向了第一个元素的位置。

3、入栈操作

        总结:入栈操作,top先加1,然后在top所指向的位置存入元素。

4、出栈操作

        总结:出栈操作,先将top所指向的位置的元素出栈,top值再减1。

4、栈空

        top = -1。

5、栈满

6、栈的上溢和下溢

图中的an,...,az,a1,a[0]来存储。
需要定义一个top变量存储栈顶指针的位置,它存储的是最后处理的那个元素的下标。
栈顶
还需要定义一个MAXSIZE表示栈的最大容量。

上溢的判断

当栈为空的时候,top的值为-1。当往栈中压入一个元素时,top的值会加1。这样,a[0]就代表第一个进栈的元素,a[i-1]代表第i个进栈的元素,a[top]则表示栈顶元素。

当top = MAX SIZE -1时表示栈满。如果再有元素进栈时,则栈会溢出,这时称为“上溢”。

下溢的判断

反之,当top等于-1时,再将栈顶元素弹出,就要发生“下溢”。

因此在进行栈处理的时候,我们应该以要判断是否会发生“上溢”和“下溢”的情况,并做出相应处理。

存储结构

栈的基本介绍

顺序栈基本介绍

顺序栈(基于数组)
栈接口
public interface Stack{
/**
     * 入栈
     * @param object
     */
    public void push(Object object);

    /**
     * 出栈
     * @return
     */
    public Object pop();

    /**
     * 获取栈顶元素
     * @return
     */
    public Object getTop();

    /**
     * 判断栈是否为空
     * @return
     */
    public boolean isEmpty();
}
实现类构造方法
public class SequenceStack implements Stack {	
	Object[] stack; // 对象数组
    final int defaultSize = 10; // 默认最大长度
    int top; // 栈顶位置元素的下标
    int maxSize; //最大长度

    //定义无参构造函数初始化栈
    public SequenceStack() {
        init(defaultSize);
    }

    //带有指定长度的构造函数初始化栈
    public SequenceStack(int maxSize) {
        init(maxSize);
    }
	//初始化方法
    private void init(int maxSize) {
        this.maxSize = maxSize;
        top = 0;
        stack = new Object[maxSize];
    }
}
入栈
@Override
    public void push(Object object) {
        //判断栈是否已经存满
        if (top == maxSize) {
            System.out.println("栈空间不足,入栈失败");
            return;
        }
        stack[top] = object;
        top++;
    }
出栈
@Override
    public Object pop() {
        //判断栈是否为空
        if (isEmpty()) {
            System.out.println("栈为空");
            return;
        }
        top--;
        return stack[top];
    }
获取栈顶元
@Override
    public Object getTop() {
        //判断栈是否为空
        if (isEmpty()) {
            System.out.println("栈为空");
            return;
        }
        return stack[top - 1];
    }
判断栈是否为空
@Override
    public boolean isEmpty() {
        return top == 0;
    }
判断栈是否满
@Override
    public Boolean isFull() {
        return top == maxSize;
    }
清除
@Override
    public void clear() {
        top = 0;
    }

链式栈基本介绍

链栈是一种基于链表实现的栈,其特点是无需事先分配固定长度的存储空间,栈的长度可以动态增长或缩小,避免了顺序栈可能存在的空间浪费和存储溢出问题。

链栈中的每个元素称为“节点”,每个节点包括两个部分:数据域和指针域。数据域用来存储栈中的元素值,指针域用来指向栈顶元素所在的节点。

链栈的基本操作包括入栈、出栈、获取栈顶元素和遍历等,相比顺序栈而言,链栈的实现难度稍高,但其在某些情况下有着更好的灵活性和效率,特别适用于在动态存储空间较为紧缺的场合。

链栈的进栈push和出栈pop操作都很简单,时间复杂度均为O(1)。

注意:如果栈的使用过程中元素变化不可预料,那么最好使用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈。

链栈的种类

链栈按照链表的实现方式可分为单链栈和双链栈。实际应用通常采用单链栈

单链栈使用单链表实现,每个节点只含有一个指向下一个节点的指针。因此,单链栈只能从栈顶进行插入和删除操作。

采用链式存储的栈称为链栈。链栈的优点是便于多个栈共享存储空间和提高效率,且不存在栈满上溢的清空。通常采用单链表实现,并且规定所有操作都是在单链表的表头进行上的(因为头结点的 next 指针指向栈的栈顶结点)。

特点
  • 采用链式存储,便于结点的插入和删除,但是对于栈这种只能在一端操作的数据结构来说,顺序栈和链栈插入和删除的时间复杂度区别不大。
  • 链栈的操作和顺序栈的操作类似,出栈和入栈的操作都在栈顶进行。
  • 对于带头结点和不带头结点的链栈,具体实现有所不同,常用带头结点。

链栈的进栈

push:将元素入栈。以 stack=[11, 22, 33]; ele=44 为例如图所示:

链栈的出栈

pop:将元素出栈,如果是空栈则不能出栈,并且将出栈元素保存到 ele 中。以 stack=[44, 33, 22, 11] 为例如图所示:

链栈的结构体定义

链式栈(基于单链表)

        链式堆栈是由一个个节点组成的,每个节点有两个域组成,一个是存放数据元素的数据元素域data,另一个是存放指向下一个节点的对象引用域next

节点类
/**
 * 节点类
 */
public class Node {
    /**
     * 存放数据
     */
    Object element;
    /**
     * 存放下一个节点
     */
    Node next;

    public Node() {
    }

    public Node(Node next) {
        this.next = next;
    }

    public Node(Object element, Node next) {
        this.element = element;
        this.next = next;
    }

    public Object getElement() {
        return element;
    }

    public void setElement(Object element) {
        this.element = element;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }
}
实现类
public class LinkStack implements Stack {
    Node head; // 栈顶指针
    int size; // 结点的个数
}
入栈
@Override
    public void push(Object object) {
        //新的object,老的head 一起赋给新的head
        head = new Node(object, head);
        size++;
    }
出栈
@Override
    public Object pop() {
        if (isEmpty()) {
            System.out.println("栈为空");
            return;
        }
        Object element = head.getElement();
        head = head.getNext();
        size--;
        return element;
    }
获取栈顶元素
@Override
    public Object getTop() {
        if (isEmpty()) {
            System.out.println("栈为空");
            return;
        }
        return head.getElement();
    }
判断栈是否为空
@Override
    public boolean isEmpty() {
        return head == null;
    }
清除
@Override
    public void clear() {
        head = null;
        size = 0;
    }

Stack使用举例

public class TestStack {
    /*
    * 测试Stack
    * */
    @Test
    public void test1(){
        Stack<Integer> list = new Stack<>();
        list.push(1);
        list.push(2);
        list.push(3);

        System.out.println("list = " + list);

        System.out.println("list.peek()=" + list.peek());
        System.out.println("list.peek()=" + list.peek());
        System.out.println("list.peek()=" + list.peek());

/*
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());//java.util.NoSuchElementException
*/

        while(!list.empty()){
            System.out.println("list.pop() =" + list.pop());
        }
    }

    /*
    * 测试LinkedList
    * */
    @Test
    public void test2(){
        LinkedList<Integer> list = new LinkedList<>();
        list.push(1);
        list.push(2);
        list.push(3);

        System.out.println("list = " + list);

        System.out.println("list.peek()=" + list.peek());
        System.out.println("list.peek()=" + list.peek());
        System.out.println("list.peek()=" + list.peek());

/*
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());
		System.out.println("list.pop() =" + list.pop());//java.util.NoSuchElementException
*/
        while(!list.isEmpty()){
            System.out.println("list.pop() =" + list.pop());
        }
    }
}

自定义栈

public class MyStack {
    // 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。
    // 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据
    private Object[] elements;

    // 栈帧,永远指向栈顶部元素
    // 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。
    //private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。
    //private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。
    private int index;

    /**
     * 无参数构造方法。默认初始化栈容量10.
     */
    public MyStack() {
        // 一维数组动态初始化
        // 默认初始化容量是10.
        this.elements = new Object[10];
        // 给index初始化
        this.index = -1;
    }

    /**
     * 压栈的方法
     * @param obj 被压入的元素
     */
    public void push(Object obj) throws Exception {
        if(index >= elements.length - 1){
            //方式1:
            //System.out.println("压栈失败,栈已满!");
            //return;
            //方式2:
            throw new Exception("压栈失败,栈已满!");
        }
        // 程序能够走到这里,说明栈没满
        // 向栈中加1个元素,栈帧向上移动一个位置。
        index++;
        elements[index] = obj;
        System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
    }

    /**
     * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
     * @return
     */
    public Object pop() throws Exception {
        if (index < 0) {
            //方式1:
            //System.out.println("弹栈失败,栈已空!");
            //return;
            //方式2:
            throw new Exception("弹栈失败,栈已空!");
        }
        // 程序能够执行到此处说明栈没有空。
        Object obj = elements[index];
        System.out.print("弹栈" + obj + "元素成功,");
        elements[index] = null;
        // 栈帧向下移动一位。
        index--;
        return obj;
    }

    // set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。
    // 封装:第一步:属性私有化,第二步:对外提供set和get方法。
    public Object[] getElements() {
        return elements;
    }

    public void setElements(Object[] elements) {
        this.elements = elements;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值