队列Queue

队列 Queue

在这里插入图片描述

队列的 概念与 特点 先进先出

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

Queue 实现的两个接口

在这里插入图片描述

Deque 和 Queue

来看LinkedList(底层是一个双向链表) 实现了 两个接口 一个是

Deque 和 Queue
在这里插入图片描述

Queue的常用方法

1.添加元素 offer 与 add

public class Main {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(1);
        queue.offer(2);
        System.out.println(queue.peek());
        System.out.println(queue.element());

    }
}

2.获取队头元素 peek 和 element

public class Main {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(1);
        queue.offer(2);
        System.out.println(queue.peek()); 1
        System.out.println(queue.element()); 1

    }
}

在这里插入图片描述

3出队列 poll 和 remove

public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        queue.add(1);
        queue.offer(2);
        System.out.println(queue.peek());
        System.out.println(queue.element());
        System.out.println(queue.poll());
        System.out.println(queue.remove());
        System.out.println(queue);
        
    }

在这里插入图片描述

4.添加 删除 查看 队头 元素 方法的区别

在这里插入图片描述

这里 添加元素 一般 用的比较多 的是 offer
在这里插入图片描述
双端队列 Deque
在这里插入图片描述
双端队列的部分方法

public class Main {
    public static void main(String[] args) {
        Deque<Integer> deque = new LinkedList<>();
        deque.offerLast(1); // Last 尾部 入队
        deque.offerFirst(2);// First 头入队
        deque.offer(3);// 默认队尾入队
        System.out.println(deque.peekFirst());
        System.out.println(deque.peekLast());
        System.out.println(deque);
    }
}

看完了 双端队列,知道了 LinkedList,底层是一个双向链表那么 回顾一下一看是 我们学习的 ArrayList 顺序表
这里 我们 在这里做一个小总结 ,

ArrayList 与 LinkedList 的 区别

从增删查改 的 角度 来看 ,ArrayList 底层 是一个数组,每次 删一个元素 ,是通过移动元素 完成,而LinkedList 是 链表结构,删除元素只需要 让上一个节点指向

这个节点的下一个节点,在指定位置添加 同理 ,ArrayList 还是 通过 移动元素 来完成添加,而 LinkedList 只需要改变节点的指向即可。

另外 ArrayList 在 内存中是连续的,而LinekdList 在 内存中 不是连续的(看上去是连续的,但是每个节点代表一块空间,通过引用连接在一起,所以他不是连续的)
了解完了这么多 那么 我们 来 分 三种方式 来实现我们字节 的队列

实现自己的队列

在这里插入图片描述

1.单链表实现

class Node {
    public int val;
    public  Node next;
    public Node(int val) {
        this.val = val;
    }
}
// 使用单链表实现队列
public class MyQueue {
    // 标记头
    public Node head;
    // 标记 尾
    public  Node last;

    public void offer(int val) {
        Node node = new Node(val);
        if(head == null) {
         head = node;
         last = node;
        }else {
            last.next = node;
            last = last.next;
        }
    }
    public int poll() {
        if(isempty()) {
            throw new RuntimeException("队列为空");
        }
        int oldVal = head.val;
        this.head = head.next;
        return oldVal;
    }
    public int peek() {
        if(isempty()) {
            throw new RuntimeException("队列为空");
        }
        int oldVal = head.val;
        return oldVal;
    }
    public boolean isempty() {
        return this.head == null;
    }

    @Override
    public String toString() {
        return "MyQueue{" +
                "head=" + head +
                ", last=" + last +
                '}';
    }
}

测试用例

public class Test {
    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        System.out.println(queue.peek());//1
        System.out.println(queue.poll());//1
        System.out.println(queue.poll());//2
        System.out.println(queue.poll());//3
        System.out.println(queue.poll());//异常

    }
}

在这里插入图片描述

2.双向链表实现

class Node {
    public int val;
    public Node next;
    public Node prev;
    public Node(int val) {
        this.val = val;
    }
}
// 使用双向链表实现栈
public class MyQueue2 {
    // 标记头
    public Node head;
    public Node cur;
    public void offer(int val) {
        Node node = new Node(val);
        if(cur == null) {
            head = node;
            cur = node;
        }else {

            cur.next = node;
            node.prev = cur;
            cur = cur.next;

        }
    }
    // 删除队头元素
    public int poll() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空无法删除");
        }
        int oldVal = head.val;

        head.prev = null;

        this.head = head.next;

        return oldVal;
    }
    public int peek() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空无法删除");
        }
        int oldVal = head.val;
        return oldVal;
    }
    public boolean isEmpty() {
        return this.head == null;
    }

}

