Je travaille (en Java) sur un algorithme de traitement d'image récursif qui traverse récursivement les pixels de l'image, vers l'extérieur à partir d'un point central.
Malheureusement, cela provoque un débordement de pile. J'ai donc décidé de passer à un algorithme basé sur la file d'attente.
Maintenant, tout cela est très bien et dandy- mais compte tenu du fait que sa file d'attente analysera des milliers de pixels dans un très court laps de temps, tout en sautant et en poussant constamment, SANS maintenir un état prévisible (il pourrait être n'importe où entre la longueur 100, et 20000), la mise en œuvre de la file d'attente doit avoir des capacités de poussée et de poussée significativement rapides.
Une liste chaînée semble attrayante en raison de sa capacité à pousser des éléments sur elle-même sans réorganiser quoi que ce soit d'autre dans la liste, mais pour qu'elle soit suffisamment rapide, elle aurait besoin d'un accès facile à la fois à sa tête ET à sa queue (ou seconde à -dernier noeud s'il n'était pas doublement lié). Malheureusement, je ne trouve aucune information liée à l'implémentation sous-jacente des listes chaînées en Java, il est donc difficile de dire si une liste chaînée est vraiment la voie à suivre ...
Cela m'amène à ma question. Quelle serait la meilleure implémentation de l'interface Queue dans Java pour ce que j'ai l'intention de faire? (Je ne souhaite pas modifier ni même accéder à autre chose que la tête et la queue de la file d'attente - Je ne souhaite pas faire de réarrangement, ni quoi que ce soit. D'un autre côté, j'ai l'intention de faire beaucoup de poussées et de sauts, et la file d'attente changera un peu de taille, donc la préallocation serait inefficace)
LinkedList semble être un chemin à parcourir, LinkedList est une liste doublement liée, ce qui est bon pour une structure de données de file d'attente (FIFO).
Il conserve des références aux éléments Head et Tail, que vous pouvez obtenir respectivement par .getFirst()
et .getLast()
.
Vous pouvez également utiliser .Push(E e)
pour ajouter un élément à la fin de la file d'attente et .pop()
pour retirer la file d'attente et récupérer le dernier élément de la file d'attente.
Si vous utilisez LinkedList, soyez prudent. Si vous l'utilisez comme ceci:
LinkedList<String> queue = new LinkedList<String>();
vous pouvez alors violer la définition de la file d'attente, car il est possible de supprimer d'autres éléments que le premier (il existe de telles méthodes dans LinkedList).
Mais si vous l'utilisez comme ceci:
Queue<String> queue = new LinkedList<String>();
cela devrait être correct, car cela avertit les utilisateurs que les insertions ne doivent se produire qu'à l'arrière et les suppressions uniquement à l'avant.
Vous pouvez surmonter l'implémentation défectueuse de l'interface Queue en étendant la classe LinkedList à une classe PureQueue qui lève UnsupportedOperationException de l'une des méthodes incriminées. Ou vous pouvez adopter une approche avec l'agrégation en créant PureQueue avec un seul champ qui est de type objet LinkedList, list et les seules méthodes seront un constructeur par défaut, un constructeur de copie, isEmpty()
, size()
, add(E element)
, remove()
et element()
. Toutes ces méthodes doivent être unifilaires, comme par exemple:
/**
* 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()
Consultez l'interface Deque , qui prévoit des insertions/suppressions aux deux extrémités. LinkedList implémente cette interface (comme mentionné ci-dessus), mais pour votre usage, un ArrayDeque peut être mieux - vous n'encourrez pas le coût d'allocations d'objets constantes pour chaque nœud. Là encore, peu importe l'implémentation que vous utilisez.
La qualité normale du polymoprisme vient à jouer: la beauté de l'écriture contre l'interface Deque, plutôt que toute implémentation spécifique de celle-ci, est que vous pouvez très facilement basculer entre les implémentations pour tester celle qui fonctionne le mieux. Modifiez simplement la ligne avec new
, et le reste du code reste le même.
Il est préférable d'utiliser ArrayDeque au lieu de LinkedList lors de l'implémentation de Stack and Queue en Java. ArrayDeque est susceptible d'être plus rapide que l'interface Stack (alors que Stack est thread-safe) lorsqu'il est utilisé en tant que pile, et plus rapide que LinkedList lorsqu'il est utilisé en tant que file d'attente. Jetez un oeil à ce lien tilisez ArrayDeque au lieu de LinkedList ou Stack .
Si vous connaissez la limite supérieure de la quantité possible d'éléments dans la file d'attente, le tampon circulaire est plus rapide que LinkedList, car LinkedList crée un objet (lien) pour chaque élément de la file d'attente.
Cependant, si vous souhaitez toujours utiliser l'algorithme récursif, vous pouvez le remplacer par "tail-recursive" qui est probablement optimisé dans la JVM pour éviter les débordements de pile.
O (1) accès aux premier et dernier nœuds.
file d'attente de classe {
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;
}
}
Je pense que vous pouvez en mettre en place une simple comme la mise en œuvre
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;
}
}
}
Voici l'interface Queue avec Iterator et Iterable
La taille de la file d'attente augmentera à mesure qu'elle sera pleine
Interface de file d'attente
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();
}
Classe d'exception personnalisée
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);
}
}
Implémentation de la file d'attente
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++];
}
}
}