Java版的数据结构——栈和队列

目录

1. 栈(Stack)

1.1 概念

1.2 栈的使用

1.3 栈的模拟实现

1.4 栈的应用场景

1.4.1 改变元素的序列

1.4.2 将递归转化为循环

2. 队列(Queue)

2.1 概念

2.2 队列的使用

2.3 队列模拟实现

2.4 循环队列

3. 双端队列(Deque)


1. 栈(Stack)

1.1 概念

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。

1.2 栈的使用

方法功能
Stack()构造一个空的栈
E push(E e)将e入栈,并返回e
E pop()将栈顶元素出栈并返回
E peek()获取栈顶元素
int size()获取栈中有效元素个数
boolean empty()检测栈是否为空
public static void main(String[] args) {
  Stack<Integer> s = new Stack();
  s.push(1);
  s.push(2);
  s.push(3);
  s.push(4);
  System.out.println(s.size());  // 获取栈中有效元素个数---> 4
  System.out.println(s.peek());  // 获取栈顶元素---> 4
  s.pop();  // 4出栈,栈中剩余1  2  3,栈顶元素为3
  System.out.println(s.pop());  // 3出栈,栈中剩余1 2  栈顶元素为3
  if(s.empty()){
    System.out.println("栈空");
 }else{
    System.out.println(s.size());
 }
}

1.3 栈的模拟实现

从上图中可以看到,Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表不同的是Vector是线程安全的。

import java.util.Arrays;

public class MyStack {
    //创建一个顺序栈
    public int[] elem;
    public int usedSize;

    public MyStack(){
        this.elem = new int[10];
    }

    //压栈
    public void push(int val){
        //首先判断栈是不是满了
        if(isFull()){
            //扩容
            elem = Arrays.copyOf(elem,elem.length * 2);
        }
        elem[usedSize++] = val;
    }

    //出栈
    public int pop(){
        if (isEmpty()){
            throw new EmptyException("栈是空的!");
        }
        return elem[--usedSize];
    }

    //查看栈顶
    public int peek(){
        if(isEmpty()){
            throw new EmptyException("栈是空的!");
        }
        return elem[usedSize - 1];
    }

    //判断栈是不是空了
    public boolean isEmpty(){
        return usedSize == 0;
    }

    //栈的大小
    public int size(){
        return usedSize;
    }
    
    //判断栈是不是满了
    public boolean isFull(){
        return usedSize == elem.length;
    }
}

1.4 栈的应用场景

1.4.1 改变元素的序列

