Добавление элемента в односвязный список в Java

#java #data-structures #singly-linked-list

#java #структуры данных #односвязный список

Вопрос:

Я реализую односвязный список на Java. Что мне не нравится в этом коде, так это то, что мне нужно проверять if (head.next == null) каждый раз, когда я добавляю элемент. Но условие выполняется только один раз, при добавлении первого элемента.

Есть ли способ реализовать односвязный некруглый список без такого условия?

 package sample;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class SinglyLinkedList<T> implements Iterable<T> {

    private Node<T> head = new Node<T>(null);
    private Node<T> last = null;

    public SinglyLinkedList(T... elements) {
        addAll(elements);
    }

    public void add(T element) {
        if (head.next == null) {
            head.next = new Node<T>(element);
            last = head.next;
        } else {
            Node<T> newNode = new Node<T>(element);
            last.next = newNode;
            last = last.next;
        }
    }

    public void addAll(T... elements) {
        for (T element : elements) {
            add(element);
        }
    }

    @Override
    public String toString() {
        Iterator<T> iterator = iterator();
        if (!iterator.hasNext()) {
            return "[]";
        }
        StringBuilder builder = new StringBuilder();
        builder.append("[");
        while (iterator.hasNext()) {
            T element = iterator.next();
            builder.append(element);
            if (!iterator.hasNext()) {
                return builder.append("]").toString();
            }
            builder.append(", ");
        }
        return builder.toString();
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {

            Node<T> current = head;

            @Override
            public boolean hasNext() {
                return current.next != null;
            }

            @Override
            public T next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                Node<T> temp = current;
                current = current.next;
                return temp.next.element;
            }

        };
    }

    private static class Node<T> {

        private Node<T> next;
        private T element;

        Node(T element) {
            this.element = element;
        }

        @Override
        public String toString() {
            return element.toString();
        }
    }
}
  

Комментарии:

1. Зачем вам нужен last объект?

2. @MarounMaroun, потому что мне нужно добавить элементы в список. На самом деле, метод add должен быть вызван addLast

3. @MarounMaroun, как вы думаете last , это излишне? Я видел несколько примеров, когда у них есть только указатель head, и перебирал список, чтобы добавить элемент в конец. Таким образом, добавление займет O (n), тогда как вставка в односвязный список должна занимать O (1). Смотрите также bigocheatsheet.com

Ответ №1:

Вы могли бы инициализировать last, чтобы указывать на head, а затем ваш if является избыточным:

 private Node<T> head = new Node<T>(null);
private Node<T> last = head;

public void add(T element) {
        Node<T> newNode = new Node<T>(element);
        last.next = newNode;
        last = last.next;
}
  

Комментарии:

1.Спасибо! Теперь я понимаю, почему это работает. So last содержит ссылку на ту же память, head что и . Таким образом, при добавлении первого элемента last.next = newNode; это означает, что last.next , а также head.next будет содержать ссылку на первый добавленный элемент.

Ответ №2:

Есть много случаев, когда «хороший OO-дизайн» позволяет обходиться без проверок if / else; чаще всего с использованием некоторой формы полиморфизма.

Значение: вместо того, чтобы запрашивать у какого-либо объекта какое-либо свойство, чтобы затем принять решение об этом в вашем клиентском коде, вы каким-то образом убедитесь, что ваш клиентский код может просто вызвать метод для какого-либо другого объекта. И затем «if» «скрыт» в коде, который изначально сгенерировал этот «другой объект» и передал его вашему клиентскому коду. (в этих видео вы найдете несколько хороших примеров того, как это работает).

Но — я думаю, что в этом случае это было бы явным излишеством!

Дело в том, что с точки зрения удобства чтения эта одна проверка действительно не повредит (возможно, вы могли бы преобразовать вещи в большее количество методов). И производительность … также не имеет значения. Если ваш код вызывается так часто, что это имеет значение, JIT все равно сработает и, вероятно, создаст код, который в большинстве случаев напрямую использует правильную ветвь.

Таким образом: это хорошая реализация; и я думаю, вам не стоит беспокоиться об этом, если -проверьте там!