第一种实现形式是比较普通常见的,链表有一个sentinal节点,由DLinkedListNode,DList类组成,具体实现如下:
/* DList.java */
package list;
/**
* A DList is a mutable doubly-linked list ADT. Its implementation is
* circularly-linked and employs a sentinel (dummy) node at the head
* of the list.
*
*/
public class DList {
/**
* head references the sentinel node.
* size is the number of items in the list. (The sentinel node does not
* store an item.)
*
*/
protected DListNode head;
protected int size;
/* DList invariants:
* 1) head != null.
* 2) For any DListNode x in a DList, x.next != null.
* 3) For any DListNode x in a DList, x.prev != null.
* 4) For any DListNode x in a DList, if x.next == y, then y.prev == x.
* 5) For any DListNode x in a DList, if x.prev == y, then y.next == x.
* 6) size is the number of DListNodes, NOT COUNTING the sentinel,
* that can be accessed from the sentinel (head) by a sequence of
* "next" references.
*/
/**
* newNode() calls the DListNode constructor. Use this class to allocate
* new DListNodes rather than calling the DListNode constructor directly.
* That way, only this method needs to be overridden if a subclass of DList
* wants to use a different kind of node.
* @param item the item to store in the node.
* @param prev the node previous to this node.
* @param next the node following this node.
*/
protected DListNode newNode(Object item, DListNode prev, DListNode next) {
return new DListNode(item, prev, next);
}
/**
* DList() constructor for an empty DList.
*/
public DList() {
head = newNode(null,null,null);
head.prev = head;
head.next = head;
size = 0;
}
/**
* isEmpty() returns true if this DList is empty, false otherwise.
* @return true if this DList is empty, false otherwise.
* Performance: runs in O(1) time.
*/
public boolean isEmpty() {
return size == 0;
}
/**
* length() returns the length of this DList.
* @return the length of this DList.
* Performance: runs in O(1) time.
*/
public int length() {
return size;
}
/**
* insertFront() inserts an item at the front of this DList.
* @param item is the item to be inserted.
* Performance: runs in O(1) time.
*/
public void insertFront(Object item) {
DListNode insertNode = newNode(item,head,head.next);
head.next.prev = insertNode;
head.next = insertNode;
size++;
}
/**
* insertBack() inserts an item at the back of this DList.
* @param item is the item to be inserted.
* Performance: runs in O(1) time.
*/
public void insertBack(Object item) {
DListNode insertNode = newNode(item,head.prev,head);
head.prev.next = insertNode;
head.prev = insertNode;
size--;
}
/**
* front() returns the node at the front of this DList. If the DList is
* empty, return null.
*
* Do NOT return the sentinel under any circumstances!
*
* @return the node at the front of this DList.
* Performance: runs in O(1) time.
*/
public DListNode front() {
if(size == 0){
return null;
}
return head.next;
}
/**
* back() returns the node at the back of this DList. If the DList is
* empty, return null.
*
* Do NOT return the sentinel under any circumstances!
*
* @return the node at the back of this DList.
* Performance: runs in O(1) time.
*/
public DListNode back() {
if(size == 0){
return null;
}
return head.prev;
}
/**
* next() returns the node following "node" in this DList. If "node" is
* null, or "node" is the last node in this DList, return null.
*
* Do NOT return the sentinel under any circumstances!
*
* @param node the node whose successor is sought.
* @return the node following "node".
* Performance: runs in O(1) time.
*/
public DListNode next(DListNode node) {
if(node == null || node == head.prev){
return null;
}
return node.next;
}
/**
* prev() returns the node prior to "node" in this DList. If "node" is
* null, or "node" is the first node in this DList, return null.
*
* Do NOT return the sentinel under any circumstances!
*
* @param node the node whose predecessor is sought.
* @return the node prior to "node".
* Performance: runs in O(1) time.
*/
public DListNode prev(DListNode node) {
if(node == null || node == head.next){
return null;
}
return node.prev;
}
/**
* insertAfter() inserts an item in this DList immediately following "node".
* If "node" is null, do nothing.
* @param item the item to be inserted.
* @param node the node to insert the item after.
* Performance: runs in O(1) time.
*/
public void insertAfter(Object item, DListNode node) {
if(node == null){
return;
}
DListNode insertNode = newNode(item, node, node.next);
node.next.prev = insertNode;
node.next = insertNode;
size++;
}
/**
* insertBefore() inserts an item in this DList immediately before "node".
* If "node" is null, do nothing.
* @param item the item to be inserted.
* @param node the node to insert the item before.
* Performance: runs in O(1) time.
*/
public void insertBefore(Object item, DListNode node) {
if(node == null){
return;
}
DListNode insertNode = newNode(item,node.prev,node);
node.prev.next = insertNode;
node.prev = insertNode;
size++;
}
/**
* remove() removes "node" from this DList. If "node" is null, do nothing.
* Performance: runs in O(1) time.
*/
public void remove(DListNode node) {
if(node == null){
return;
}
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
}
/**
* toString() returns a String representation of this DList.
*
* DO NOT CHANGE THIS METHOD.
*
* @return a String representation of this DList.
* Performance: runs in O(n) time, where n is the length of the list.
*/
public String toString() {
String result = "[ ";
DListNode current = head.next;
while (current != head) {
result = result + current.item + " ";
current = current.next;
}
return result + "]";
}
public static void main(String[] args){
DList list = new DList();
for(int i = 10; i >= 0; i--){
list.insertFront(i);;
}
// System.out.print(list.toString());
list.remove(list.head);
System.out.print(list.toString());
}
}
/* DListNode.java */
package list;
/**
* A DListNode is a node in a DList (doubly-linked list).
*/
public class DListNode {
/**
* item references the item stored in the current node.
* prev references the previous node in the DList.
* next references the next node in the DList.
*
*/
public Object item;
protected DListNode prev;
protected DListNode next;
/**
* DListNode() constructor.
* @param i the item to store in the node.
* @param p the node previous to this node.
* @param n the node following this node.
*/
DListNode(Object i, DListNode p, DListNode n) {
item = i;
prev = p;
next = n;
}
}
第二种实现形式与第一种略有不同,第二种的DListNode中加入了myList域,指向节点所在的链表,并且在DListNode类中加入了item(),setItem()方法,将原来在DList中的一些方法如next(),remove()等方法移到了DListNode中。具体实现如下:
/* DList.java */
package list;
/**
* A DList is a mutable doubly-linked list ADT. Its implementation is
* circularly-linked and employs a sentinel node at the head of the list.
*
**/
public class DList extends List {
/**
* (inherited) size is the number of items in the list.
* head references the sentinel node.
* Note that the sentinel node does not store an item, and is not included
* in the count stored by the "size" field.
*
**/
protected DListNode head;
/* DList invariants:
* 1) head != null.
* 2) For every DListNode x in a DList, x.next != null.
* 3) For every DListNode x in a DList, x.prev != null.
* 4) For every DListNode x in a DList, if x.next == y, then y.prev == x.
* 5) For every DListNode x in a DList, if x.prev == y, then y.next == x.
* 6) For every DList l, l.head.myList = null. (Note that l.head is the
* sentinel.)
* 7) For every DListNode x in a DList l EXCEPT l.head (the sentinel),
* x.myList = l.
* 8) size is the number of DListNodes, NOT COUNTING the sentinel,
* that can be accessed from the sentinel (head) by a sequence of
* "next" references.
**/
/**
* newNode() calls the DListNode constructor. Use this method to allocate
* new DListNodes rather than calling the DListNode constructor directly.
* That way, only this method need be overridden if a subclass of DList
* wants to use a different kind of node.
*
* @param item the item to store in the node.
* @param list the list that owns this node. (null for sentinels.)
* @param prev the node previous to this node.
* @param next the node following this node.
**/
protected DListNode newNode(Object item, DList list,
DListNode prev, DListNode next) {
return new DListNode(item, list, prev, next);
}
/**
* DList() constructs for an empty DList.
**/
public DList() {
head = newNode(null,null,null,null);
head.prev = head;
head.next = head;
size = 0;
}
/**
* insertFront() inserts an item at the front of this DList.
*
* @param item is the item to be inserted.
*
* Performance: runs in O(1) time.
**/
public void insertFront(Object item) {
DListNode insertNode = newNode(item,this,head,head.next);
head.next.prev = insertNode;
head.next = insertNode;
size++;
}
/**
* insertBack() inserts an item at the back of this DList.
*
* @param item is the item to be inserted.
*
* Performance: runs in O(1) time.
**/
public void insertBack(Object item) {
DListNode insertNode = newNode(item,this,head.prev,head);
head.prev.next = insertNode;
head.prev = insertNode;
size++;
}
/**
* front() returns the node at the front of this DList. If the DList is
* empty, return an "invalid" node--a node with the property that any
* attempt to use it will cause an exception. (The sentinel is "invalid".)
*
*
* @return a ListNode at the front of this DList.
*
* Performance: runs in O(1) time.
*/
public ListNode front() {
return head.next;
}
/**
* back() returns the node at the back of this DList. If the DList is
* empty, return an "invalid" node--a node with the property that any
* attempt to use it will cause an exception. (The sentinel is "invalid".)
*
*
* @return a ListNode at the back of this DList.
*
* Performance: runs in O(1) time.
*/
public ListNode back() {
return head.prev;
}
/**
* toString() returns a String representation of this DList.
*
*
* @return a String representation of this DList.
*
* Performance: runs in O(n) time, where n is the length of the list.
*/
public String toString() {
String result = "[ ";
DListNode current = head.next;
while (current != head) {
result = result + current.item + " ";
current = current.next;
}
return result + "]";
}
/* DListNode.java */
package list;
/**
* A DListNode is a mutable node in a DList (doubly-linked list).
**/
public class DListNode extends ListNode {
/**
* (inherited) item references the item stored in the current node.
* (inherited) myList references the List that contains this node.
* prev references the previous node in the DList.
* next references the next node in the DList.
*
**/
protected DListNode prev;
protected DListNode next;
/**
* DListNode() constructor.
* @param i the item to store in the node.
* @param l the list this node is in.
* @param p the node previous to this node.
* @param n the node following this node.
*/
DListNode(Object i, DList l, DListNode p, DListNode n) {
item = i;
myList = l;
prev = p;
next = n;
}
/**
* isValidNode returns true if this node is valid; false otherwise.
* An invalid node is represented by a `myList' field with the value null.
* Sentinel nodes are invalid, and nodes that don't belong to a list are
* also invalid.
*
* @return true if this node is valid; false otherwise.
*
* Performance: runs in O(1) time.
*/
public boolean isValidNode() {
return myList != null;
}
/**
* next() returns the node following this node. If this node is invalid,
* throws an exception.
*
* @return the node following this node.
* @exception InvalidNodeException if this node is not valid.
*
* Performance: runs in O(1) time.
*/
public ListNode next() throws InvalidNodeException {
if (!isValidNode()) {
throw new InvalidNodeException("next() called on invalid node");
}
return next;
}
/**
* prev() returns the node preceding this node. If this node is invalid,
* throws an exception.
*
* @return the node preceding this node.
* @exception InvalidNodeException if this node is not valid.
*
* Performance: runs in O(1) time.
*/
public ListNode prev() throws InvalidNodeException {
if (!isValidNode()) {
throw new InvalidNodeException("prev() called on invalid node");
}
return prev;
}
/**
* insertAfter() inserts an item immediately following this node. If this
* node is invalid, throws an exception.
*
* @param item the item to be inserted.
* @exception InvalidNodeException if this node is not valid.
*
* Performance: runs in O(1) time.
*/
public void insertAfter(Object item) throws InvalidNodeException {
if (!isValidNode()) {
throw new InvalidNodeException("insertAfter() called on invalid node");
}
DListNode insertNode = ((DList) this.myList).newNode(item, (DList) this.myList, this, this.next);
this.next.prev = insertNode;
this.next = insertNode;
this.myList.size++;
}
/**
* insertBefore() inserts an item immediately preceding this node. If this
* node is invalid, throws an exception.
*
* @param item the item to be inserted.
* @exception InvalidNodeException if this node is not valid.
*
* Performance: runs in O(1) time.
*/
public void insertBefore(Object item) throws InvalidNodeException {
if (!isValidNode()) {
throw new InvalidNodeException("insertBefore() called on invalid node");
}
DListNode insertNode = ((DList) this.myList).newNode(item,(DList) this.myList,this.prev,this);
this.prev.next = insertNode;
this.prev = insertNode;
this.myList.size++;
}
/**
* remove() removes this node from its DList. If this node is invalid,
* throws an exception.
*
* @exception InvalidNodeException if this node is not valid.
*
* Performance: runs in O(1) time.
*/
public void remove() throws InvalidNodeException {
if (!isValidNode()) {
throw new InvalidNodeException("remove() called on invalid node");
}
this.prev.next = this.next;
this.next.prev = this.prev;
myList.size--;
// Make this node an invalid node, so it cannot be used to corrupt myList.
myList = null;
// Set other references to null to improve garbage collection.
next = null;
prev = null;
}
}
(1)第一种方法是看似常规的实现,但是它的封装性并不好,如果调用l.remove(n)方法,若n并不在l中,这将恶意的修改size,继而破坏链表的invarient(size总是对的)。因此混乱搭配链表和节点将导致invarient被破坏。如果每次都是遍历链表来判断n是否在链表中这样执行效率很低,解决这种问题的方法是第二种实现,在DListNode中加入myList域,指向它所在的DList,并且将remove等方法移到DListNode中,这样从根本上杜绝了破坏invarient的问题。
(2)对于第一种实现,如果先调用l.remove(n)再调用l.insertAfter(i,n)将会出现如下图的结果:
这种将破坏x.next == y,then y.prev == x的invarient。虽然在删除的时候可以将n.prev与n.next设置成null以避免这种问题,但是在调用n.insertAfter()的时候依然会破坏size.
所以第二种实现方法中当删除n后,将n的myList设置成null,当调用n.insertAfter()是将抛InvalidNodeException。