1. 若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是(C
 A: 1,4,3,2  B: 2,3,4,1  C: 3,1,4,2  D: 3,4,2,1
 
2.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是(C)。
A: 12345ABCDE  B: EDCBA54321  C: ABCDE12345  D: 54321EDCBA

1.4.2 将递归转化为循环

// 递归方式
void printList(Node head){
  if(null != head){
    printList(head.next);
 }
  System.out.print(head.val + " ");
}
Stack<Node> s = new Stack<>();
  // 将链表中的结点保存在栈中
  Node cur = head;
  while(null != cur){
    s.push(cur);
    cur = cur.next;
  }

 // 将栈中的元素出栈
 while(!s.empty()){
    System.out.print(s.pop().val + " ");
 }

2. 队列(Queue)

2.1 概念

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First
In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头
(Head/Front)

2.2 队列的使用

 在Java中,Queue是个接口,底层是通过链表实现的。

方法功能
boolean offer(E e)入队列
E poll()出队列
peek()获取队头元素
int size()获取队列中的有效元素个数
boolean isEmpty()检测队列是否为空

注意:Queue是个接口,在实例化时必须实例化LinkedList的对象,因为LinkedList实现了Queue接口。

public static void main(String[] args) {
  Queue<Integer> q = new LinkedList<>();
  q.offer(1);
  q.offer(2);
  q.offer(3);
  q.offer(4);
  q.offer(5);          // 从队尾入队列
  System.out.println(q.size());
  System.out.println(q.peek());  // 获取队头元素
 
  q.poll();
  System.out.println(q.poll());  // 从队头出队列,并将删除的元素返回
 
  if(q.isEmpty()){
    System.out.println("队列空");
 }else{
    System.out.println(q.size());
 }
}

2.3 队列模拟实现

队列中既然可以存储元素,那底层肯定要有能够保存元素的空间,通过前面线性表的学习了解到常见的空间类型有两种:顺序结构 和 链式结构

小伙伴们思考下:队列的实现使用顺序结构还是链式结构好?【在下列的循环队列解释】

//链式队列
public class MyQueue {
    //首先创建结点,用内部类实现
    static class Node{
        public int val;
        public Node next;
        //构造方法
        public Node(int val){
            this.val = val;
        }
    }
    //有个队头指针和队尾指针
    public Node head;
    public Node last;
    //队列的大小
    public int usedSize;
    
    //入队
    public void offer(int val){
        Node node = new Node(val);
        if(head == null){//证明队列为空
            head = node;
            last = node;
        }else{//如果有元素了
            last.next = node;
            last = node;
        }
        usedSize++;
    }
    
    
    
    //出队
    public int poll() {
        //判断队列是否为空
        if (empty()) {
            throw new EmptyException("队列为空");//自定义异常类
        }
        int ret = head.val;
        head = head.next;
        if (head == null) {
            last = null;//只有一个结点时,那么last也要置空。因为如果last不置空,出队的结点还是有引用指向它,它就不会被gc回收
        }
        usedSize--;
        return ret;
    }

    //判断队列是否为空
    public boolean empty() {
        return usedSize == 0;
    }
    
    //查看队头元素
    public int peek(){
        //判断队列是否为空
        if (empty()) {
            throw new EmptyException("队列为空");//自定义异常类
        }
        return head.val;
    }
    
    //获得队列大小
    public int getUsedSizeize(){
        return usedSize;
    }

}

2.4 循环队列

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列通常使用数组实现。

如果是普通的顺序队列,就会导致队头标记跑到了数组的末尾,导致存储空间的浪费,这也就是上面的队列实现要使用链式队列的原因

数组下标循环的小技巧
1. 下标最后再往后(offset 小于 array.length): index = (index + offset) % array.length

2. 下标最前再往前(offset 小于 array.length): index = (index + array.length - offset) % array.length

 如何区分空与满

1. 通过添加 size 属性记录
2. 保留一个位置
3. 使用标记

代码实现: 

//循环队列
public class MyCircularQueue {
    private int elem[];
    private int front;//表示队列的头
    private int rear;//表示队列的尾

    //创建循环队列
    public MyCircularQueue(int k) {
        //如果是浪费空间,这里必须多加一个1
        this.elem = new int[k + 1];
    }

    //判断队列是否为满
    public boolean isFull() {
        return (rear + 1) % elem.length == front;
    }

    //入队列
    public boolean enQueue(int value) {
        //1. 检查是否队列是满的
        if (isFull()) {
            return false;
        }
        rear = (rear + 1) % elem.length;
        elem[rear] = value;
        return true;
    }


    //出队列
    public boolean deQueue() {
        //队列为空
        if (isEmpty()) {
            return false;
        }
        front = (front + 1) % elem.length;
        return true;
    }

    //得到队头元素
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return elem[front];
    }

    //得到队尾元素
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int index = (rear == 0) ? elem.length - 1 : rear-1;
        return elem[index];

    }

    //判读队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }
}

3. 双端队列(Deque)

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。

Deque是一个接口,使用时必须创建LinkedList的对象。

在实际工程中,使用Deque接口是比较多的,栈和队列均可以使用该接口

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木子斤欠木同

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

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

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

打赏作者

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

抵扣说明:

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

余额充值