栈和队列的详解

碎碎念:今天在学习数据结构,刷Leetcode的时候偶尔栈和队列不会了,还得刷,正所谓勤能补拙是良训,一分辛苦一分才。
这里感谢大佬的博客1.http://c.biancheng.net/data_structure/stack_queue/。 2.https://blog.csdn.net/whl190412/article/details/94565236 3.https://blog.csdn.net/wyqwilliam/article/details/82725637
本人对其加以理解并整理。

定义

栈和队列严格来说属于线性表,但是都都比较特殊:

  • 栈 :先进后出
  • 队列:先进先出
    既然栈和队列都属于线性表,根据线性表分为顺序表和链表的特点,栈也可分为顺序栈和链表,队列也分为顺序队列和链队列,这些内容都会在本章做详细讲解。

和顺序表的链表一样也是存储一对一的关系的数据结构。

说明一下哈:

  • 顺序表是数组的表,可以直接访问数据的位置所产生的表结构
  • 链表是链式结构,不能访问到及具体的位置信息,只能通过链式查找

在这里插入图片描述
简单理解:栈只能从一头存储数据,并且从那个位置来取数据。
因此,我们可以给栈下一个定义,即栈是一种只能从表的一端存取数据且遵循 “先进后出” 原则的线性存储结构。
通常,栈的开口端被称为栈顶;相应地,封口端被称为栈底。因此,栈顶元素指的就是距离栈顶最近的元素,拿图 2 来说,栈顶元素为元素 4;同理,栈底元素指的是位于栈最底部的元素,图 2 中的栈底元素为元素 1。
在这里插入图片描述
这里:要注意,栈顶是指距离最近的位置的元素,千万不要搞混。

进栈出栈

基于栈结构的特点,在实际应用中,通常只会对栈执行以下两种操作:

  • 向栈中添加元素,此过程被称为"进栈"(入栈或压栈);
  • 从栈中提取出指定元素,此过程被称为"出栈"(或弹栈);

应用

例如,浏览器的回退,以及括号的编程问题,等等,当然在面试与笔试中也占了很大的一部分,希望学会。

顺序表结构栈

顺序栈,即用顺序表实现栈存储结构。例如,我们先使用顺序表(a 数组)存储 {1,2,3,4},存储状态如图 1 所示:
在这里插入图片描述
使用栈存储的结构情况为
在这里插入图片描述
通过图 1 和图 2 的对比不难看出,使用顺序表模拟栈结构很简单,只需要将数据从 a 数组下标为 0 的位置依次存储即可。
了解了顺序表模拟栈存储数据后,接下来看如何模拟栈中元素出栈的操作。由于栈对存储元素出栈的次序有"先进后出"的要求,如果想将图 1 中存储的元素 1 从栈中取出,需先将元素 4、元素 3 和元素 2 依次从栈中取出。这里给出使用顺序表模拟栈存储结构常用的实现思路,即在顺序表中设定一个实时指向栈顶元素的变量(一般命名为 top),top 初始值为 -1,表示栈中没有存储任何数据元素,及栈是"空栈"。

这里简单来讲就是从栈顶向外弹出元素,栈顶top 初始为-1 表示为空

栈的结构

public class Stack implements IStack {//实现接口  
    private int[] mIndex;  
    private Object[] stackElem;  
  
    public Object[] getStackElem() {  
        return stackElem;  
    }  
  
    public int[] getmIndex() {  
        return mIndex;  
    }  
  
    private int top;  
  
    public Stack(int maxSize) {  
        stackElem = new Object[maxSize];  
        mIndex = new int[maxSize];  
        top = 0;  
    }  
      public void clear() {  
        // TODO Auto-generated method stub  
        top = 0;  
    }  
  
    public boolean isEmpty() {  
        // TODO Auto-generated method stub  
        return top == 0;//当top为0,栈为空;  
    }  
  
    public int length() {  
        // TODO Auto-generated method stub  
        return top;  
    }  
  
