class Node<E> { Node(E data) {this(data, null);} Node(E data, Node next) { this.data = data; this.next = next; } E data; Node next; }There are a couple of operations that can be coded using nothing more than the notion of a node (i.e., before moving on to lists of nodes).
insertAfter
Insert an element after a particular node (pointed to by the node
parameter)
void insertAfter(Node node, E data) { if (node == null) throw new Exception("..."); Node newNode = new Node(data, node.next); node.next = newNode; }
data
field to the passed data
node
's next
field to the new node's next
field
node
's next
field to the new node.
removeAfter
Remove the element after a particular node (pointed to by node
) in a list
E removeAfter(Node node) { if (node == null) throw new Exception("null sent as argument to removeAfter"); if (node.next == null) throw new Exception("Attempt to remove node after last node of list"); Node hold = node.next; node.next = hold.next; return hold.data; }
node
's next
field to the node after the node to be deleted
next
link fields; the result as known as a linked list
head
—
to the first node on the list.
next
explicit address fields of the nodes.
null
in its next
field to indicate the end of the list
next
link/field; we call these singly-linked lists
class LinkedList { Node head = null; }
public class SL1N_Deque<E> implements Deque<E>{ static class Node<E> { public Node(E data, Node<E> next) { this.data = data; this.next = next; } Node(E data) {this(data, null);} Node() {this(null);} E data; Node<E> next; } public void addFirst(E data) { var node = new Node<>(data, head); head = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = head; head = head.next; return node.data; } public void addLast(E data) { if (isEmpty()) addFirst(data); else { Node<E> p = head; while (p.next != null) { p = p.next; } p.next = new Node<E>(data); } } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = head; while (p != null) { count++; p = p.next; } return count; } public boolean isEmpty() {return head == null;} public String toString() { String result = "{"; var p = head; while (p != null) { result += p.data + (p.next != null ? ", " : ""); p = p.next; } result += "}"; return result; } public int capacity() {return -1;} private Node<E> head = null; }
removeLast
method which is left as a lab.
addFirstr
and removeFirst
simply manipulate pointers, they do not traverse the list or otherwise do anything
that is proportional to the size of the list, and thus they are O(1) operations.
addLast
and removeLast
both require a traversal to the end of the list in this implementation, thus making them O(n) operations.
addLast
method leverages the addFirst
when the list is empty (i.e., when adding the last element is the same as adding the first element).
if (isEmpty()) addFirst(data);
addLast
when the list is empty.
addLast
involves a traversal to the end of the list. Here is that logic:
>
Node<E> p = head;
while (p.next != null) {
p = p.next;
}
p.next = new Node<E>(data);
NullPointerException
in the while
header:
Node<E> p = head; // if list is empty, p == head == null while (p.next != null) { p = p.next; }
q
) trails one node behind p
, and thus remins on the end o fhte list when p falls off (p == null
(more
on piggybacking below):
// without leveraging addFirst public void addLast(E data) { Node<E> p = head q = null; while (p != null) { q = p; p = p.next; } if (q == null) // q never moved; list was empty head = new Node<E>(data); // or could have called addFirst else q.next = new Node<E>(data); }
addFirst
somewhat hides this issue, but it still required us to test oofr this condition (if isEmpty()
)
removeLast
for a singleton (i.e., one -element) list … removing the last element is the same as removing the first element.
size
also requires a traversal, making it an O(n) operation
for (int i = 0; i < list.size(); i++)which then actually becomes a O(n2) operations (n times through the loop — O(n) — with the call to
size
itself being O(n).
isEmpty
: head == null
, rather than size() == 0
.
isEmpty
/ size
is the 'poster child' of method leveraging. In general, isEmpty
is defined as size() == 0
:
size
method in order to come up with a succinct, readable definition of isEmpty
and thus allows us to avoidm replicating that logic in isEmpty
isEmpty
in an abstract class, where the definition (implementation) of size
is deferred to a subclass.
size
to define isEmpty
transforms an O(1) operation (head == null
) into
an O(n) (as discussed above, size
is an O(n) operation in this implementation.
size
instance variable in the list to avoid the traversal, which would solve both problems (the
overhead of using size
n the for loop and using it to leverage isEMpty
capacity
method which had discussed earlier as not being part of the 'pure' interface (being an implementation issue) becomes a bit of an issue here, since
indeed, linked lists do not have a capacity issue.
-1
as the capacity value of our list, thus allowing us to conform to our 'capacity'-possessing interface, but
specifying an 'impossible' capacity, thus informing the call that there is no capacity information wrt the list.
UnsupportedOperationException
to be
thrown.
Node getNodeAt(int position)
getNodeAt(int i)
that returns a reference to the i'th node and either null
or throws an exception if i
is beyond
the end of the list (or negative).
size
field
getNodeAt
method to check that i
is in the
bounds of the list before embarking on what could be an expensive traversal if the list is large.
addFirst
and removeFirst
methods (renaming them push
and pop
,
or alternatively code the Stack
directly (i.e., independently).
push
void push(int data) { // same as an addFront method Node node = new Node(data, head); head = node; }
data
field to the passed data
next
field to the current head of the list
Node node - new Node(data); node.next = head;
void push(Node *&head, const EType &data) { head = new Node(data, head); }
pop
int pop() { // same as a removeFront method if (isEmpty()) throw new Exception("...."); Node hold = head; head = head.next; return hold.data; }
head
of the list the node after the node to be deleted
addLast
/
removeLast
, and thus the whole issue of special logic (which was only applicable when operating at the end of the list) goes away.
class Node { Node next; } class DataNodeextends Node { private E data; } class MetaNode implements Node { MetaData data; } class LinkedList { MetaNode head; }
public class SL1H_Deque<E> implements Deque<E>{ static class Node<E> { public Node(E data, Node<E> next) { this.data = data; this.next = next; } Node(E data) {this(data, null);} Node() {this(null);} E data; Node<E> next; } SL1H_Deque() { header = new Node<>(); } public void addFirst(E data) { var node = new Node<>(data, header.next); header.next = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = header.next; header.next = node.next; return node.data; } p ublic void addLast(E data) { Node<E> p = header; while (p.next != null) { p = p.next; } p.next = new Node<E>(data); } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = header.next<.span>;<.span> while (p != null) { count++; p = p.next; } return count; } public boolean isEmpty() {return header.next == null;} public String toString() { String result = "{"; var p = header.next; while (p != null) { result += p.data + (p.next != null ? ", " : ""); p = p.next; } result += "}"; return result; } public int capacity() {return -1;} private Node<E> header = null; }
head
to it.
p
is set to refer to the
node pointed to by the header node.
addLast
; p
is set to the header node, which is always
there and thus the p.next
is completely safe.
addFirst
or
removeFirst
, and thus there is no benefit to having a header node in that context; the special logic only
is required when operating at the end of the list, and stack operates only at the beginning.
mublic class SL2N_Deque<E> implements Deque<E>{ static class Node<E> { public Node(E data, Node<E> next) { this.data = data; this.next = next; } Node(E data) {this(data, null);} Node() {this(null);} E data; Node<E> next; } public void addFirst(E data) { var node = new Node<>(data, head); if (head == null) tail = node; head = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = head; head = head.next; if (head == null) tail = null; return node.data; } public void addLast(E data) { if (isEmpty()) addFirst(data); else { Node<E> node = new Node<E>(data); tail.next = node; tail = node; } } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = head, q = null; while (p != null) { count++; q = p; p = p.next; } if (q != tail) { System.out.println("*** Deque integrity error: tail is not pointing at last node"); System.exit(1); } return count; } public boolean isEmpty() {return head == null && tail == null;} public String toString() { String result = "{"; Node<E> p = head, q = null; while (p != null) { result += p.data + (p.next != null ? ", " : ""); q = p; p = p.next; } if (q != tail) { System.out.println("*** Deque integrity error: tail is not pointing at last node"); System.exit(1); } result += "}"; return result; } public int capacity() {return -1;} private Node<E> head = null, tail = null; }
addLast
becomes an O(1) operation.
removeLast
(which you are to code), however, remains an O(n) as it requires a traversal to get to the node
before the one pointed to by tail
.
addFirst
and removeFirst
, we have special logic assigning dealing withthe synchronization of tail
tail
being at the front of the list
tail
will be affected as well.
tail
(which, like head
, was null
needs to be
assigned to the node.
tail
must be set to null
(in addition to head
being set to null
as part of the removeFromt
code.
addLast
/removeLast
methoids, it doesn't address the
special logic of keeping the two pointers in synch.
add (to rear)
void add(E data) { Node node = new Node(data); if (tail != null) tail.next = node; else head = node; tail = node; }
remove (from front)
E remove() { if (isEmpty()) throw new Exception("…"); E data = head.data; head = head.next; if (head == null) tail = null; return data;; }
public class SC1N_Deque<E> implements Deque<E>{
static class Node<E> {
public Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
Node(E data) {this(data, null);}
Node() {this(null);}
E data;
Node<E> next;
}
public void addFirst(E data) {
var node = new Node<>(data);
if (tail == null) {
node.next = node;
tail = node;
}
else {
node.next = tail.next;
tail.next = node;
}
}
public E removeFirst() {
if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container");
var node = tail.next;
if (tail.next == tail)
tail = null;
else
tail.next = node.next;
return node.data;
}
public void addLast(E data) {
addFirst(data);
tail = tail.next;
}
public E removeLast(); // you are to implement this method
public int size() {
int count = 0;
if (tail != null) {
Node<E> p = tail.next;
do {
count++;
p = p.next;
} while (p != tail.next);
}
return count;
}
public boolean isEmpty() {return tail == null;}
public String toString() {
String result = "{";
if (tail != null) {
var p = tail.next;
do {
result += p.data + (p.next != tail.next ? ", " : "");
p = p.next;
} while (p != tail.next);
}
result += "}";
return result;
}
public int capacity() {return -1;}
private Node<E> tail = null;
}
tail
point at the last node also means the first node is the next node, i.e.,
tail.next
.
node.next = node
addFirst
and addLast
is the latter moves the tail
pointer.
class Node<E> { Node(E data, Node prev, Node next) { this.data = data; this.prev = prev; this.next = next; } Node(E data) {this(data, null, null);} E data; Node next, prev; // often left, right }
public class DL1N_Deque<E> implements Deque<E> { static class Node<E> { public Node(Node<E> prev, E data, Node<E> next) { this.prev = prev; this.data = data; this.next = next; } Node(E data, Node<E> next) {this(null, data, next);} Node(Node<E> prev, E data) {this(prev, data, null);} Node(E data) {this(null, data, null);} Node( ) {this(null);} E data; Node<E> prev, next; } public void addFirst(E data) { Node<E> node = new Node<>(data, head); if (head != null) head.prev = node; head = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = head; if (node.next != null) node.next.prev = null; head = node.next; return node.data; } public void addLast(E data) { if (isEmpty()) addFirst(data); else { Node<E> p = head; while (p.next != null) p = p.next; Node<E> node = new Node<E>(p, data); p.next = node; } } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = head; while (p != null) { count++; p = p.next; } return count; } public boolean isEmpty() {return head == null;} public String toString() { String result = "{"; Node<E> p = head; while (p != null) { result += p.data + (p.next != null ? ", " : ""); p = p.next; } result += "}"; return result; } public int capacity() {return -1;} private Node<E> head = null; }
class List<E> {
static class Node<E> {
public Node(Node<E> prev, E data, Node<E> next) {
this.prev = prev;
this.data = data;
this.next = next;
}
Node(E data, Node<E> next) {this(null, data, next);}
Node(Node<E> prev, E data) {this(prev, data, null);}
Node(E data) {this(null, data, null);}
Node() {this(null);}
E data;
Node<E> prev, next;
}
E get(int pos) {return getNode(pos).data;}
void set(int pos, E val) {getNode(pos).data = val;}
void add(int pos, E val) {
Node<E> atPrev, atNext;
if (pos == 0) {
atPrev = null;
atNext = head;
}
else {
atPrev = getNode(pos-1);
atNext = atPrev.next;
}
Node<E> node = new Node<E>(atPrev, val, atNext);
if (atNext != null) atNext.prev = node;
if (atPrev != null) atPrev.next = node;
if (pos == 0) head = node;
size++;
}
E remove(int pos) {
Node<E> node = getNode(pos);
E data = node.data;
Node<E> atNext = node.next;
Node<E> atPrev = node.prev;
if (atPrev != null) atPrev.next = atNext;
if (atNext != null) atNext.prev = atPrev;
size--;
return data;
}
int size() {return size;}
boolean isEmpty() {return size == 0;}
private Node<E> getNode(int pos) {
int count = 0;
Node<E> p = head;
while (p != null && count < pos) {
p = p.next();
count++;
}
if (p == null) throw new ListException("illegal position " + pos);
return p;
}
public String toString() {
Node<E> p = head;
String result = "";
result += "[";
while (p != null) {
result += p.data + (p.next != null ? ", " : "");
p = p.next;
}
result += "]";
return result;
}
Node<E> head = null;
int size = 0;
}
size
variable to eliminate traversal for the operation
public class DL2N_Deque<E> implements Deque<E> { static class Node<E> { public Node(Node<E> prev, E data, Node<E> next) { this.prev = prev; this.data = data; this.next = next; } Node(E data, Node<E> next) {this(null, data, next);} Node(Node<E> prev, E data) {this(prev, data, null);} Node(E data) {this(null, data, null);} Node() {this(null);} E data; Node<E> prev, next; } public void addFirst(E data) { Node<E> node = new Node<>(data, head); if (isEmpty()) tail = node; else head.prev = node; head = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = head; if (node.next != null) node.next.prev = null; head = node.next;; return node.data; } public void addLast(E data) { if (isEmpty()) addFirst(data); else { Node<E> node = new Node<>(tail, data); tail.next = node; tail = node; } } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = head; while (p != null) { count++; p = p.next; } return count; } public boolean isEmpty() {return head == null;} public String toString() { String result = "{"; Node<E> p = head; while (p != null) { result += p.data + (p.next != null ? ", " : ""); p = p.next; } result += "}"; return result; } public int capacity() {return -1;} private Node<E> head = null, tail = null; }
removeLast
and as a result provide us with an O(1) linked deque implementation.
public class DC1N_Deque<E> implements Deque<E> { static class Node<E> { public Node(Node<E> prev, E data, Node<E> next) { this.prev = prev; this.data = data; this.next = next; } Node(E data) {this(null, data, null);} Node() {this(null);} E data; Node<E> prev, next; } public void addFirst(E data) { Node<E> node = new Node<>(data); if (head == null) { node.next = node.prev = node; } else { node.prev = head.prev; head.prev.next = node; node.next = head; head.prev = node; } head = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = head; if (head.next == head) head = null; else { node.next.prev = node.prev; node.prev.next = node.next;; head = node.next; } return node.data; } public void addLast(E data) { if (head == null) addFirst(data); else { Node<E> node = new Node<>(head.prev, data, head); node.prev = head.prev; head.prev.next = node; node.next = head; head.prev = node; } } public E removeLast(); // you are to implement this method public int size() { int count = 0; if (head != null) { Node<E> p = head; do { count++; p = p.next; } while (p != head); } return count; } public boolean isEmpty() {return head == null;} public String toString() { String result = "{"; if (head != null) { Node<E> p = head; do { result += p.data + (p.next != head ? ", " : ""); p = p.next; } while (p != head); } result += "}"; return result; } public int capacity() {return -1;} private Node<E> head = null; }
head == new Node();
public class DC1H_Deque<E> implements Deque<E> { static class Node<E> { public Node(Node<E> prev, E data, Node<E> next) { this.prev = prev; this.data = data; this.next = next; } Node(E data) {this(null, data, null);} Node() {this(null);} E data; Node<E> prev, next; } DC1H_Deque() { header = new Node<>(); header.next = header.prev = header; } public void addFirst(E data) { Node<E> node = new Node<>(header, data, header.next); header.next.prev = node; header.next = node; } public E removeFirst() { if (isEmpty()) throw new CollectionException("Deque", "removeFirst", "empty container"); var node = header.next; node.next.prev = header; header.next = node.next;; return node.data; } public void addLast(E data) { Node<E> node = new Node<>(header.prev, data, header); header.prev.next = node; header.prev = node; } public E removeLast(); // you are to implement this method public int size() { int count = 0; Node<E> p = header.next; while (p != header) { count++; p = p.next; } return count; } public boolean isEmpty() {return header.next == header && header == header.prev;} public String toString() { String result = "{"; Node<E> p = header.next; while (p != header) { result += p.data + (p.next != header ? ", " : ""); p = p.next; } result += "}"; return result; } public int capacity() {return -1;} private Node<E> header; }
If we are willing to traverse the list, we can also consider:
remove
: remove the first element equal to some specified value (why not insert
?)
remove
really belongs to the associative world, as it it value-based. We are presenting it here to introduce
a standard technique in linked list processing.
remove
remove
: remove the first element equal to some specified value (content-based remove)
boolean remove(E data) { Node p = head, q = null; while (p != null && data != p.data) { q = p; // this is the piggyback action p = p.next; } if (p == null) return false; else if (q == null) removeFront(); else removeAfter(q); }
remove,
uses a technique known as piggybacking
length
: returns number of elements in a list
toString
/ returns a String representation of the list elements
clear
: removes all the elements from a list
find
: returns whether a particular value is an element of a list
copy
: copy the elements of a list to a new list
remove
... someFunc(Node head...) { while (head != null) { // head passed by value, can modify it without worry ...head.data // process data of node pointed to by head head = head.next; // go to next node } return; }
length
int length(Node head) { int count = 0; while (head != null) { count++; head = head.next; } return count; }
copy
Node copy(Node head) { Node lastNode = null, newHead = null; while (head != null) { Node q = new Node(head.data); if (newHead = null) // only true one (first time) newHead = q; else lastNode.next = q; lastNode = q; head = head.next; } return newHead; }
head
contains nullptr
, or
... someFunc(Node head...) { if (head == null) // list is empty return; else { ... head.data ... // process first element of the list someFunc(head.next); // call the function recursively on the rest of the list } }
length
int length(Node head) { if (head == null) return 0; else return length(head.next) + 1; }or
int length(Node head) {return head != null ? length(head.next) + 1 : 0;}
copy
Node copy(Node head) { if (head == null) return null; return new Node(head.data, copy(head.next)); }
head
class LinkedList { … public int length() {return length(head);} // can't be recursive, but can call a private recursive helper function … private int length(Node p) { recurses on its Node parm if (p == null) return 0; return 1 + length(p.next); } Node head; … };