java-数据结构与算法-02-数据结构-06-双端队列

1. 概述

双端队列、队列、栈对比

在这里插入图片描述

注1:

  • Java 中 LinkedList 即为典型双端队列实现,不过它同时实现了 Queue 接口,也提供了栈的 push pop 等方法

注2:

  • 不同语言,操作双端队列的方法命名有所不同,参见下表

在这里插入图片描述
接口定义

/**
 * 双端队列接口
 * 双端队列是一种可以在其两端进行添加和移除元素的线性数据结构。
 * 该接口定义了在队列的首端和尾端进行操作的方法。
 * @param <E> 队列中元素的类型
 */
/**
 * 双端队列接口
 * @param <E> 队列中元素类型
 */
public interface Deque<E> {

    /**
     * 在队列的首端添加一个元素。
     * @param e 要添加到队列首端的元素
     * @return 如果成功添加元素,则返回true;否则返回false
     */
    boolean offerFirst(E e);

    /**
     * 在队列的尾端添加一个元素。
     * @param e 要添加到队列尾端的元素
     * @return 如果成功添加元素,则返回true;否则返回false
     */
    boolean offerLast(E e);

    /**
     * 从队列的首端移除一个元素。
     * @return 队列首端的元素;如果队列为空,则返回null
     */
    E pollFirst();

    /**
     * 从队列的尾端移除一个元素。
     * @return 队列尾端的元素;如果队列为空,则返回null
     */
    E pollLast();

    /**
     * 获取队列的首端元素,但不移除它。
     * @return 队列首端的元素;如果队列为空,则返回null
     */
    E peekFirst();

    /**
     * 获取队列的尾端元素,但不移除它。
     * @return 队列尾端的元素;如果队列为空,则返回null
     */
    E peekLast();

    /**
     * 检查队列是否为空。
     * @return 如果队列为空,则返回true;否则返回false
     */
    boolean isEmpty();

    /**
     * 检查队列是否已满。
     * 注意:具体实现可能需要定义队列的容量限制。如果没有预定义的容量限制,则该方法总是返回false。
     * @return 如果队列已满,则返回true;否则返回false
     */
    boolean isFull();
}

2. 链表实现

/**
 * 双向队列的链表实现。
 * 基于双向环形链表实现的双端队列,支持在队列的首尾位置进行添加和移除元素的操作。
 * 
 * @param <E> 队列中元素的类型。
 */
package com.itheima.datastructure.deque;

import java.util.Iterator;

/**
 * 双向队列的链表实现类。
 */
public class LinkedListDeque<E> implements Deque<E>, Iterable<E> {


    /**
     * 队列中元素的节点类。
     */
    static class Node<E> {
        Node<E> prev;
        E value;
        Node<E> next;

        public Node(Node<E> prev, E value, Node<E> next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    int capacity; // 队列的容量
    int size; // 队列中当前元素的数量
    Node<E> sentinel = new Node<>(null, null, null); // 队列的哨兵节点,用于简化链表操作

    /**
     * 构造一个具有指定容量的双向队列。
     * 
     * @param capacity 队列的容量。
     */
    public LinkedListDeque(int capacity) {
        this.capacity = capacity;
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }
}
    /**
     * 在队列首部添加一个元素。
     * 
     * @param e 要添加的元素。
     * @return 如果队列未满,则添加成功并返回true;否则,添加失败并返回false。
     */
    @Override
    public boolean offerFirst(E e) {
        if (isFull()) {
            return false;
        }
        Node<E> a = sentinel;
        Node<E> b = sentinel.next;
        Node<E> added = new Node<>(a, e, b);
        a.next = added;
        b.prev = added;
        size++;
        return true;
    }

    /**
     * 在队列尾部添加一个元素。
     * 
     * @param e 要添加的元素。
     * @return 如果队列未满,则添加成功并返回true;否则,添加失败并返回false。
     */
    @Override
    public boolean offerLast(E e) {
        if (isFull()) {
            return false;
        }
        Node<E> a = sentinel.prev;
        Node<E> b = sentinel;
        Node<E> added = new Node<>(a, e, b);
        a.next = added;
        b.prev = added;
        size++;
        return true;
    }

