HW5一开始做不出来的同学可以继续先看Lecture,Encapsulated List那一节老师会回顾一下HW5,对作业有启发思路的作用。
Part1.
HW5的DListNode增加一个myList的field,用于确认node位于哪个list,同时也为后面的isValidNode()服务。
remove()之后需要将myList, prev, next 统统指向null,不然这个node的prev和next仍指向前后,后导致后面如果insertAfter出错。
Part2.
insert(), union(), intersection()等方法中的对比需要使用compareTo(), 因为求并交集时要比较字典序lexicographically,比如“3”在“5”的前面还是后面。回顾一下==, .equals(), compareTo()的用法
a==b | 比较a和b是否指向同一个object |
a.equals(b) | 验证a和b是否相等。允许a.equals(null) |
a.compareTo(b) | 比较a和b的字典序,1. a<b, 返回负数; 2.a=b,返回0; 3.a>b,返回正数. a.compareTo(null)会返回NullPointException |
DListNode
/* DListNode.java */ package com.homework5; /** * 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. * * DO NOT CHANGE THE FOLLOWING FIELD DECLARATIONS. **/ 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"); } // Your solution here. Will look something like your Homework 4 solution, // but changes are necessary. For instance, there is no need to check if // "this" is null. Remember that this node's "myList" field tells you // what DList it's in. You should use myList.newNode() to create the // new node. DListNode new_node = ((DList)myList).newNode(item,(DList)myList,this,this.next); this.next.prev = new_node; this.next = new_node; 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"); } // Your solution here. Will look something like your Homework 4 solution, // but changes are necessary. For instance, there is no need to check if // "this" is null. Remember that this node's "myList" field tells you // what DList it's in. You should use myList.newNode() to create the // new node. DListNode new_node = ((DList)myList).newNode(item,(DList)myList,this.prev,this); this.prev.next = new_node; this.prev = new_node; 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"); } // Your solution here. Will look something like your Homework 4 solution, // but changes are necessary. For instance, there is no need to check if // "this" is null. Remember that this node's "myList" field tells you // what DList it's in. this.prev.next = this.next; this.next.prev = this.prev; this.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; } }
DList
/* DList.java */ package com.homework5; /** * 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. * * DO NOT CHANGE ANY METHOD PROTOTYPES IN THIS FILE. **/ 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. * * DO NOT CHANGE THE FOLLOWING FIELD DECLARATION. **/ 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() { // Your solution here. Similar to Homework 4, but now you need to specify // the `list' field (second parameter) as well. head = newNode(null,null,null,null); head.next = head; head.prev = 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) { // Your solution here. Similar to Homework 4, but now you need to specify // the `list' field (second parameter) as well. DListNode new_node = newNode(item,this,head,head.next); head.next.prev = new_node; head.next = new_node; 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) { // Your solution here. Similar to Homework 4, but now you need to specify // the `list' field (second parameter) as well. DListNode new_node = newNode(item,this,head.prev,head); head.prev.next = new_node; head.prev = new_node; 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".) * * DO NOT CHANGE THIS METHOD. * * @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".) * * DO NOT CHANGE THIS METHOD. * * @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. * * 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 + "]"; } private static void testInvalidNode(ListNode p) { System.out.println("p.isValidNode() should be false: " + p.isValidNode()); try { p.item(); System.out.println("p.item() should throw an exception, but didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.item() should throw an exception, and did."); } try { p.setItem(new Integer(0)); System.out.println("p.setItem() should throw an exception, but didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.setItem() should throw an exception, and did."); } try { p.next(); System.out.println("p.next() should throw an exception, but didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.next() should throw an exception, and did."); } try { p.prev(); System.out.println("p.prev() should throw an exception, but didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.prev() should throw an exception, and did."); } try { p.insertBefore(new Integer(1)); System.out.println("p.insertBefore() should throw an exception, but " + "didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.insertBefore() should throw an exception, and did." ); } try { p.insertAfter(new Integer(1)); System.out.println("p.insertAfter() should throw an exception, but " + "didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.insertAfter() should throw an exception, and did." ); } try { p.remove(); System.out.println("p.remove() should throw an exception, but didn't."); } catch (InvalidNodeException lbe) { System.out.println("p.remove() should throw an exception, and did."); } } private static void testEmpty() { List l = new DList(); System.out.println("An empty list should be [ ]: " + l); System.out.println("l.isEmpty() should be true: " + l.isEmpty()); System.out.println("l.length() should be 0: " + l.length()); System.out.println("Finding front node p of l."); ListNode p = l.front(); testInvalidNode(p); System.out.println("Finding back node p of l."); p = l.back(); testInvalidNode(p); l.insertFront(new Integer(10)); System.out.println("l after insertFront(10) should be [ 10 ]: " + l); } public static void main(String[] argv) { testEmpty(); List l = new DList(); l.insertFront(new Integer(3)); l.insertFront(new Integer(2)); l.insertFront(new Integer(1)); System.out.println("l is a list of 3 elements: " + l); try { ListNode n; int i = 1; for (n = l.front(); n.isValidNode(); n = n.next()) { System.out.println("n.item() should be " + i + ": " + n.item()); n.setItem(new Integer(((Integer) n.item()).intValue() * 2)); System.out.println("n.item() should be " + 2 * i + ": " + n.item()); i++; } System.out.println("After doubling all elements of l: " + l); testInvalidNode(n); i = 6; for (n = l.back(); n.isValidNode(); n = n.prev()) { System.out.println("n.item() should be " + i + ": " + n.item()); n.setItem(new Integer(((Integer) n.item()).intValue() * 2)); System.out.println("n.item() should be " + 2 * i + ": " + n.item()); i = i - 2; } System.out.println("After doubling all elements of l again: " + l); testInvalidNode(n); n = l.front().next(); System.out.println("Removing middle element (8) of l: " + n.item()); n.remove(); System.out.println("l is now: " + l); testInvalidNode(n); n = l.back(); System.out.println("Removing end element (12) of l: " + n.item()); n.remove(); System.out.println("l is now: " + l); testInvalidNode(n); n = l.front(); System.out.println("Removing first element (4) of l: " + n.item()); n.remove(); System.out.println("l is now: " + l); testInvalidNode(n); } catch (InvalidNodeException lbe) { System.err.println ("Caught InvalidNodeException that should not happen." ); System.err.println ("Aborting the testing code."); } } }
Set
package com.homework5;/* Set.java */ /** * A Set is a collection of Comparable elements stored in sorted order. * Duplicate elements are not permitted in a Set. **/ public class Set { /* Fill in the data fields here. */ List setList; /** * Set ADT invariants: * 1) The Set's elements must be precisely the elements of the List. * 2) The List must always contain Comparable elements, and those elements * must always be sorted in ascending order. * 3) No two elements in the List may be equal according to compareTo(). **/ /** * Constructs an empty Set. * * Performance: runs in O(1) time. **/ public Set() { // Your solution here. setList = new DList(); } /** * cardinality() returns the number of elements in this Set. * * Performance: runs in O(1) time. **/ public int cardinality() { // Replace the following line with your solution. return setList.length(); } /** * insert() inserts a Comparable element into this Set. * * Sets are maintained in sorted order. The ordering is specified by the * compareTo() method of the java.lang.Comparable interface. * * Performance: runs in O(this.cardinality()) time. **/ public void insert(Comparable c) { // Your solution here. if (setList.isEmpty()){ setList.insertFront(c); } else { ListNode current = setList.front(); try { while(current != setList.back() && ((Comparable)(current.item)).compareTo(c)<0){ current = current.next(); } if(((Comparable)(current.item)).compareTo(c) > 0){ current.insertBefore(c); } else if(current == setList.back() && ((Comparable)(current.item)).compareTo(c) < 0){ current.insertAfter(c); } } catch (InvalidNodeException e){ System.out.println("Exception caught"); } } } /** * union() modifies this Set so that it contains all the elements it * started with, plus all the elements of s. The Set s is NOT modified. * Make sure that duplicate elements are not created. * * Performance: Must run in O(this.cardinality() + s.cardinality()) time. * * Your implementation should NOT copy elements of s or "this", though it * will copy _references_ to the elements of s. Your implementation will * create new _nodes_ for the elements of s that are added to "this", but * you should reuse the nodes that are already part of "this". * * DO NOT MODIFY THE SET s. * DO NOT ATTEMPT TO COPY ELEMENTS; just copy _references_ to them. **/ public void union(Set s) { // Your solution here. if(s != null && !s.setList.isEmpty()){ ListNode curNode = this.setList.front(); ListNode sNode = s.setList.front(); Comparable curItem, sItem; try { while (curNode.isValidNode() && sNode.isValidNode()){ curItem = (Comparable)curNode.item; sItem = (Comparable)sNode.item; if (curItem.compareTo(sItem) == 0){ curNode = curNode.next(); sNode = sNode.next(); }else if(curItem.compareTo(sItem) < 0){ curNode = curNode.next(); } else { curNode.insertBefore(sNode); curNode = curNode.next(); } } while (sNode.isValidNode()){ sItem = (Comparable)sNode.item; setList.insertBack(sItem); sNode = sNode.next(); } } catch (InvalidNodeException e){ System.out.println("Union Failed"); } } } /** * intersect() modifies this Set so that it contains the intersection of * its own elements and the elements of s. The Set s is NOT modified. * * Performance: Must run in O(this.cardinality() + s.cardinality()) time. * * Do not construct any new ListNodes during the execution of intersect. * Reuse the nodes of "this" that will be in the intersection. * * DO NOT MODIFY THE SET s. * DO NOT CONSTRUCT ANY NEW NODES. * DO NOT ATTEMPT TO COPY ELEMENTS. **/ public void intersect(Set s) { // Your solution here. if (!s.setList.isEmpty() && s != null){ ListNode curNode = this.setList.front(); ListNode sNode = s.setList.front(); Comparable curItem, sItem; try { while(curNode.isValidNode() && sNode.isValidNode()){ curItem = (Comparable)curNode.item; sItem = (Comparable)sNode.item; if (curItem.compareTo(sItem) == 0){ curNode = curNode.next(); sNode = sNode.next(); } else if (curItem.compareTo(sItem) < 0) { if (curNode != setList.back()){ curNode = curNode.next(); curNode.prev().remove(); } else{ curNode.remove(); } } else { sNode = sNode.next(); } } while (curNode.isValidNode()){ if (curNode != setList.back()){ curNode = curNode.next(); curNode.prev().remove(); } else { curNode.remove(); } } }catch (InvalidNodeException e){ System.out.println("Intersect Failed"); } } } /** * toString() returns a String representation of this Set. The String must * have the following format: * { } for an empty Set. No spaces before "{" or after "}"; two spaces * between them. * { 1 2 3 } for a Set of three Integer elements. No spaces before * "{" or after "}"; two spaces before and after each element. * Elements are printed with their own toString method, whatever * that may be. The elements must appear in sorted order, from * lowest to highest according to the compareTo() method. * * WARNING: THE AUTOGRADER EXPECTS YOU TO PRINT SETS IN _EXACTLY_ THIS * FORMAT, RIGHT UP TO THE TWO SPACES BETWEEN ELEMENTS. ANY * DEVIATIONS WILL LOSE POINTS. **/ public String toString() { // Replace the following line with your solution. String p = "{ "; ListNode curNode = setList.front(); try{ while(curNode.isValidNode()){ p = p + curNode.item + " "; curNode = curNode.next(); } }catch (InvalidNodeException e){ System.out.println("toString() Failed"); } p = p + "}"; return p; } public static void main(String[] argv) { Set s = new Set(); s.insert(new Integer(3)); s.insert(new Integer(4)); s.insert(new Integer(3)); System.out.println("Set s = " + s); Set s2 = new Set(); s2.insert(new Integer(4)); s2.insert(new Integer(5)); s2.insert(new Integer(5)); System.out.println("Set s2 = " + s2); Set s3 = new Set(); s3.insert(new Integer(5)); s3.insert(new Integer(3)); s3.insert(new Integer(8)); System.out.println("Set s3 = " + s3); s.union(s2); System.out.println("After s.union(s2), s = " + s); s.intersect(s3); System.out.println("After s.intersect(s3), s = " + s); System.out.println("s.cardinality() = " + s.cardinality()); // You may want to add more (ungraded) test code here. } }