关于栈和队列

目录

  • 栈(Stack)
    • 什么是栈
    • 栈的使用
    • 栈的模拟实现
  • 队列(Queue)
    • 什么是队列
    • 队列的使用
    • 队列的模拟实现
    • 循环队列
    • 双端队列 (Deque)

栈(Stack)

什么是栈

栈是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后入先出的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据是在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
关于栈和队列_第1张图片
出栈时,是C先出,然后是B,再是A .

注意:这个栈是一种数据结构,与JVM内存模型中的栈不同,JVM中的栈是指JVM中一段内存区域。

栈的使用

关于栈和队列_第2张图片

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 栈顶元素为2
	if(s.empty()){
		System.out.println("栈空");
	}else{
		System.out.println(s.size());
	}
}

关于栈和队列_第3张图片

栈的模拟实现

关于栈和队列_第4张图片
可以看到,Stack继承了Vector,Vector是动态顺序表,与ArrayList类似,不同的是 Vector 是线程安全的。

import java.util.Arrays;

public class MyStack {
    int array[];     //数组用于存放数据
    int size = 0;    //size用于记录当前元素个数

    public MyStack() {  
        this(12);     //默认开辟大小为12的空间
    }
    public MyStack(int m) {
        array = new int[m];
    }
    //入栈
    public int push(int value) {
        if(size == array.length) {
            //扩容
            int[] copy;
            //复制array,并将其扩容一倍
            copy = Arrays.copyOf(array,2* array.length);
            array = copy;  //将array指向扩容后的数组
        }
        array[size] = value;
        size++;
        return value;
    }
    //出栈
    public int pop() {
        if(size == 0) {
            throw new RuntimeException("栈中没有元素");
        }
        int n = array[size-1];
        size--;
        return n;
    }
    //获取栈顶元素
    public int peek() {
        if(size == 0) {
            throw new RuntimeException("栈中没有元素");
        }
        return array[size-1];
    }
    //获取元素个数
    public int getSize() {
        return size;
    }
    //判断栈是否为空
    public boolean isEmpty() {
        return size == 0;
    }
}

队列(Queue)

什么是队列

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出特性。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头

关于栈和队列_第5张图片

队列的使用

关于栈和队列_第6张图片
在Java中,Queue是个接口,底层是通过链表LinkedList实现的,LinkedList实现了Queue接口,我们可以通过LinkedList实例化对象,如:

	Queue<Integer> queue = new LinkedList<>();

队列里的方法:

关于栈和队列_第7张图片

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

输出:
关于栈和队列_第8张图片

队列的模拟实现

实现一个队列,我们可以用数组存储数据,也可以用链表存储数据,那究竟用哪种存储方式更好呢?

我们认为链表存储更好,因为链表的存储空间不连续,可以更好的利用存储空间(可以利用零碎的空间),而数组它的空间是连续的,万一空间不足就会出问题(不能利用零碎空间)。

public class MyQueue {
    //双向链表节点
    public static class ListNode {
        ListNode next;  //记录下一个节点
        ListNode prev;  //记录上一个节点
        int val;      //记录该节点存储的值
        ListNode(int val){
            this.val = val;
        }
    }
    ListNode head; // 队头
    ListNode last; // 队尾
    int size = 0;

    //入队列
    public void offer(int val) {
        ListNode node = new ListNode(val);  //先实例化这个节点出来
        if(last == null) {   //链表中未存放元素时
            last = node;
            head = last;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
        size++;   //链表元素个数加一
    }

    //出队列
    public int poll() {
        if(isEmpty()) {  //判断队列是否为空
            throw new RuntimeException("队列为空,无法出元素");
        }
        int m = head.val;
        head = head.next;
        head.prev = null;
        size--;
        return m;
    }

    //判断队列是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    //获取对头元素
    public int peek() {
        if(isEmpty()) {  //判断队列是否为空
            throw new RuntimeException("队列为空,无法获取元素");
        }
        return head.val;
    }

    //获取元素个数
    public int getSize() {
        return size;
    }
}

循环队列

我们有时还会使用一种队列叫循环队列。环形队列通常使用数组实现。
队头添加元素,队尾删除元素,每块空间都能反复利用,有效节省空间。

front 指向队列的第一个元素,即array[front]为队列的第一个元素,初始值为0;

rear 指向最后一个元素的后一个元素,初始值为0;
关于栈和队列_第9张图片
从上图可以看出队列为空的条件:front == rear
当队列存放一个元素时,rear向前走一步,如下图:
关于栈和队列_第10张图片

那队列啥时候满了呢?当情况为下图时吗?
关于栈和队列_第11张图片
如果这就是满的话,但如何判断呢?是 front == rear 吗?
显然,这与判断队列为空相矛盾,我们不应该这样判断,那应如何判断呢?
我们有两种方法:

  1. 通过添加 size 属性记录(用size记录元素个数)
  2. 牺牲一个位置来表示满

我们就以牺牲一个位置为例吧,如果我们牺牲一个位置来表示满,当情况为下图时:
关于栈和队列_第12张图片
此情况就表示满,条件即是:(rear + 1) % array.length == front

class MyCircularQueue {
    int front = 0;
    int rear = 0;
    int[] array;
    public MyCircularQueue(int k) {  //设计一个可以存放k个数据的数组
        array = new int[k+1];     //为什么要设计k+1个大小呢?
    }                           //因为在队尾后面需要牺牲一个空间,方便判满
    
    //向循环队列插入一个元素。如果成功插入则返回真。
    public boolean enQueue(int value) {
        if(isFull()) {
            return false;
        }
        array[rear] = value;
        rear = (rear + 1) % array.length;
        return true;
    }
    
    //从循环队列中删除一个元素。如果成功删除则返回真。
    public boolean deQueue() {
        if(isEmpty()) {
            return false;
        }
        front = (front + 1) % array.length;
        return true;
    }
    
    //从队首获取元素。如果队列为空,返回 -1 。
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return array[front];
    }
    
    //获取队尾元素。如果队列为空,返回 -1 。
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        return array[(rear+array.length-1)%array.length];
    }
    
    //检查循环队列是否为空。
    public boolean isEmpty() {
        if(rear == front) {
            return true;
        }
        return false;
    }
    
    //检查循环队列是否已满。
    public boolean isFull() {
        if((rear+1) % array.length == front) {
            return true;
        }
        return false;
    }
}

双端队列 (Deque)

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

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

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

	Deque<Integer> stack = new ArrayDeque<>();//双端队列的线性实现
	Deque<Integer> queue = new LinkedList<>();//双端队列的链式实现

你可能感兴趣的:(Java学习,java,数据结构)