    /**
     * 从队列首部移除一个元素。
     * 
     * @return 如果队列不为空,则移除队列首部的元素并返回;否则,返回null。
     */
    @Override
    public E pollFirst() {
        if (isEmpty()) {
            return null;
        }
        Node<E> a = sentinel;
        Node<E> removed = sentinel.next;
        Node<E> b = removed.next;
        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

    /**
     * 从队列尾部移除一个元素。
     * 
     * @return 如果队列不为空,则移除队列尾部的元素并返回;否则,返回null。
     */
    @Override
    public E pollLast() {
        if (isEmpty()) {
            return null;
        }
        Node<E> b = sentinel;
        Node<E> removed = sentinel.prev;
        Node<E> a = removed.prev;
        a.next = b;
        b.prev = a;
        size--;
        return removed.value;
    }

    /**
     * 获取队列首部的元素,但不移除。
     * 
     * @return 如果队列不为空,则返回队列首部的元素;否则,返回null。
     */
    @Override
    public E peekFirst() {
        if (isEmpty()) {
            return null;
        }
        return sentinel.next.value;
    }

    /**
     * 获取队列尾部的元素,但不移除。
     * 
     * @return 如果队列不为空,则返回队列尾部的元素;否则,返回null。
     */
    @Override
    public E peekLast() {
        if (isEmpty()) {
            return null;
        }
        return sentinel.prev.value;
    }

    /**
     * 检查队列是否为空。
     * 
     * @return 如果队列为空,则返回true;否则,返回false。
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 检查队列是否已满。
     * 
     * @return 如果队列已满,则返回true;否则,返回false。
     */
    @Override
    public boolean isFull() {
        return size == capacity;
    }

    /**
     * 返回队列的迭代器,用于遍历队列中的元素。
     * 
     * @return 队列的迭代器。
     */
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            Node<E> p = sentinel.next;

            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public E next() {
                E value = p.value;
                p = p.next;
                return value;
            }
        };
    }


3. 数组实现

package com.itheima.datastructure.deque;

import java.util.Iterator;

/**
 * 循环数组双端队列的实现。
 * <p>
 * 特点:
 * - 使用循环数组为基础进行实现。
 * - 当尾指针追赶上头指针时,队列被认为是满的。
 * - 头指针和尾指针的移动通过取模运算实现循环。
 *
 * @param <E> 队列中元素的类型。
 */
public class ArrayDeque1<E> implements Deque<E>, Iterable<E> {

    /**
     * 计算头指针的下一个位置。
     *
     * @param i 当前头指针的位置。
     * @param length 数组的长度。
     * @return 头指针的下一个位置。
     */
    static int inc(int i, int length) {
        if (i + 1 >= length) {
            return 0;
        }
        return i + 1;
    }

    /**
     * 计算尾指针的上一个位置。
     *
     * @param i 当前尾指针的位置。
     * @param length 数组的长度。
     * @return 尾指针的上一个位置。
     */
    static int dec(int i, int length) {
        if (i - 1 < 0) {
            return length - 1;
        }
        return i - 1;
    }

    /**
     * 在队列首部添加一个元素。
     *
     * @param e 要添加的元素。
     * @return 如果队列未满,则返回true;否则返回false。
     */
    @Override
    public boolean offerFirst(E e) {
        if (isFull()) {
            return false;
        }
        head = dec(head, array.length);
        array[head] = e;
        return true;
    }

    /**
     * 在队列尾部添加一个元素。
     *
     * @param e 要添加的元素。
     * @return 如果队列未满,则返回true;否则返回false。
     */
    @Override
    public boolean offerLast(E e) {
        if (isFull()) {
            return false;
        }
        array[tail] = e;
        tail = inc(tail, array.length);
        return true;
    }

    /**
     * 从队列首部移除并返回一个元素。
     *
     * @return 队列首部的元素,如果队列为空,则返回null。
     */
    @Override
    public E pollFirst() {
        if (isEmpty()) {
            return null;
        }
        E e = array[head];
        array[head] = null; // 帮助垃圾回收
        head = inc(head, array.length);
        return e;
    }

    /**
     * 从队列尾部移除并返回一个元素。
     *
     * @return 队列尾部的元素,如果队列为空,则返回null。
     */
    @Override
    public E pollLast() {
        if (isEmpty()) {
            return null;
        }
        tail = dec(tail, array.length);
        E e = array[tail];
        array[tail] = null; // 帮助垃圾回收
        return e;
    }

    /**
     * 返回队列首部的元素,但不移除。
     *
     * @return 队列首部的元素,如果队列为空,则返回null。
     */
    @Override
    public E peekFirst() {
        if (isEmpty()) {
            return null;
        }
        return array[head];
    }

    /**
     * 返回队列尾部的元素,但不移除。
     *
     * @return 队列尾部的元素,如果队列为空,则返回null。
     */
    @Override
    public E peekLast() {
        if (isEmpty()) {
            return null;
        }
        return array[dec(tail, array.length)];
    }

    /**
     * 检查队列是否为空。
     *
     * @return 如果队列为空,则返回true;否则返回false。
     */
    @Override
    public boolean isEmpty() {
        return head == tail;
    }

    /**
     * 检查队列是否已满。
     *
     * @return 如果队列已满,则返回true;否则返回false。
     */
    @Override
    public boolean isFull() {
        if (tail > head) {
            return tail - head == array.length - 1;
        } else if (tail < head) {
            return head - tail == 1;
        } else {
            return false;
        }
    }

