What is a set?
List data structures as sets:
// We need to import the class LinkedList from here: import java.util.*; public class SetExample { public static void main (String[] argv) { // Create an instance of the data structure. LinkedList<String> favoriteShows1 = new LinkedList<String>(); // Put some elements in so that it becomes a set of strings. favoriteShows1.add ("Yes minister"); favoriteShows1.add ("Seinfeld"); favoriteShows1.add ("Cheers"); favoriteShows1.add ("Frasier"); favoriteShows1.add ("Simpsons"); // Create a second instance and add some elements. LinkedList<String> favoriteShows2 = new LinkedList<String>(); favoriteShows2.add ("Mad about you"); favoriteShows2.add ("Seinfeld"); favoriteShows2.add ("Frasier"); favoriteShows2.add ("Cosby show"); // Compute set intersection and the difference favoriteShows1-favoriteShows2 in separate methods: computeIntersection (favoriteShows1, favoriteShows2); computeDifference (favoriteShows1, favoriteShows2); } static void computeIntersection (LinkedList<String> listA, LinkedList<String> listB) { System.out.println ("Intersection:"); for (int i=0; i < listA.size(); i++) { String s = listA.get(i); // To be in the intersection, s needs to be in both sets. if ( listB.contains(s) ) { System.out.println (" " + s); } } } static void computeDifference (LinkedList<String> listA, LinkedList<String> listB) { System.out.println ("Difference: "); for (int i=0; i < listA.size(); i++) { String s = listA.get(i); // If s is not in B, it's in the difference. if ( ! listB.contains(s) ) { System.out.println (" " + s); } } } }Note:
LinkedList<String> favoriteShows1 = new LinkedList<String>();
In-Class Exercise 1: Download this program and implement union.
A similar example with integers:
import java.util.*; public class SetExample3 { public static void main (String[] argv) { // Create an instance of a linked list and add some data. LinkedList<Integer> oddGuys = new LinkedList<Integer>(); oddGuys.add (1); oddGuys.add (3); oddGuys.add (5); oddGuys.add (7); oddGuys.add (9); // Another set. LinkedList<Integer> primes = new LinkedList<Integer>(); primes.add (1); primes.add (2); primes.add (3); primes.add (5); primes.add (7); // Set intersection and difference. computeIntersection (oddGuys, primes); computeDifference (oddGuys, primes); } static void computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB) { System.out.println ("Intersection:"); for (int i=0; i<listA.size(); i++) { Integer K = listA.get(i); // To be in the intersection, K needs to be in both sets. if ( listB.contains(K) ) { System.out.println (" " + K); } } } static void computeDifference (LinkedList<Integer> listA, LinkedList<Integer> listB) { System.out.println ("Difference: "); for (int i=0; i<listA.size(); i++) { Integer K = listA.get(i); // If K is not in B, it's in the difference. if ( ! listB.contains(K) ) { System.out.println (" " + K); } } } }
We'll modify the Integer example to actually compute the intersection and difference sets instead of printing them:
import java.util.*; public class SetExample4 { public static void main (String[] argv) { // Create an instance of a linked list and add some data. LinkedList<Integer> oddGuys = new LinkedList<Integer>(); oddGuys.add (1); oddGuys.add (3); oddGuys.add (5); oddGuys.add (7); oddGuys.add (9); // Another set. LinkedList<Integer> primes = new LinkedList<Integer>(); primes.add (1); primes.add (2); primes.add (3); primes.add (5); primes.add (7); // Set intersection and difference. LinkedList<Integer> intersection = computeIntersection (oddGuys, primes); // Note use of toString() in LinkedList. System.out.println ("Intersection: " + intersection); LinkedList<Integer> difference = computeDifference (oddGuys, primes); System.out.println ("Difference: " + difference); } static LinkedList<Integer> computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB) { LinkedList<Integer> listC = new LinkedList<Integer>(); for (int i=0; i<listA.size(); i++) { Integer K = listA.get(i); // To be in the intersection, K needs to be in both sets. if ( listB.contains(K) ) { listC.add (K); } } return listC; } static LinkedList<Integer> computeDifference (LinkedList<Integer> listA, LinkedList<Integer> listB) { LinkedList<Integer> listC = new LinkedList<Integer>(); for (int i=0; i<listA.size(); i++) { Integer K = listA.get(i); // If K is not in B, it's in the difference. if ( ! listB.contains(K) ) { listC.add (K); } } return listC; } }Note:
static LinkedList<Integer> computeIntersection (LinkedList<Integer> listA, LinkedList<Integer> listB) { // ... }
LinkedList<Integer> intersection = computeIntersection (oddGuys, primes); System.out.println ("Intersection: " + intersection);
In-Class Exercise 2: Download this program and implement union so that it returns a list containing the union.
In-Class Exercise 3: Download SetExample6.java, DataTool.java, DataSet.java, and the text file datafortwo. Then, do the following:
Recall the methods that we used in the LinkedList class:
Thus, if we were to create our own data structure to do this, it might look something like:
public class OurList { public void add (String s) { // ... } public int size () { // ... } public String get (int i) { // ... } // ... contains() method ... }
In-Class Exercise 4: What is the signature of the contains method that should be in the template above?
We will implement such a data structure using arrays:
public class OurListUsingArrays { // This is the array in which we'll store strings. String[] strings = new String [100];; // Initially, there are none. int numStrings = 0; public void add (String s) { if (numStrings < 100) { strings[numStrings] = s; numStrings ++; } } public int size () { return numStrings; } public String get (int i) { return strings[i]; } public boolean contains (String s) { // Note: we need to use numStrings instead of strings.length for (int i=0; i < numStrings; i++) { if ( strings[i].equalsIgnoreCase(s) ) { return true; } } return false; } }Note:
import java.util.*; public class SetExample7 { public static void main (String[] argv) { // Create an instance of the data structure. OurListUsingArrays favoriteShows1 = new OurListUsingArrays(); favoriteShows1.add ("Yes minister"); favoriteShows1.add ("Seinfeld"); favoriteShows1.add ("Cheers"); favoriteShows1.add ("Frasier"); favoriteShows1.add ("Simpsons"); // Create a second instance and add some elements. OurListUsingArrays favoriteShows2 = new OurListUsingArrays(); favoriteShows2.add ("Mad about you"); favoriteShows2.add ("Seinfeld"); favoriteShows2.add ("Frasier"); favoriteShows2.add ("Cosby show"); // Compute set intersection and the difference favoriteShows1-favoriteShows2 computeIntersection (favoriteShows1, favoriteShows2); } static void computeIntersection (OurListUsingArrays listA, OurListUsingArrays listB) { System.out.println ("Intersection:"); for (int i=0; i < listA.size(); i++) { String s = listA.get(i); // To be in the intersection, s needs to be in both sets. if ( listB.contains(s) ) { System.out.println (" " + s); } } } }
In-Class Exercise 5: Download OurListUsingArrays.java and modify SetExample8.java to compute unions.
About our implementation:
A linked list solves the "unknown size" problem:
To see how a linked list works, we'll first examine this piece of code:
// We'll use instances of this object in main() below. class ListItem { String data; ListItem next; } public class StrangeExample { public static void main (String[] argv) { // Make one instance and put a string in the data field. ListItem first = new ListItem(); first.data = "Yes minister"; // Make a second one. ListItem second = new ListItem(); second.data = "Seinfeld"; // Now link them: make the first one point to the second. first.next = second; // Make a third and make the second point to the third. ListItem third = new ListItem(); third.data = "Cheers"; second.next = third; // Make a fourth etc. ListItem fourth = new ListItem(); fourth.data = "Frasier"; third.next = fourth; ListItem last = new ListItem(); last.data = "Simpsons"; fourth.next = last; // Now print. Note the extensive use of the dot-operator. System.out.println ("First: " + first.data); System.out.println ("Second: " + first.next.data); System.out.println ("Third: " + first.next.next.data); System.out.println ("Fourth: " + first.next.next.next.data); System.out.println ("Last: " + first.next.next.next.next.data); System.out.println ("Last (alt): " + last.data); } }
In-Class Exercise 6: Draw the memory picture after each new ListItem has been created.
Instead of using the dot-operator in sequence, we'll use a different approach:
class ListItem { String data; ListItem next; } public class StrangeExample2 { public static void main (String[] argv) { // Make one instance and put a string in the data field. ListItem first = new ListItem(); first.data = "Yes minister"; // This is also the last one, right now. ListItem last = first; // Make a second one. ListItem nextOne = new ListItem(); nextOne.data = "Seinfeld"; // Now link them: make the first one point to the second. last.next = nextOne; // Advance the "last" pointer. last = nextOne; // Make a third and make the second point to the third. nextOne = new ListItem(); nextOne.data = "Cheers"; last.next = nextOne; last = nextOne; // Make a fourth etc. nextOne = new ListItem(); nextOne.data = "Frasier"; last.next = nextOne; last = nextOne; // Last one. nextOne = new ListItem(); nextOne.data = "Simpsons"; last.next = nextOne; last = nextOne; // Now print by repeatedly advancing the pointer. ListItem listPointer = first; System.out.println ("First: " + listPointer.data); listPointer = listPointer.next; System.out.println ("Second: " + listPointer.data); listPointer = listPointer.next; System.out.println ("Third: " + listPointer.data); listPointer = listPointer.next; System.out.println ("Fourth: " + listPointer.data); listPointer = listPointer.next; System.out.println ("Last: " + listPointer.data); } }Note:
The repetition in printing suggests a loop:
class ListItem { String data; ListItem next; } public class StrangeExample3 { public static void main (String[] argv) { // Make one instance and put a string in the data field. ListItem first = new ListItem(); first.data = "Yes minister"; ListItem last = first; // Make a second one. ListItem nextOne = new ListItem(); nextOne.data = "Seinfeld"; last.next = nextOne; last = nextOne; // Make a third and make the second point to the third. nextOne = new ListItem(); nextOne.data = "Cheers"; last.next = nextOne; last = nextOne; // Make a fourth etc. nextOne = new ListItem(); nextOne.data = "Frasier"; last.next = nextOne; last = nextOne; // Last one. nextOne = new ListItem(); nextOne.data = "Simpsons"; last.next = nextOne; last = nextOne; // Now print by repeatedly advancing the pointer - in a simple loop. ListItem listPointer = first; while (listPointer != null) { System.out.println (listPointer.data); listPointer = listPointer.next; } } }Note:
while (listPointer != null) { // ... }
In-Class Exercise 7: Download StrangeExample3.java and add the line
System.out.println (listPointer);inside the while-loop. Now draw the list with actual memory addresses and show how the variable listPointer advances along the list.
Can the code for adding new items also be compacted?
⇒
Yes, indeed:
class ListItem { String data; ListItem next; } public class StrangeExample4 { static ListItem first = null; static ListItem last = null; public static void main (String[] argv) { // Make one instance and put a string in the data field. first = new ListItem(); first.data = "Yes minister"; last = first; // Add the rest. add ("Seinfeld"); add ("Cheers"); add ("Frasier"); add ("Simpsons"); // Now print by repeatedly advancing the pointer - in a simple loop. ListItem listPointer = first; while (listPointer != null) { System.out.println (listPointer.data); listPointer = listPointer.next; } } static void add (String s) { // Make a new instance (new list node) and add the data. ListItem nextOne = new ListItem(); nextOne.data = s; // Make the current last one point to the new one. last.next = nextOne; // Adjust the last pointer. last = nextOne; } }
We're now in position to create our own linked-list:
class ListItem { String data; ListItem next; } public class ListWithLinks { // Instance variables. ListItem front = null; ListItem rear = null; // To keep track of the size. int numItems = 0; public void add (String s) { if (front == null) { // The special case of an empty list needs to be handled differently. front = new ListItem (); front.data = s; rear = front; rear.next = null; } else { // Just like before: ListItem nextOne = new ListItem (); nextOne.data = s; rear.next = nextOne; rear = nextOne; } numItems ++; } public int size () { return numItems; } public String get (int i) { // Sanity check: if (i >= numItems) { return null; } // Otherwise, count up to the i-th item. int count = 0; ListItem listPtr = front; while (count < i) { listPtr = listPtr.next; count ++; } return listPtr.data; } public boolean contains (String s) { // Sanity check. if (front == null) { return false; } // Start from the front and walk down the list. If it's there, // we'll be able to return true from inside the loop. ListItem listPtr = front; while (listPtr != null) { if ( listPtr.data.equals(s) ) { return true; } listPtr = listPtr.next; } return false; } }
This class itself will be used in "main" (or elsewhere) as a data structure:
public class ListWithLinksExample { public static void main (String[] argv) { ListWithLinks favoriteShows1 = new ListWithLinks(); favoriteShows1.add ("Yes minister"); favoriteShows1.add ("Seinfeld"); favoriteShows1.add ("Cheers"); favoriteShows1.add ("Frasier"); favoriteShows1.add ("Simpsons"); ListWithLinks favoriteShows2 = new ListWithLinks(); favoriteShows2.add ("Mad about you"); favoriteShows2.add ("Seinfeld"); favoriteShows2.add ("Frasier"); favoriteShows2.add ("Cosby"); computeIntersection (favoriteShows1, favoriteShows2); } static void computeIntersection (ListWithLinks listA, ListWithLinks listB) { System.out.println ("Intersection:"); for (int i=0; i < listA.size(); i++) { // Calls the size() method in ListWithLinks String s = listA.get(i); // Calls the get() method if ( listB.contains(s) ) { // The contains() method System.out.println (" " + s); } } } }Note:
In-Class Exercise 8: Download ListWithLinks2.java and ListWithLinksExample2.java and implement the printList() method in ListWithLinks2 to print out the list. The code in ListWithLinksExample2 calls this method.
Conceptual view of a linked-list:
We will add a couple more methods ("features") to our list:
class ListItem { // ... } public class OurLinkedList { // ... same as before ... public void add (String s) { // ... } public int size () { // ... } public String get (int i) { // ... } public boolean contains (String s) { // ... } public String toString () { if (front == null) { return "empty"; } // Put all the elements (data only) into the string. String s = "["; ListItem listPtr = front; while (listPtr != null) { s += " \"" + listPtr.data + "\""; listPtr = listPtr.next; } return s + "]"; } public void printWithAddresses () { if (front == null) { return; } ListItem listPtr = front; while (listPtr != null) { // listPtr's default toString() prints out the memory address. System.out.println (" \"" + listPtr.data + "\" at address " + listPtr); listPtr = listPtr.next; } } }Note:
In-Class Exercise 9: Add a toString() method to ListItem above, and see what happens.
About the linked-list we've seen so far:
public class SinglyLinkedListProblem { public static void main (String[] argv) { // Some list code: ListItem front = new ListItem (); front.next = new ListItem (); rear = front.next; // Given rear, can one print the element before it? printPrevNode (rear); } static void printPrevNode (ListItem listPtr) { // How do we go to whichever node comes before listPtr? } }
In a doubly-linked list:
class ListItem { String data; ListItem next; // To point to next node in list. ListItem prev; // To point to the previous node in the list. }
class ListItem { String data; ListItem next; // To point to next node in list. ListItem prev; // To point to the previous node in the list. } public class DoublyLinkedList { // Instance variables. ListItem front = null; ListItem rear = null; int numItems = 0; public void add (String s) { if (front == null) { // Similar to singly-linked list, except for setting rear.prev front = new ListItem (); front.data = s; rear = front; rear.next = null; rear.prev = null; // Must set this correctly. } else { // Make new ListItem and set its fields correctly. ListItem nextOne = new ListItem (); nextOne.data = s; nextOne.next = null; nextOne.prev = rear; // Adjust the next pointer of the current last one, and adjust rear itself. rear.next = nextOne; rear = nextOne; } numItems ++; } public int size () { // ... } public String get (int i) { // ... same as in singly-linked list ... } public boolean contains (String s) { // ... same as in singly-linked list ... } public String toString () { // ... } }Note:
In-Class Exercise 10: Download DoublyLinkedList2.java and DoublyLinkedListExample2.java and implement a method to print the list in reverse (starting from the rear).
Next, let's examine what needs to change to build a doubly-linked list that can store int's.
In-Class Exercise 11: Download DoublyLinkedIntList.java and DoublyLinkedListExample3.java and implement a doubly-linked list to hold integers. You can copy over the code in DoublyLinkedList above into your DoublyLinkedIntList to begin with, and then change methods/data etc so that the list stores int's.
There are many ways by which we might want to delete a particular element:
DoublyLinkedList favoriteShows = new DoublyLinkedList(); favoriteShows.add ("Crocodile Hunter"); // ... add more stuff ... favoriteShows.delete ("Crocodile Hunter");
DoublyLinkedList favoriteShows = new DoublyLinkedList(); favoriteShows.add ("Crocodile Hunter"); // ... add more stuff ... favoriteShows.delete (0); // Delete 0-th element.
class ListItem { // ... } public class DoublyLinkedList4 { // ... public void add (String s) { // ... } public int size () { // ... } public String get (int i) { // ... } public boolean contains (String s) { // ... } public String toString () { // ... } // Find String s and delete it if it occurs in the list. public void delete (String s) { ListItem listPtr = front; while ( (listPtr != null) && (! listPtr.data.equals(s)) ) { listPtr = listPtr.next; } // If it's not there, return. if (listPtr == null) { return; } // Otherwise delete: four cases. if (front == rear) { // Case 1: only one element. front = rear = null; } else if (listPtr == front) { // Case 2: we're deleting from the front. front = listPtr.next; front.prev = null; } else if (listPtr == rear) { // Case 3: delete the last element. rear = listPtr.prev; rear.next = null; } else { // Case 4: In the middle: stitch the prev and next nodes together. listPtr.prev.next = listPtr.next; listPtr.next.prev = listPtr.prev; } numItems --; } // Delete the element at a particular position in the list public void delete (int i) { // Check for bad input. if ( (i < 0) || (i >= numItems) ) { return; } // Find the i-th element. int count = 0; ListItem listPtr = front; while ( (listPtr != null) && (count != i) ) { listPtr = listPtr.next; count ++; } // Otherwise delete: four cases. if (front == rear) { // Case 1: only one element. front = rear = null; } else if (listPtr == front) { // Case 2: we're deleting from the front. front = listPtr.next; front.prev = null; } else if (listPtr == rear) { // Case 3: delete the last element. rear = listPtr.prev; rear.next = null; } else { // Case 4: In the middle: stitch the prev and next nodes together. listPtr.prev.next = listPtr.next; listPtr.next.prev = listPtr.prev; } numItems --; } }
In-Class Exercise 12: Modify DoublyLinkedList4.java above to print out the addresses of the node-to-be-deleted, along with the nodes on either side. Then, draw "before" and "after" lists complete with addresses. Use DoublyLinkedListExample4.java as the class with main().
Deletion in a singly-linked list:
In-Class Exercise 13: Write on paper the code needed to search the list for both the item-to-be-deleted and the node prior to it. Then write on paper the code needed for deletion.
In-Class Exercise 14:
Implement your code in OurLinkedList2.java
and test it with OurLinkedListExample2.java.