Этот связанный список реализуется по кругу?

Это мой первый пост здесь! Я студент CS, поэтому я все еще учусь. Если у вас есть какие-либо советы или указатели (ха-ха!..!), Которые можно дать в отношении структурирования моего кода или общей практики, я был бы признателен за любые отзывы!

Мне дали задание повторно реализовать класс Queue в java (https://docs.oracle.com/javase/7/docs/api/java/util/Queue.html), используя кольцевой связанный список. Мое обращение прилагается к этому вопросу. Я получил ноль за задание — по словам оценщика, моя реализация связанного списка не является круговой.

Я не хочу показаться эгоистичным или слишком самоуверенным, потому что я, очевидно, в какой-то степени понятия не имею, что делаю, но в строках со 155 по 161 я закомментировал раздел, в котором выводятся данные следующая ссылка в списке. Насколько мне известно, узлы в списке связаны по кругу, потому что они продолжают печатать данные первого узла даже после того, как данные последнего узла были напечатаны.

Например (и я знаю, что этот сегмент выглядит беспорядочно, я установил его таким образом только в целях отладки — на самом деле это раздел, в котором я закомментировал свой фактический код):

System.out.println("next: " + cursor.getNext().getData());
System.out.println("next.next: " + cursor.getNext().getNext().getData());
System.out.println("next.next.next: " + cursor.getNext().getNext().getNext().getData());
System.out.println("next.next.next.next: " + cursor.getNext().getNext().getNext().getNext().getData());

печатает:

следующий: 45

следующий.следующий: 71

следующий.следующий.следующий: 3

следующий.следующий.следующий.следующий: 45

когда есть три узла, содержащие данные, введенные в список.

Кроме того, метод add, начинающийся в строке 30, назначает следующий узел после хвоста списка головному (в отличие от нулевого значения), поэтому список должен циклически повторяться, верно? Вот сегмент:

Node<T> node = new Node<T>(element);
if(isEmpty() == true){
    head = node;
}else{tail.setNext(node);}

tail = node;
node.setNext(head);

Я предполагаю, что мой вопрос: каким образом это не реализуется по кругу?

Опять же, вполне возможно, что я понятия не имею, о чем говорю, но, насколько мне известно, на самом деле это круговой связанный список.

Спасибо заранее за любую помощь!

Полный код (три класса — CircularLinkedQueue, Node и CircularLinkedListTest соответственно):

Круговая связанная очередь:

import java.util.NoSuchElementException;

public class CircularLinkedQueue<T> {
int count = 0;
private Node<T> head = null;
private Node<T> tail = head;

public CircularLinkedQueue(){
    Node<T> node = new Node<T>();
    tail = node;
}

/* Inserts the specified element into this queue if it is
 * possible to do so immediately without violating capacity
 * restrictions, returning true upon success and throwing
 * an IllegalStateException if no space is currently available.
 */
public boolean add(T element){
    try{
        Node<T> node = new Node<T>(element);
        if(isEmpty() == true){
            head = node;
        }else{tail.setNext(node);}
        
        tail = node;
        node.setNext(head);
        count++;
        return true;
    }catch(Exception all){
        Node<T> node = new Node<T>(element);
        if(element == null){throw new NullPointerException("Specified 
element is null.");}
        else if(element.getClass().getName() != 
node.getData().getClass().getName()){
            throw new ClassCastException
            ("Class of specified element prevents it from being added.");
        }
        return false;
    }
}
/* Retrieves, but does not remove, the head of this queue.
 * This method differs from peek only in that it throws
 * an exception if this queue is empty.
 */
public T element(){
    Node<T> cursor;
    if(isEmpty() != true){
        cursor = head;
    }else{throw new NoSuchElementException("No such element exists.");}
    return cursor.getData();
    
}
/*
Inserts the specified element into this queue if it is possible
to do so immediately without violating capacity restrictions.
When using a capacity-restricted queue, this method is generally
preferable to add(E), which can fail to insert an element only
by throwing an exception.
/*
public boolean offer(T element){
    try{
        Node<T> node = new Node<T>(element);
        if(isEmpty() == true){
            head = node;
        }else{tail.setNext(node);}
        
        tail = node;
        node.setNext(head);
        count++;
        return true;
    }catch(Exception all){return false;}
}

/* Retrieves, but does not remove, the head of this queue,
 * or returns null if this queue is empty.
 */
public T peek(){
    Node<T> cursor;
    //set cursor to head if not empty, create new null node otherwise
    if(isEmpty() != true){
        cursor = head;
    }else{cursor = new Node<T>(); cursor.setData(null);}
    //return null if no data is stored
    return cursor.getData();
}
/* Retrieves and removes the head of this queue,
 * or returns null if this queue is empty.
 */
public T poll(){
    Node<T> cursor = head;
    if(isEmpty() != true){
        if(count <= 1){
            head.setNext(null);
            head = head.getNext();
            tail = head;
            count--;
            return null;
        }else{
            //cursor = head;
            head = head.getNext();
            tail.setNext(head);
        }
    }else{cursor = new Node<T>(); cursor.setData(null);}
    count--;
    return cursor.getData();
}
/* Retrieves and removes the head of this queue.
 * This method differs from poll only in that it
 * throws an exception if this queue is empty.
 */
public T remove(){
    Node<T> cursor;
    if(isEmpty() != true){
        if(count <= 1){
            head.setNext(null);
            head = head.getNext();
            tail = head;
            count--;
            return null;
        }else{
            cursor = head;
            head = head.getNext();
            tail.setNext(head);
        }
    }
    else{throw new NoSuchElementException("No such element exists.");}
    count--;
    return cursor.getData();
}
//returns whether the list is empty or not
public boolean isEmpty(){return head == null;}

/* This method puts all the values of the circular linked list
 * into a String type for output purposes.
 */
@Override
public String toString(){
    int cycles = count;
    String s = "";
    Node<T> cursor = head;
    while(cycles > 0){
        s = s + cursor.getData() + "\n";
        cursor = cursor.getNext();
        cycles--;
    }
    /*
     * these lines print as expected & exist only for debugging purposes
    System.out.println("next: " + cursor.getNext().getData());
    System.out.println("next.next: " + 
cursor.getNext().getNext().getData());
    System.out.println("next.next.next: " + 
cursor.getNext().getNext().getNext().getData());
    System.out.println("next.next.next.next: " + 
cursor.getNext().getNext().getNext().getNext().getData());
    */
    return s;
}
//returns the length of the list
public int getCount(){return count;}
}

Узел:

public class Node<T> {
T data;
Node<T> next;

public Node(){data = null;}

public Node(T data){this.data = data;}

public void setData(T data){this.data = data;}

public void setNext(Node<T> nextNode){next = nextNode;}

public T getData(){return data;}

public Node<T> getNext(){return next;}
}

Циркуларлинкедлисттест:

public class CircularLinkedListTest<T>{

public static void main(String[] args) {
    /* demonstrates creation of new circular linked lists/linked queues,
     * uses two different data types
     */
    CircularLinkedQueue<Integer> c1 = new CircularLinkedQueue<Integer>();
    CircularLinkedQueue<String> c2 = new CircularLinkedQueue<String>();
    //demonstrate add and offer methods detailed in Queue interface
    c1.add(3);
    c1.offer(45);
    c1.offer(71);
    c2.add("hello");
    c2.offer("good evening");
    c2.offer("how do you do");
    //demonstrates a toString method and prints after data has been added
    System.out.println("c1.toString(): \n" + c1.toString());
    System.out.println("c2.toString(): \n" + c2.toString());
    /* demonstrates the remove and poll methods, prints out the values in 
the list,
     * poll method returns null when implemented, isEmpty method shows the
     * nodes are truly being removed from the list after poll or remove
methods are
     * called
     */
    c1.remove();
    c2.remove();
    c2.remove();
    System.out.println("c1.toString(): \n" + c1.toString());
    System.out.println("c2.poll(): " + c2.poll());
    System.out.println("c2.getCount(): " + c2.getCount());
    System.out.println("c2.isEmpty(): " + c2.isEmpty());
    System.out.println("");
    //re-add data to c2
    c2.offer("howdy");
    c2.offer("hi there");
    //reprint c2, getCount and isEmpty to prove updated data values
    System.out.println("c2.toString(): \n" + c2.toString());
    System.out.println("c2.getCount(): " + c2.getCount());
    System.out.println("c2.isEmpty(): " + c2.isEmpty());
    System.out.println("");
    /* demonstrate peek and element functions by printing out most
     * recent items in the linked queue
     */
    System.out.println("c1.peek(): " + c1.peek());
    System.out.println("c2.element(): " + c2.peek());
    System.out.println("");
    //remove items from c1 to show peek returns null when list is empty
    c1.remove();
    c1.remove();
    System.out.println("c1.peek(): " + c1.peek());
    System.out.println("c1.getCount(): " + c1.getCount());
    System.out.println("c1.isEmpty(): " + c1.isEmpty());
    
    //all methods in queue interface have been demonstrated
}

}

Еще раз спасибо заранее за любую помощь!


person Noah    schedule 12.12.2018    source источник
comment
Просто для ясности: это был просто круговой список, и не требовалось, чтобы он был двухсвязным, верно?   -  person KevinO    schedule 12.12.2018
comment
вы должны спросить оценщика... по крайней мере, IMO, который представляет собой круговой связанный список.... конечно, некоторые странные части, такие как инициализация tail пустым узлом в конструкторе или выражения, такие как if (isEmpty() != true) (то же самое как if (!isEmpty()), что немного легче читать, как not isEmpty), но я не смог найти никаких причин для того, чтобы заявить, что он не круговой (не проверено)   -  person user85421    schedule 12.12.2018
comment
@KevinO Верно, это был циклический односвязный список.   -  person Noah    schedule 13.12.2018
comment
@CarlosHeuberger На мой взгляд, я мысленно озвучивал как «if (isEmpty() != true)», так и «if (!isEmpty())», поскольку «isEmpty() не возвращает true». В будущем я буду иметь в виду ваше предложение, хотя. Кроме того, при первом просмотре я подумал так же, поскольку программа функционирует как круговой связанный список, но в другом комментарии указывалось, что настоящий круговой связанный список не использует головной или хвостовой узел. В любом случае я ценю обратную связь, спасибо!   -  person Noah    schedule 13.12.2018
comment
хорошо, если у вас нет head (или любой другой ссылки на список, например курсора), как вы получите к нему доступ? или просто называет это "головой" проблемой...   -  person user85421    schedule 13.12.2018
comment
Мне было интересно то же самое, я думаю, что «голова» обычно указывает на начало списка. Поэтому мне понадобится узел «текущий», который, вероятно, будет помещен в конец списка, тогда я буду использовать узел курсора для просмотра списка. Как только курсор указывает на адрес памяти как на «текущий» узел, мы знаем, что следующий за «текущим» узел уже рассмотрен и отмечает закрытие циклического списка. Я почти уверен, что мои мысли здесь верны... Я уверен, что есть бесчисленное множество других способов реализовать это, возможно, счетчик тоже сработает.   -  person Noah    schedule 13.12.2018


Ответы (1)


Я получил ноль за задание — по словам оценщика, моя реализация связанного списка не является круговой.

Я нахожу эту оценку несколько резкой. Снаружи ваш класс ведет себя замкнуто: он может добавлять новые значения за время O(1), а "последнее" значение имеет ссылку на " сначала», замыкая петлю.

Я предполагаю, что мой вопрос: каким образом это не реализуется по кругу?

В действительно круговой реализации я бы не ожидал увидеть понятия «голова» и «хвост». Ведь у круга нет ни начала, ни конца. Он может иметь текущий элемент со ссылками на следующий и предыдущий элементы. А может быть, это и требовалось. Глядя изнутри, эта реализация очень похожа на заурядный связанный список с быстрым доступом к хвосту. Лучше спросить у классника!

person janos    schedule 12.12.2018
comment
Возможно, я неправильно понял, но в единственном связанном списке не будет «предыдущего» элемента. Таким образом, мы можем сделать единый связанный список циклическим, указав .next последнего элемента на голову, но не будет предыдущего для доступа или обновления. Или я что-то не понял в ответе? - person KevinO; 12.12.2018
comment
@KevinO Конечно, ссылка на предыдущий элемент может быть необязательной, по-настоящему круговой список можно реализовать с одинарной ссылкой, нет необходимости в двойной ссылке. Пока достаточно идти всегда вперед. Если есть необходимость вернуться назад, что вполне естественно в круговых структурах, то потребуется реализация с двойной связью. - person janos; 12.12.2018
comment
Благодарю вас! Это отлично отвечает на вопрос. Я свяжусь с оценщиком. - person Noah; 13.12.2018
comment
Быстрый дополнительный вопрос (я собирался добавить его к моему предыдущему комментарию, но я случайно нажал клавишу ввода, и я думаю, что вы можете редактировать комментарии только в течение пяти минут): без головы или хвоста, когда курсор перемещается по списку, как вы могли указать весь список был рассмотрен? Вы бы сравнили адрес памяти курсора с «текущим» узлом? Или было бы лучше использовать счетчик, который продолжается столько раз, сколько узлов в списке? Я уверен, что есть много способов, мне просто любопытно, какой из них вы считаете лучшим. Еще раз спасибо за вашу помощь! - person Noah; 13.12.2018
comment
@Noah использует размер списка, чтобы узнать, сколько элементов нужно перебрать, чтобы рассмотреть весь список. - person janos; 13.12.2018