    /**
     * 获取队列的迭代器,用于遍历队列元素。
     *
     * @return 队列元素的迭代器。
     */
    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            int p = head;

            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public E next() {
                E e = array[p];
                p = inc(p, array.length);
                return e;
            }
        };
    }

    E[] array;
    int head;
    int tail;

    /**
     * 构造函数,初始化队列。
     *
     * @param capacity 队列的容量。
     */
    @SuppressWarnings("all")
    public ArrayDeque1(int capacity) {
        array = (E[]) new Object[capacity + 1];
    }
}

数组实现中,如果存储的是基本类型,那么无需考虑内存释放,例如

在这里插入图片描述
但如果存储的是引用类型,应当设置该位置的引用为 null,以便内存及时释放
在这里插入图片描述

习题

E01. 二叉树 Z 字层序遍历-Leetcode 103

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
示例1:
在这里插入图片描述
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[20,9],[15,7]]
示例 2:

输入:root = [1]
输出:[[1]]
示例 3:

输入:root = []
输出:[]

在这里插入图片描述

提示:

树中节点数目在范围 [0, 2000] 内
-100 <= Node.val <= 100

题解:
利用双端队列的两端皆可添加元素的特性,设打印列表(双端队列) tmp ,并规定:

  • 奇数层 则添加至 tmp 尾部 ,
  • 偶数层 则添加至 tmp 头部 。

算法流程:

  1. 特例处理: 当树的根节点为空,则直接返回空列表 [] 。
  2. 初始化: 打印结果空列表 res ,包含根节点的双端队列 deque 。
  3. BFS 循环: 当 deque 为空时跳出。
    • 新建列表 tmp ,用于临时存储当前层打印结果。
    • 当前层打印循环: 循环次数为当前层节点数(即 deque 长度)。
      • 出队: 队首元素出队,记为 node。
      • 打印: 若为奇数层,将 node.val 添加至 tmp 尾部;否则,添加至 tmp 头部。
      • 添加子节点: 若 node 的左(右)子节点不为空,则加入 deque 。
    • 将当前层结果 tmp 转化为 list 并添加入 res 。
  4. 返回值: 返回打印结果列表 res 即可。

在这里插入图片描述

/**
 * 对二叉树进行锯齿形层次遍历,并返回结果列表。
 * 锯齿形层次遍历的特点是,每个层级的元素在结果列表中交替地从前向后和从后向前排列。
 * 
 * @param root 二叉树的根节点
 * @return 包含锯齿形层次遍历结果的列表
 */
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    /* 使用队列来辅助进行层次遍历 */
    Queue<TreeNode> queue = new LinkedList<>();
    /* 用于存储最终的遍历结果 */
    List<List<Integer>> res = new ArrayList<>();
    /* 如果根节点不为空,则将其加入队列 */
    if (root != null) queue.add(root);
    /* 当队列不为空时,继续遍历 */
    while (!queue.isEmpty()) {
        /* 使用双端队列来存储当前层级的节点值,以便实现锯齿形排列 */
        LinkedList<Integer> tmp = new LinkedList<>();
        /* 遍历当前层级的所有节点 */
        for(int i = queue.size(); i > 0; i--) {
            /* 从队列中取出一个节点 */
            TreeNode node = queue.poll();
            /* 根据当前层级的索引,决定节点值是添加到队列的前端还是后端 */
            if (res.size() % 2 == 0) tmp.addLast(node.val);
            else tmp.addFirst(node.val);
            /* 如果节点有左子节点,则将左子节点加入队列 */
            if (node.left != null) queue.add(node.left);
            /* 如果节点有右子节点,则将右子节点加入队列 */
            if (node.right != null) queue.add(node.right);
        }
        /* 将当前层级的节点值列表加入到结果中 */
        res.add(tmp);
    }
    /* 返回最终的遍历结果 */
    return res;
}

Ex1. 设计双端队列-Leetcode 641

实现 MyCircularDeque 类:

MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
int getFront()):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
int getRear() :获得双端队列的最后一个元素。如果双端队列为空,返回 -1 。
boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false 。
boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。

示例 1:

输入
[“MyCircularDeque”, “insertLast”, “insertLast”, “insertFront”, “insertFront”, “getRear”, “isFull”, “deleteLast”, “insertFront”, “getFront”]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
输出
[null, true, true, true, false, 2, true, true, true, 4]

解释
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2 circularDeque.isFull();
// 返回 true circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4

提示:

1 <= k <= 1000
0 <= value <= 1000
insertFront, insertLast,deleteFront, deleteLast, getFront, getRear, isEmpty, isFull 调用次数不大于
2000 次

与例题也是差别不大,略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值