【数据结构】队列的实现

1. 队列的概念

队列(Queue)是一种线性表,只允许在一段进行数据的插入,在另一端进行数据的删除操作。具有先进先出的特点,简称FIFO(First In First Out)。在队尾进行数据的插入,叫做入队;在队首进行数据的删除,叫做出队。在java中,Queue是一个接口,必须用它的实现类来实例化,底层用链表实现。

Queue的常用方法:

(1)入队列:offer(E e)

(2)出队列:  poll()

(3)获取队首元素: peek()

(4)获取队列中的有效元素个数: int size()

(5)判断队列是否为空: boolean isEmpty()

2. 队列的模拟实现

使用单链表模拟实现队列: 

 2.1 定义Queue接口

public interface Queue<E> {
    void offer(E element);
    E poll();
    E peek();
}

2.2 定义MyQueue类实现Queue接口

public class MyQueue<E> implements Queue<E> {
    //定义链表的头,尾
    private Node head;
    private Node tail;
    private int size;
    class Node{
        E val;
        Node next;
        public Node(E val) {
            this.val = val;
        }
    }
    /**
     * /入队列:链表的尾插
     */
    @Override
    public void offer(E element) {
        //产生新节点
        Node node = new Node(element);
        //如果链表为空,此时新节点就是头和尾
        if(head==null){
            head = tail = node;
        }
        //如果链表不为空,此时
        tail.next = node;
        tail = node;
        size++;
    }
    /**
     * /出队列:出的是链表中的头结点
     */
    @Override
    public E poll() {
        E val = head.val;
        head = head.next;
        size--;
        return val;
    }
    /**
     * 查看队首元素
     */
    @Override
    public E peek() {
        if (size == 0) {
            throw new NoSuchElementException("queue is empty!cannot peek");
        }
        return head.val;
    }
    /**
     * 重写ToString方法
     */
    @Override
    public String toString() {
        //遍历链表
        StringBuilder sb = new StringBuilder();
        sb.append("front[");
        for (Node x = head; x !=null; x=x.next) {
            sb.append(x.val);
            if(x.next != null){
                sb.append(", ");
            }
        }
        sb.append("]tail");
        return sb.toString();
    }
}

2.3 测试类 

public class MyQueueTest {
    public static void main(String[] args) {
        Queue<Integer> queue = new MyQueue<>();
        //入队列
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        queue.offer(4);
        queue.offer(5);
        System.out.println(queue);
        //查看队首元素
        System.out.println(queue.peek());
        //出队列
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue);
    }
}

测试结果:

3. 循环队列

除了我们上述使用到的普通队列之外,还有一种叫做循环队列,循环队列一般使用数组实现。 

 关键:循环队列怎么判断元素已满?

如果我们将循环队列展平就是这个样子:入队是从队尾也就是数组尾部插入,然后更新tail索引值;出队就是从队首出,然后更新head值。 

 我们判断循环队列最简单的做法是:

  • 多创建一个空间,让tail指向最后一个有效元素的下一个位置
  • 判断为空:head == tail;
  • 判断为满: ( tail+1) % arr.length == head;

注意:在循环队列中,我们不能将索引简单的++或者--,索引的计算采用的都是取模操作;

4. 队列与栈的相互实现

4.1 单队列实现栈

(1)主要思路:让新元素先入队列,然后将原先存储的旧元素依次出队列再重新入队列,此时的顺序就符合栈的出栈特点 。其中要注意的是:这个旧元素有几个就要出几次栈。所以用一个值记录queue队列中的值,之后的出栈次数也就是该值。

(2)代码实现:

    private Queue<Integer> queue = new LinkedList<>();
    public void push(int x) {
        // 记录队列中的元素个数
        int num = queue.size();
        // 新元素先入队列
        queue.offer(x);
        // 将旧元素依次入队再出队,新元素就在了队首
        for (int i = 0; i < num; i++) {
            queue.offer(queue.poll());
        }
    }

    public int pop() {
        return queue.poll();
    }

    public int top() {
        return queue.peek();
    }

    public boolean empty() {
        return queue.isEmpty();
    }

4.2 双队列实现栈

(1)主要思想:交换引用:创建两个队列q1和q2,让新元素入q2,q1保存所有的元素;如果q1为空,则直接交换两者的名字(也就是此时只有一个元素入队列);如果q1中不为空,则此时现将q1的所有元素弹出再入q2,再交换两者的名字。此时q1中的弹出顺序就与栈的顺序一致了。

(2)代码实现

    //创建两个队列
    //q1用来存储所有的元素值,q2作为辅助
    Queue<Integer> q1 = new LinkedList<>();
    Queue<Integer> q2 = new LinkedList<>();
    //入队列
    public void push(int x) {
        //让所有的新元素入q2
        q2.offer(x);
        //判断q1是否有元素:有元素则要弹出并添加到q2
        while (!q1.isEmpty()){
            q2.offer(q1.poll());
        }
        //交换两者的名字
        Queue<Integer> temp = q1;
        q1 = q2;
        q2 = temp;
    }
    //弹出
    public int pop() {
        return q1.poll();
    }
    //查看队首元素
    public int top() {
        return q1.peek();
    }
    //判断队列是否为空
    public boolean empty() {
        return q1.isEmpty();
    }

4.3 双栈实现队列

(1)主要思想:s1存放所有的元素,s2作为辅助。我们的目标是希望将新元素存在s1的栈底,如果s1中没有元素,那直接存;但是如果s1中有元素,就将s1中的所有元素全部弹出先存到s2中,再将新元素存入s1,此时新元素就处于s1的栈底。  然后,我们再将s2中的元素全部弹出压入s1中。即符合出队列的特点。

(2)代码实现

    //先创建两个栈:s1存储所有元素,s2出栈
    Stack<Integer> s1 = new Stack<>();
    Stack<Integer> s2 = new Stack<>();
    //入栈
    public void push(int x) {
        //如果s1中有元素,先弹出将栈底空出来
        while (!s1.isEmpty()){
            s2.push(s1.pop());
        }
        //将x添加到s1的栈底
        s1.push(x);
        //将s2的值依次弹出来压入到s1中
        while (!s2.isEmpty()){
            s1.push(s2.pop());
        }
    }

    public int pop() {
        return s1.pop();
    }

    public int peek() {
        return s1.peek();
    }

    public boolean empty() {
        return s1.isEmpty();
    }

关于栈的简单学习就到这里,明天也要加油呀~z

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值