    // 取栈顶元素的函数  
    public Object peek() {  
        // TODO Auto-generated method stub  
        if (!isEmpty())  
    top = top - 1;
            return stackElem[top+1];//top指向最新插入的元素,即栈顶。  
        else  
            return null;  
    }  

(这里绝对我讲的不透彻可以看看上面大神的链接)
基本内容和大神的过程,如下:

入栈

在这里插入图片描述

/** 
     * 进栈操作 要求:时间复杂度是O(1) 
     * */  
    public boolean push(Object x) {  
        // TODO Auto-generated method stub  
        if (top == stackElem.length) {  
            System.out.print("栈已满");  
            return false;  
        }  
        if (top == 0) {  
            mIndex[top] = 0;  
        } else if ((Integer)x <(Integer)peek()&& peek() != null) {  
            mIndex[top] = top;  
        } else if (peek() != null) {  
            mIndex[top] = mIndex[top - 1];  
        }  
        stackElem[top++] = x;  
        return true;  
  
    }  

出栈

在这里插入图片描述

 /** 
     * 顺序栈的出栈操作 时间复杂度为:O(1) 
     * */  
    public Object pop() throws Exception {  
        // TODO Auto-generated method stub  
        if (!isEmpty()) {// 若栈不空,则移去栈顶元素并返回其值  
            mIndex[top - 1] = -1;  
            return stackElem[--top];  
        } else  
            return null;// 若栈空,则返回空值  
  
    }  

寻找最小的元素

 /** 
     * 返回栈中最小的元素,需要一个辅助栈 要求:时间复杂度是O(1) 
     * 解决这道题的思路在于:用空间换时间!很关键,很直白,但并不是很个人都能很灵活的运用!比如我就不能! 
     * 代码实现也很简单,定义一个结构体,结构体中两个等大的数组stackElem,mIndex;一个存储数据,另一个存储最小值的下标, 
     * mIndex[i]表示[0,i]区间中最小值的下标。 
     * */  
    public Object min() {  
        if (top == 0)  
            return null;  
        else  
            return stackElem[mIndex[top - 1]];  
  
    }  
  

颠倒栈的结构

  @Override  
    /** 
     * 要求用递归的方法,在不浪费空间的情况下,颠倒栈。 
     * */  
    public void reverseStack(Stack s) throws Exception {  
        if (!s.isEmpty()) {  
            Object t = s.pop();  
            reverseStack(s);  
            pushStackButtom(s, t);  
        }  
    }  
  
    public void pushStackButtom(Stack s, Object t) throws Exception {  
        if (s.isEmpty()) {  
            s.push(t);  
        } else {  
            Object top = s.pop();  
            pushStackButtom(s, t);  
            s.push(top);  
        }  
    }  
}  

链表式结构

以下代码来源于:博主「东北一绝、英俊侠」的原创文章,原文链接https://blog.csdn.net/whl190412/article/details/94565236,后续本人将从Leetcode的题型中进行实现

栈结构

public class LinkedListStack<Item> {
  // 栈中元素的总数
  private int N = 0;
  // 链表头元素
  private Node front;
  // 内部结点类
  private class Node {
    Item item;
    Node next;
  }

插入元素

  /**
   * @description: 向栈顶插入元素
   */
  public void push (Item item) {
    Node oldFront = front;
    // 向链表头部插入新的结点
    front = new Node();
    front.item = item;
    // 将新头结点的next指针指向旧的头结点
    front.next = oldFront;
    // 栈的长度加1
    N++;
  }

弹出元素

/**
   * @description: 向栈顶删除元素,并将删除的元素返回
   */
  public Item pop () {
    // 当栈还是空的时候, 不删除并且返回空
    if(isEmpty()) return null;
    // 保存待删除的项以便返回
    Item item = front.item;
    // 删除原头结点
    front = front.next;
    // 栈的长度减1
    N--;
    return item;
  }

判断是否为空

 /**
   * @description: 判断栈是否为空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回栈的大小
   */
  public int size () {
    return N;
  }

创建实例对象

 public static void main (String args []) {
    // 创建栈
    LinkedListStack<Integer> stack = new LinkedListStack<>();
    // 向栈顶依次添加3个元素
    stack.push(1);
    stack.push(2);
    stack.push(3);
    // 依次从栈顶删除3个元素
    System.out.println(stack.pop());
    System.out.println(stack.pop());
    System.out.println(stack.pop());
  }

队列

定义:队列即遵循先进先出的原则,就相当于现实生活中的排队
在这里插入图片描述
同样:实现队列也有两种方式,一种是链表, 另一种是循环数组 ------------队列和栈在实现上的不同

栈主要是从数据或者链表的一端进行操作,
队列从两端进行操作

链表实现

public class LinkedListQueue<Item> {
  // 链表中的结点数目
  private int N = 0;
  // 链表头结点
  private Node front = null;
  // 链表尾结点
  private Node rear = null;
  // 结点内部类
  private class Node {
    Item item;
    Node next;
  }

入列

/**
   * @description: 元素入列(在链表尾部添加)
   */
  public void enqueue (Item item) {
    Node oldRear = rear;
    rear = new Node();
    rear.item = item;
    if (isEmpty()) front = rear;
    else           oldRear.next = rear;
    N++;
  }

出列

