双端队列(Double-ended queue,简称Deque)是一种数据结构,允许在队列的两端进行插入和删除操作。它可以被看作是一个允许元素插入和删除的线性表,但与普通队列不同,双端队列支持在队列的头部和尾部进行插入和删除操作,因此具有更加灵活的使用方式。
双端队列的特点包括:
-
前端插入和删除: 可以在双端队列的前端执行插入和删除操作。这使得双端队列可以像栈一样用于一些问题,比如实现逆序计算或其他需要后进先出(LIFO)操作的场景。
-
后端插入和删除: 同样可以在双端队列的后端执行插入和删除操作。这使得双端队列保持了队列的性质,即先进先出(FIFO)。
双端队列常见的操作包括:
- 在前端插入元素(push_front):将一个新元素插入到双端队列的头部。
- 在前端删除元素(pop_front):删除并返回双端队列的头部元素。
- 在后端插入元素(push_back):将一个新元素插入到双端队列的尾部。
- 在后端删除元素(pop_back):删除并返回双端队列的尾部元素。
- 获取前端元素(front):返回双端队列的头部元素,但不进行删除。
- 获取后端元素(back):返回双端队列的尾部元素,但不进行删除。
双端队列在很多算法和数据结构问题中都有广泛的应用,因为它的灵活性使得它可以同时满足栈和队列的需求。在编程中,许多编程语言提供了标准库中的双端队列实现,以方便开发者使用。
下面分别是用链表和数组实现双端队列:
先定义一个接口:
public interface Deque<E>
{
/**
* 从头部插入
* @param e 要插入的元素
* @return 是否插入成功
*/
boolean offerFirst(E e);
/**
* 从尾部插入
* @param e 要插入的元素
* @return 是否插入成功
*/
boolean offerLast(E e);
/**
* 从头部删除
* @return 被删除的元素
*/
E pollFirst();
/**
* 从尾部删除
* @return 被删除的元素
*/
E pollLast();
/**
* 从头部查看
* @return 被查看的元素
*/
E peekFirst();
/**
* 从尾部查看
* @return 被查看的元素
*/
E peekLast();
/**
* 队列是否为空
* @return 是否为空
*/
boolean isEmpty();
/**
* 队列是否已满
* @return 是否已满
*/
boolean isFull();
}
链表实现:
public class LinkedListDeque<E> implements Deque<E>, Iterable<E>
{
/**
* 双向链表节点类
* @param <E> 节点存储的元素类型
*/
static class Node<E>
{
Node<E> prev;//前驱,表示当前节点的前一个节点
E value;//值
Node<E> next;//后继,当前节点的后一个节点
Node(final Node<E> prev,final E value,final Node<E> next)
{
this.prev = prev;
this.value = value;
this.next = next;
}
}
int capacity = Integer.MAX_VALUE;//容量
int size;//当前元素个数
Node<E> sentinel = new Node<>(null,null,null);//哨兵节点
/**
* 构造函数
* @param capacity 容量
*/
public LinkedListDeque(final int capacity)
{
this.capacity = capacity;
sentinel.prev = sentinel;
sentinel.next = sentinel;
}
/**
* 构造函数
*/
public LinkedListDeque()
{
sentinel.prev = sentinel;
sentinel.next = sentinel;
}
/**
* 迭代器
* @return 迭代器
*/
@Override
public Iterator<E> iterator()
{
return new Iterator<E>() {
Node<E> p = sentinel.next;//当前节点
/**
* 是否有下一个元素
* @return 是否有下一个元素,p不等于哨兵节点时有下一个元素
*/
@Override
public boolean hasNext()
{
return p != sentinel;
}
/**
* 获取当前元素的值,并将p指向下一个元素
* @return 当前元素的值
*/
@Override
public E next()
{
E value = p.value;
p = p.next;
return value;
}
};
}
/**
* 从头部插入
* @param e 要插入的元素
* @return 是否插入成功
*/
@Override
public boolean offerFirst(final 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 是否插入成功
*/
@Override
public boolean offerLast(final 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 被删除的元素
*/
@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 被删除的元素
*/
@Override
public E pollLast()
{
if (isEmpty())
{
return null;
}
Node<E> removed = sentinel.prev;
Node<E> a = removed.prev;
Node<E> b = sentinel;
a.next = b;
b.prev = a;
size --;
return removed.value;
}
/**
* 从头部查看
* @return 被查看的元素
*/
@Override
public E peekFirst()
{
if (isEmpty())
{
return null;
}
return sentinel.next.value;
}
/**
* 从尾部查看
* @return 被查看的元素
*/
@Override
public E peekLast()
{
if (isEmpty())
{
return null;
}
return sentinel.prev.value;
}
/**
* 队列是否为空
* @return 是否为空
*/
@Override
public boolean isEmpty()
{
return size == 0;
}
/**
* 队列是否已满
* @return 是否已满
*/
@Override
public boolean isFull()
{
return size == capacity;
}
/**
* 重写toString方法
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("[");
int i = 0;
for (E e : this)
{
i ++;
if (i == size)
{
sb.append(e);
}
else
{
sb.append(e);
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
}
数组实现:
public class ArrayDeque1<E> implements Iterable<E>, Deque<E>
{
/**
* 用于给数组下标加1,如果到达数组末尾则返回0
* @param i 当前下标
* @param length 数组长度
* return i+1 下标加1
*/
static int inc(int i,int length)
{
if (i + 1 >= length)
{
return 0;
}
return i + 1;
}
/**
* 用于给数组下标减1,如果到达数组头部则返回数组末尾下标
* @param i 当前下标
* @param length 数组长度
* return i-1 下标减1
*/
static int dec(int i,int length)
{
if (i - 1 < 0)
{
return length - 1;
}
return i - 1;
}
E[] array;//数组
int head;//头指针,代表该队列的第一个元素
int tail;//尾指针,始终置空,不存储元素,只是用来标记下一次尾部插入时,元素插入的位置
/**
* 构造函数
* @param capacity 容量
*/
public ArrayDeque1(int capacity)
{
array = (E[]) new Object[capacity + 1];
}
/**
* 实现迭代器
*/
@Override
public Iterator<E> iterator()
{
return new Iterator<E>() {
int p = head;//当前指针
/**
* 是否有下一个元素
* @return 是否有下一个元素,p不等于尾指针时有下一个元素
*/
@Override
public boolean hasNext() {
return p != tail;
}
/**
* 获取当前元素的值,并将p指向下一个元素
* @return 当前元素的值
*/
@Override
public E next() {
E value = array[p];
p = inc(p,array.length);
return value;
}
};
}
/**
* 从头部插入
* @param e 要插入的元素
* @return 是否插入成功
*/
@Override
public boolean offerFirst(E e)
{
if (isFull())
{
return false;
}
head = dec(head,array.length);//头指针向前一步
array[head] = e;//在当前头指针处插入元素
return true;
}
/**
* 从尾部插入
* @param e 要插入的元素
* @return 是否插入成功
*/
@Override
public boolean offerLast(E e)
{
if (isFull())
{
return false;
}
array[tail] = e;//在当前尾指针处插入元素
tail = inc(tail,array.length);//尾指针向后一步
return true;
}
/**
* 从头部删除
* @return 被删除的元素
*/
@Override
public E pollFirst()
{
if (isEmpty())
{
return null;
}
E e = array[head];
array[head] = null;//清理内存,help GC
head = inc(head,array.length);
return e;
}
@Override
public E pollLast()
{
if (isEmpty())
{
return null;
}
tail = dec(tail,array.length);
E e = array[tail];
array[tail] = null;//help GC
return e;
}
/**
* 从头部查看
* @return 被查看的元素
*/
@Override
public E peekFirst()
{
if (isEmpty())
{
return null;
}
return array[head];
}
/**
* 从尾部查看
* @return 被查看的元素
*/
@Override
public E peekLast()
{
if (isEmpty())
{
return null;
}
return array[dec(tail,array.length)];
}
/**
* 队列是否为空
* @return 是否为空
*/
@Override
public boolean isEmpty()
{
return head == tail;
}
/**
* 队列是否已满,两种情况:
* 1.如果尾指针在头指针后面,则当尾指针减头指针等于数组长度减1时,队列已满
* 2.如果尾指针在头指针前面,则当头指针减尾指针等于1时,也就是头指针紧挨着尾指针,队列已满
* @return 是否已满
*/
@Override
public boolean isFull()
{
if (tail > head)
{
return tail - head == array.length - 1;
}
else if (tail < head)
{
return head - tail == 1;
}
else
{
return false;
}
}
}