【问题标题】:Best implementation of Java Queue?Java队列的最佳实现?
【发布时间】:2012-06-24 08:51:54
【问题描述】:

我正在(在 Java 中)研究一种递归图像处理算法,该算法从中心点向外递归遍历图像的像素。

不幸的是,这会导致堆栈溢出。所以我决定改用基于队列的算法。

现在,这一切都很好——但考虑到它的队列将在很短的时间内分析数千个像素,同时不断弹出和推送,而不保持可预测的状态(它可能介于长度为 100 和 20000),队列实现需要具有显着的快速弹出和推送能力。

链表看起来很有吸引力,因为它能够将元素推送到自身而不重新排列列表中的任何其他内容,但为了使其足够快,它需要轻松访问其头部和尾部(或倒数第二个节点,如果它不是双重链接的)。可悲的是,我找不到与 Java 中链表的底层实现相关的任何信息,所以很难说链表是否真的是要走的路……

这让我想到了我的问题。对于我打算做的事情,Java 中 Queue 接口的最佳实现是什么? (除了队列的头部和尾部之外,我不希望编辑甚至访问任何东西——我不希望进行任何类型的重新排列或任何事情。另一方面,我确实打算做很多推送和弹出,队列的大小会发生相当大的变化,因此预分配效率低下)

【问题讨论】:

  • 也许你需要退后一步,想想是否有比将数千个单独的像素一个一个地推入数据结构更好的方法(如果这确实是你正在做的)。
  • 这是一种blob检测算法,其思想是从blob上的一点开始,向外遍历到blob的边缘。我不相信有任何其他(简单)方法可以做到这一点。此外,队列只存储兴趣点——它实际上并没有将像素保留在队列中,队列主要只是用作跟踪它所在位置的一种方式。类似于很多寻路算法

标签: java queue


【解决方案1】:

用途:

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

您可以使用.offer(E e) 将一个元素附加到队列的末尾,并使用.poll() 出列并检索队列的头部(第一个元素)。

Java 定义了接口QueueLinkedList 提供了一个实现。

它还维护对 Head 和 Tail 元素的引用,您可以分别通过 .getFirst().getLast() 获得。


感谢 @Snicolas 提出的队列接口建议

【讨论】:

  • 我更喜欢使用由linkedList实现的Queue方法:添加到入队和轮询到出队。
  • 我对这个问题的回答谈到了这一点,请参见下文。不推荐使用LinkedList,而是将其实例化为Queue接口……见下文。
【解决方案2】:

如果你使用 LinkedList 要小心。如果你这样使用它:

LinkedList<String> queue = new LinkedList<String>();

那么你可以违反队列定义,因为可以删除除first之外的其他元素(LinkedList中有这样的方法)。

但是如果你这样使用它:

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

应该没问题,因为这是提醒用户,插入应该只发生在后面,而删除应该只发生在前面。

您可以通过将 LinkedList 类扩展为 PureQueue 类来克服 Queue 接口的缺陷实现,该类会引发任何违规方法的 UnsupportedOperationException。或者,您可以通过仅使用一个类型为 LinkedList 对象、列表的字段创建 PureQueue 来采用聚合方法,唯一的方法将是默认构造函数、复制构造函数、isEmpty()size()add(E element)、@987654326 @ 和 element()。所有这些方法都应该是单行的,例如:

/**
* Retrieves and removes the head of this queue.
* The worstTime(n) is constant and averageTime(n) is constant.
*
* @return the head of this queue.
* @throws NoSuchElementException if this queue is empty.
*/
public E remove()
{
    return list.removeFirst();
} // method remove()