测试用例

public class Test {
    public static void main(String[] args) {
        MyQueue2 queue = new MyQueue2();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        queue.offer(4);
        System.out.println(queue.peek());//1
        System.out.println(queue.poll());//1
        System.out.println(queue.poll());//2
        System.out.println(queue.poll());//3
        System.out.println(queue.poll());//4
        System.out.println(queue.poll());//异常

    }
}

在这里插入图片描述
通过链表实现完 队列,有没有想过 使用 数组来实现 队列呢??
在 实现数组 实现 队列 的时候 我们 要先学习一个 循环队列 ,要不 我们很难实现 队列

引出 循环队列

在这里插入图片描述

先来看一下 环形队列

在这里插入图片描述
接下来看 一下我们的小手段

数组下标循环的小技巧

  1. 下标最后再往后(offset 小于 array.length):

    index = (index + offset) % array.length

在这里插入图片描述
7 走 到 2 的偏移量 offset 为 4 通过 公式 (index+ offset)% arraylength = (7 + 4) % 9 = 11 % 9 = 2
在这里插入图片描述

循环队列判满的三种方法

在这里插入图片描述

第一种解决方式 使用 usedSize

在这里插入图片描述

第二种 解决方式 使用标志位

创建一个boolean 变量flag 先 赋值 为 false rear每次 走 一步 flag 赋值为 true 知道 front 与 rear 相遇如果 flag 为 true 数组就为 满 ,

front 每次 走一步 flag 赋值为 false 当front 与 rear 相遇 且 flag 为false 数组就为空
在这里插入图片描述

第三种 浪费一个格子判断空和满

在这里插入图片描述
在这里插入图片描述
了解 了 这么 多 循环队列的 知识 那么 我们直接 上oj题
在这里插入图片描述
在这里插入图片描述
那么 开始 吧

设计循环队列

入队 和 判断队列是否满
在这里插入图片描述
出队 和 判断 数组 是否为空,获取 队头 和 队尾下标
在这里插入图片描述
完成了 方法 我们 放到 oj 上来 跑
在这里插入图片描述
结果发现 错误 这里 不要机 我们 来 观察 输出 和 预计 结果

接下来我们 要观察一下 oj 上 给的 错误信息
在这里插入图片描述

class MyCircularQueue {
        public int[] elem;
        public int front; // 队头
        public int rear; // 队尾

    public MyCircularQueue(int k) {
        this.elem = new int[k+1];
    }

    // 入队
    public boolean enQueue(int value) {
        if(isFull()) return false;
       this.elem[rear] = value;
        rear =   (rear + 1) % elem.length;
        return true;
    }

    public boolean deQueue() {
        if(isEmpty()) return false;
        front = (front + 1) % elem.length;
        return true;
    }

    /**
     * Front得到队头元素
     * @Front
     */
    public int Front() {
        // 这里 oj 上 判断 数组是否为空不用抛异常 同一返回-1即可
        if(isEmpty()) {
            return -1;
        }
        return elem[front];
    }

    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        int index = -1;
        
        if(rear == 0) {
            index = elem.length - 1;
        }else {
            index = rear - 1;
        }
        return elem[index];

    }

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

    public boolean isFull() {
        if((this.rear+1)%elem.length == front) {
            return true;
        }
        return false;
    }
}

方法二 使用 suedSize 来 解题

// 使用 usedSize
public class MyCircularQueue {
    public int[] elem;
    public int front;
    public int rear;
    int usedSize;
    public MyCircularQueue(int k) {
        this.elem = new int[k];
    }

    public boolean enQueue(int value) {
        if(isFull()) return false;

        this.elem[rear] = value;
        rear = (rear+1) % elem.length;
        usedSize++;
        return true;
    }
    这里每次添加 rear 都 会 向后 一步 入 先 0 下标 添加完 1 执行rear = (rear+1) % elem.length; 就会指向2 那么 查找尾节点,就可以 返回
        elem[rear - 1] , 但是 有一个特里 就是 当 rear 为 0 时 就需要 返回下标为 elem。length 的值 这里加个if 语句就ok