 /**
   * @description: 元素出列(在链表头部删除)
   */
  public Item dequeue () {
    if(isEmpty()) return null;
    Item item = front.item;
    front = front.next;
    N--;
    if(isEmpty()) rear = null;
    return item;
  }

判断是否为空,返回长度

 /**
   * @description: 判断队列是否为空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回队列长度
   */
  public int size () {
    return N;
  }

测试

public static void main (String args []) {
    LinkedListQueue<String> queue = new LinkedListQueue<>();
    queue.enqueue("A");
    queue.enqueue("B");
    queue.enqueue("C");
    queue.enqueue("D");
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
  }
}

数组的队列

这里采用循环的方式遍历数组,如果是普通的数组会产生很大的浪费如下
在这里插入图片描述
在这里插入图片描述
代码的方式如下:
代码如下图所示, 可以看到,实现循环的关键是使用的一个取余数的操作,使得指针在移动到数组尾部的时候,能够重新移动到数组的头部:

栈结构

public class CircleArrayQueue<Item> {
  // 队列元素总数
  private int N = 0;
  // 数组长度
  private int M;
  // 队列头部元素指针
  private int front = 0;
  // 队列尾部元素指针
  private int rear = 0;
  private Item [] items;
  public CircleArrayQueue (int M) {
    this.M = M;
    items = (Item [])new Object[M];
  }

入列操作

/**
   * @description: 入列操作
   */
  public void enqueue (Item item) {
    // 当队列为空时, 不能进行入列操作
    if (isFull()) return;
    // 向队列尾部插入元素
    items[rear] = item;
    // 用数组长度M取余, 使得rear到达数组尾部时能返回数组头部
    rear = (rear + 1) % M;
    // 增加队列长度
    N++;
  }

出列操作

  /**
   * @description: 出列,并返回被删除项
   */
  public Item dequeue () {
    // 当队列为满时, 不能进行出列操作
    if (isEmpty()) return null;
    // 保存待删除元素, 以待返回
    Item item = items[front];
    // 删除队列头部元素
    items[front] = null;
    // 用数组长度M取余, 使得front到达数组尾部时能返回数组头部
    front = (front + 1) % M;
    // 减少队列长度
    N--;
    // 返回删除元素
    return item;
  }

判断是否满是否空等。

 /**
   * @description: 判断队列是否满了
   */
  public boolean isFull () {
    return N == M;
  }
  /**
   * @description: 判断队列是否为空
   */
  public boolean isEmpty () {
    return N == 0;
  }
  /**
   * @description: 返回队列元素总数
   */
  public int size () {
    return N;
  }

循环数据方式

判断循环数组的满状态和空状态
在循环数组的实现中,一个非常重要的操作就是区分数组是处在"满"状态还是“空”状态,因为当front和rear指向同一个元素位置时,既可能处在满状态也可能处在空状态。上面的代码里我们是通过一个表示队列元素总数的变量N去判断的,除此之外,我们也可以通过另外一种不依赖于变量N的方式去判断数组的满和空的状态, 但代价是少用一个元素空间,例如:

public class CircleArrayQueue2<Item> {
  private int M;
  private int front = 0;
  private int rear = 0;
  private Item [] items;
  public CircleArrayQueue2 (int M) {
    this.M = M;
    items = (Item [])new Object[M];
  }

  public void enqueue (Item item) {
    if (isFull()) return;
    items[rear] = item;
    rear = (rear + 1) % M;
  }

  public Item dequeue () {
    if (isEmpty()) return null;
    Item item = items[front];
    items[front] = null;
    front = (front + 1) % M;
    return item;
  }

  public boolean isFull () {
    return (rear + 1) % M == front;
  }

  public boolean isEmpty () {
    return rear == front;
  }

  public static void main (String args []) {
    CircleArrayQueue2<Integer> queue = new CircleArrayQueue2<>(3);
    queue.enqueue(1);
    queue.enqueue(2);
    queue.enqueue(3);
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
    System.out.println(queue.dequeue());
  }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值