【讨论】:

    【解决方案3】:

    查看Deque 接口,它提供了两端的插入/删除。 LinkedList 实现了该接口(如上所述),但对于您的使用,ArrayDeque 可能会更好——您不会为每个节点产生常量对象分配的成本。再说一次,您使用哪种实现可能并不重要。

    正常多态性的优点开始发挥作用:针对 Deque 接口而不是它的任何特定实现进行编写的美妙之处在于,您可以非常轻松地切换实现以测试哪个实现最好。只需更改其中包含new 的行,其余代码保持不变。

    【讨论】:

      【解决方案4】:

      在 Java 中实现 Stack 和 Queue 时,最好使用 ArrayDeque 而不是 LinkedList。当用作堆栈时,ArrayDeque 可能比 Stack 接口(而 Stack 是线程安全的)更快,而当用作队列时,它可能比 LinkedList 更快。看看这个链接Use ArrayDeque instead of LinkedList or Stack

      【讨论】:

        【解决方案5】:

        如果您知道队列中可能的项目数量的上限,循环缓冲区比 LinkedList 更快,因为 LinkedList 为队列中的每个项目创建一个对象(链接)。

        【讨论】:

          【解决方案6】:

          我认为您可以通过简单的类似实现来解决问题

          package DataStructures;
          
          public class Queue<T> {
          
             private Node<T> root;
          
             public Queue(T value) {
                root = new Node<T>(value);
             }
          
             public void enque(T value) {
                Node<T> node = new Node<T>(value);
                node.setNext(root);
                root = node;
             }
          
             public Node<T> deque() {
          
                Node<T> node = root;
                Node<T> previous = null;
          
                while(node.next() != null) {
                   previous = node;
                   node = node.next();
                }
                node = previous.next();
                previous.setNext(null);
                return node;
             }
          
             static class Node<T> {
          
                private T value;
                private Node<T> next;
          
                public Node (T value) {
                   this.value = value;
                }
          
                public void setValue(T value) {
                   this.value = value;
                }
          
                public T getValue() {
                   return value;
                }
          
                public void setNext(Node<T> next) {
                   this.next = next;
                }
          
                public Node<T> next() {
                   return next;
                }
             }
          }
          

          【讨论】:

          • Deque 操作在最坏的情况下需要 O(n) 时间,并且队列数据结构应该花费恒定的时间来插入和删除。这是一个简单的队列实现,请避免它。
          • 为什么在你的双端队列方法中返回 Node&lt;T&gt; 而不是 T
          • 如果实现java.util.Queue接口就好了。
          • 只是一个小修正,出队方法应该返回一个'T'。 Node 是 Queue 的内部实现,不应在 Queue 实现之外可用。
          【解决方案7】:

          但是,如果您仍想使用递归算法,您可以将其更改为 "tail-recursive",这可能在 JVM 中进行了优化以避免堆栈溢出。

          【讨论】:

          • 是的,你是对的,因为尾递归将克服堆栈溢出错误。我知道像 Scala 这样的语言支持尾递归来进行优化。但是我怀疑 Java 是否不支持 Tail 递归。如果我错了,请纠正我。
          • JVM 可以通过特殊的操作码支持更通用的东西,Java 编译器可以在非常有限的条件下直接实现它。有一个愿望清单项目,但它过去从未真正实现过,尽管它可能会在未来实现。 - 此外,您不应该建议依赖“可能存在”的优化,特别是如果优化的存在/不存在将使程序工作相应。崩溃。
          【解决方案8】:

          O(1) 访问第一个和最后一个节点。

          class Queue {
          
              private Node head;
              private Node end;
          
              public void enqueue(Integer data){
          
                  Node node = new Node(data);
                  if(this.end == null){
                      this.head = node;
                      this.end = this.head;
                  }
                  else {
                      this.end.setNext(node);
                      this.end = node;
                  }
              }
          
              public void dequeue (){
          
                  if (head == end){
                      end = null;
                  }
          
                  head = this.head.getNext();
              }
          
          
              @Override
              public String toString() {
                  return head.getData().toString();
              }
          
              public String deepToString() {
          
                  StringBuilder res = new StringBuilder();
                  res.append(head.getData());
          
                  Node cur = head;
                  while (null != (cur = cur.getNext())){
                      res.append(" ");
                      res.append(cur.getData());
          
                  }
                  return res.toString();
              }
          }
          
          class Node {
          
              private Node next;
              private Integer data;
          
          
              Node(Integer i){
                  data = i;
              }
          
              public Integer getData() {
                  return data;
              }
          
              public Node getNext() {
                  return next;
              }
          
              public void setNext(Node next) {
                  this.next = next;
              }
          }
          

          【讨论】:

            【解决方案9】:

            这是带有 IteratorIterable 接口的 Queue 实现

            队列大小会随着填满而增加

            队列接口

            package com.practice.ds.queue;
            
            import com.practice.ds.queue.exception.QueueException;
            
            public interface QueueInterface<T> {
            
                public boolean empty();
            
                public void enqueue(T item);
            
                public void dequeue() throws QueueException;
            
                public T front() throws QueueException;
            
                public void clear();
            }
            

            自定义异常类

            package com.practice.ds.queue.exception;
            
            public class QueueException extends Exception {
            
                private static final long serialVersionUID = -884127093599336807L;
            
                public QueueException() {
                    super();
                }
            
                public QueueException(String message) {
                    super(message);
                }
            
                public QueueException(Throwable e) {
                    super(e);
                }
            
                public QueueException(String message, Throwable e) {
                    super(message, e);
                }
            }
            

            队列的实现

            package com.practice.ds.queue;
            
            import java.util.Iterator;
            
            import com.practice.ds.queue.exception.QueueException;
            
            public class Queue<T> implements QueueInterface<T>, Iterable<T> {
            
                private static final int DEFAULT_CAPACITY = 10;
                private int current = 0;
                private int rear = 0;
                private T[] queueArray = null;
                private int capacity = 0;
            
                @SuppressWarnings("unchecked")
                public Queue() {
                    capacity = DEFAULT_CAPACITY;
                    queueArray = (T[]) new Object[DEFAULT_CAPACITY];
                    rear = 0;
                    current = 0;
                }
            
                @Override
                public boolean empty() {
                    return capacity == current;
                }
            
                @Override
                public void enqueue(T item) {
                    if(full())
                        ensureCapacity();
                    queueArray[current] = item;
                    current++;
                }
            
                @Override
                public void dequeue() throws QueueException {
                    T dequeuedItem = front();
                    rear++;
                    System.out.println("Dequed Item is " + dequeuedItem);
                }
            
                @Override
                public T front() throws QueueException {
                    return queueArray[rear];
                }
            
                @Override
                public void clear() {
                    for (int i = 0; i < capacity; i++)
                        queueArray[i] = null;
                    current = 0;
                    rear = 0;
                }
            
                @SuppressWarnings("unchecked")
                private void ensureCapacity() {
                    if (rear != 0) {
                        copyElements(queueArray);
                    } else {
                        capacity *= 2;
                        T[] tempQueueArray = (T[]) new Object[capacity];
                        copyElements(tempQueueArray);
                    }
                    current -= rear;
                    rear = 0;
                }
            
                private void copyElements(T[] array) {
                    for (int i = rear; i < current; i++)
                        array[i - rear] = queueArray[i];
                    queueArray = array;
                }
            
                @Override
                public Iterator<T> iterator() {
                    return new QueueItearator<T>();
                }
            
                public boolean full() {
                    return current == capacity;
                }
            
                private class QueueItearator<T> implements Iterator<T> {
            
                    private int index = rear;
            
                    @Override
                    public boolean hasNext() {
                        return index < current;
                    }
            
                    @SuppressWarnings("unchecked")
                    @Override
                    public T next() {
                        return (T) queueArray[index++];
                    }
                }
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-12-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-02
              • 2016-08-18
              相关资源
              最近更新 更多