    public boolean deQueue() {
        if(isEmpty()) return false;
        front = (front + 1) % elem.length;
        usedSize--;
        return true;
    }

    public int Front() {
        if(isEmpty()) return -1;
        return elem[front];
    }

    // 获得队尾元素
    public int Rear() {
        if(isEmpty()) return -1;


        int index = -1;
        if(rear == 0) {
            index = elem.length - 1;
        }else {
            index = rear - 1;
        }
        return elem[index];
    }

    public boolean isEmpty() {
        return usedSize == 0;
    }

    public boolean isFull() {
        return usedSize == elem.length;

    }


}

在这里插入图片描述
最后 还有一种 通过 标志位 来判 满 的 可以 尝试一下 这就 不给 出答案 ,因为我没去写嘿嘿
在这里插入图片描述
完成了循环队列 ,差不多 用数组 来实现 队列 就是这样那么接下来 趁热打铁继续 冲 oj题

用队列实现栈

class MyStack {
    // 通过队列来实现栈
    //不为空的进 ,为空出
    Queue<Integer> que1 ;
    Queue<Integer> que2 ;
    public MyStack() {
        que1 = new LinkedList<>();
        que2 = new LinkedList<>();
    }

    public void push(int x) {
        if(!que1.isEmpty()) {
            que1.offer(x);
        }else if(!que2.isEmpty()) {
            que2.offer(x);
        }else {
            que1.offer(x);
        }
    }

    public int pop() {
        if(empty()) return -1;
       if(!que1.isEmpty()) {
         int  size = que1.size() - 1;
           for(int i = 0;i<size;i++) {
               int ret = que1.poll();
               que2.offer(ret);
           }
           return que1.poll();
       }else {
           int  size = que2.size() - 1;
           for(int i = 0;i<size;i++) {
               int ret = que2.poll();
               que1.offer(ret);
           }
           return que2.poll();
       }
    }

    public int top() {
        if(empty()) return -1;
        int ret = 0;
        if(!que1.isEmpty()) {
            int  size = que1.size();
            for(int i = 0;i<size;i++) {
               ret = que1.poll();
                que2.offer(ret);
            }
            return ret;
        }
        if(!que2.isEmpty()) {
            int  size = que2.size();
            for(int i = 0;i<size;i++) {
                ret = que2.poll();
                que1.offer(ret);
            }
            return ret;
        }
        return -1;
    }

    public boolean empty() {
        return que1.isEmpty()&&que2.isEmpty();
    }
}

在这里插入图片描述
队列实现了 栈 那么 用栈来实现 队列 不是一个道理 吗 同样使用

两个 栈 来实现 队列

我们先来了解一下逻辑
在这里插入图片描述
了解了 逻辑 那么直接上 oj 干就行了

用栈实现队列

class MyQueue {

      public Stack<Integer> stack1;
    //入队列的栈
    public Stack<Integer> stack2;
    //出队列的栈

    public MyQueue() {
    stack1 = new Stack<>();
    stack2 = new Stack<>();
    }

    public void push(int x) {
    stack1.push(x);
    }

    public int pop() {
        if(empty()) return -1;
        if(!stack2.empty()) {
           int ret =  stack2.pop();
            return ret;
        }else {
            int size = stack1.size();
            for(int i = 0;i<size;i++) {
                int ret = stack1.pop();
                stack2.push(ret);
            }
            return stack2.pop();
        }
    }

    
    public int peek() {
        if(empty()) return -1;
        if(!stack2.empty()) {
            int ret =  stack2.peek();
            return ret;
        }else {
            int size = stack1.size();
            for(int i = 0;i<size;i++) {
                int ret = stack1.pop();
                stack2.push(ret);
            }
            return stack2.peek();
        }
    }

    public boolean empty() {
        return stack1.empty() && stack2.empty();
    }
}

在这里插入图片描述
其实 队列 与 栈 的 要学习的 东西 不多 多的 事 如何 去使用他,向 栈 还有 单调栈 ,就是 栈顶 到 栈 低 成一个 单调递增或单调递减的趋势,我们 要知道 栈和队列的 特点 ,栈 : 先进 后出 队列 : 先进先出 ,在 去 使用栈和 队列 解决 些 练习题 就能很好的熟知他们。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值