Download presentation
Presentation is loading. Please wait.
Published byNora Jenkins Modified over 6 years ago
1
Practicum 1: - Persistent vs. destructive lists - Java interfaces
Fundamental Data Structures and Algorithms Peter Lee January 22, 2004
2
Reminders HW2 is out Read due on Monday at 11:59pm
Chapter 17 (this should be review)
3
Integer lists in Java An integer list is either public class List {
int head; List tail; public List(int n, List l) { head = n; tail = l; } An integer list is either an empty list, or an integer paired with an integer list
4
Implementing length() and add()
public class ListOps { public static int length (List l) { if (l==null) return 0; else return 1 + length(l.tail); } public static List add (int n, List l) { if (l==null) return new List(n, null); else return new List(l.head, add(n, l.tail));
5
This version is in two classes…
public class List { int head; List tail; public List(int n, List l) { head = n; tail = l; } Notice use of static methods public class ListOps { public static int length (List l) { … } public static List add (int n, List l) public static List reverse (List l) }
6
Using list objects … myList1 = new List(3,null);
myList2 = myList1.add(9); … ListOps.length(myList1) … What does this return?
7
Choice 1: Destructively modifying L
myList1 nil
8
Choice 1: Destructively modifying L
myList1 nil
9
Choice 1: Destructively modifying L
myList1 nil myList2
10
Choice 2: Preserving the old list
myList1 nil
11
Choice 2: Preserving the old list
myList1 nil nil
12
Choice 2: Preserving the old list
myList1 nil nil myList2
13
Which choice did we implement?
public class ListOps { public static int length (List l) { if (l==null) return 0; else return 1 + length(l.tail); } public static List add (int n, List l) { if (l==null) return new List(n, null); else return new List(l.head, add(n, l.tail));
14
Which choice is the right one?
Should add() be destructive (choice 1) or not (choice 2)? Does it matter? Why or why not?
15
Which one is right? Non-destructive operations are often safer and avoid strange bugs But often the object-oriented programming style encourages the use of destructive operations we’ll see this shortly The bottom line: Neither is “right” – both are useful and the choice must be made carefully
16
A destructive version (other versions also possible)
public class List { int head; List tail; … public List add (int n) { if (tail==null) tail = new List(n,null); else tail = tail.add(n); } Note: No static methods Note: No ListOps class Q: What about empty lists? Note: the tail is modified destructively
17
Implementing reverse()
public class ListOps { … public static List reverse(List l) { if (l==null) return null; else { List r = reverse(l.tail); return add(l.head, r); }
18
How many list cells? Is this version of reverse() destructive or not?
How many list objects are there after invoking reverse()? [If you are a C/C++ hacker, be careful – We aren’t flipping pointers here!]
19
Faster reverse? Question:
Can you write a version of nondestructive reverse() that runs in just n steps? It should have the same behavior as the reverse we have shown here, i.e., it should not destroy the original list.
20
Faster nondestructive reverse()
public static List reverse(List l) { return rev(l, null); } private static List rev(List l, List result) { if (l==null) return result; else return rev(l.tail, new List(l.head, result));
21
Linear-time destructive reverse()?
Exercise: Write a linear-time destructive reverse()
22
Objects Java is an object-oriented programming language.
This means that it encourages organizing a program’s functionality around the idea of an “object”. But the code for Lists that we have presented here is not really object- oriented…
23
Our List code is not object-oriented
public class List { int head; List tail; public List(int n, List l) { head = n; tail = l; } Essentially a C/C++ struct Essentially C/C++ code public class ListOps { public static int length (List l) { … } public static List add (int n, List l) public static List reverse (List l) }
24
Weiss’ lists (Ch.17) public class ListNode { Object element; ListNode next; } public class LinkedList { private ListNode header; public LinkedList() { header = new ListNode(); } public isEmpty () { return header.next == null; } … } Notice that Weiss does not use null to represent the empty list. Why not?
25
The empty list is not an object!
… myList1 = new List(3,null); myList2 = null; … myList1.length() … … myList2.length() … Because null is not an object, method invocations can fail. Yuck!
26
Counters As a first example of object-oriented programming, let’s define counters. No, not these kinds of counters…
27
Counters Let’s define a counter to be an object that maintains a count that can be initialized and incremented. inc resetCount readCount
28
Counters Let’s define a counter to be an object that maintains a count that can be initialized and incremented. inc resetCount readCount 1
29
Counters Let’s define a counter to be an object that maintains a count that can be initialized and incremented. inc resetCount readCount 1 1
30
Counters Let’s define a counter to be an object that maintains a count that can be initialized and incremented. inc resetCount readCount 1
31
Counters Informally, a counting object
can be initialized, can be incremented, and can be read. We can specify these things in Java by writing a Java interface.
32
The Countable interface
/** Interface for counting objects. */ public interface Countable { /** Increment the internal counter. */ public void inc(); /** Reset the internal counter to 0. */ public void resetCount(); /** Return the internal count. */ public int readCount(); } Often an adjective
33
Interfaces A Java interface is a kind of specification for classes of objects. In this example, the interface specifies that any class of “countable” objects must provide the inc(), resetCount(), and readCount() methods.
34
A simple implementation
/** Simple counting objects. */ public class SimpleCounter implements Countable { /** Initialization for new counters. */ public SimpleCounter () { resetCount(); } /** Reset the counter to 0. */ public void resetCount () { count = 0; } /** Increment the counter. */ public void inc () { count++; } /** Return the current count. */ public int readCount () { return count; } private int count; } The constructor method. Private instance variable.
35
Using SimpleCounter Creation of new SimpleCounter objects.
/** A program that uses a SimpleCounters. */ public class Main { public static void main (String args[]) { int n = 99; Countable c = new SimpleCounter(); Countable d = new SimpleCounter(); c.inc(); d.inc(); d.inc(); System.out.println (n + c.readCount() + d.readCount()); } Creation of new SimpleCounter objects.
36
Creating new objects c Countable c = new SimpleCounter(); inc
inc resetCount readCount Countable c = new SimpleCounter();
37
Creating new objects c d Countable c = new SimpleCounter();
inc resetCount readCount Countable c = new SimpleCounter(); Countable d = new SimpleCounter(); d inc resetCount readCount
38
Our little program Files we have created for our little program:
Countable.java The interface specification. SimpleCounter.java An implementation of the interface. Main.java A client program.
39
Why use interfaces? Strictly speaking, Java does not require us to make use of an interface for this program. public class SimpleCounter implements Countable { … }
40
Why use interfaces? Strictly speaking, Java does not require us to make use of an interface for this program. public class SimpleCounter { … } Leaving out the implements declaration is OK with Java.
41
Why use interfaces? Interfaces allow us to separate interface from implementation. If properly designed, then clients assume only the interface. Clients don’t have to change, even if the underlying implementations change.
42
Interface vs implementation
open >>(int) close setf write read
43
Interface vs implementation
open >>(int) close setf write read O(n2)
44
Abstract data types When the allowable operations on a type of data are controlled in this manner, we say that we are using abstract data types. In our first example, we have defined the ADT of counters.
45
Abstract data types Abstract data types can make it easier to…
…make use of pre-existing code. …work with a team. …maintain code in the long run. …write down the results of your careful thinking about the problem.
46
Another counting class
/** Counting objects that double every time. */ public class DoublingCounter implements Countable { /** Initialization for new counters. */ public DoublingCounter () { resetCount(); } /** Reset the counter to 1. */ public void resetCount () { count = 1; } /** Increment the counter. */ public void inc () { count *= 2; } /** Return the current count. */ public int readCount () { return count; } private int count; }
47
A digression Why the /** … */ comments?
These are special documentation comments that are used for public classes and members of classes. When used properly, the javadoc command will create web pages for your code automatically. (See the hw1 code.)
48
Back to Lists
49
Lists of integers public interface IntList {
the length of the list */ public int length (); /** Add n to the end of the list */ public IntList add (int n); an Iterator */ public IntIterator iterator (); … }
50
Iterators public interface IntIterator {
true if more elements */ public boolean hasNext (); the next element */ public int next (); }
51
Many interfaces are pre-defined
For convenience, the J2SE defines many useful interfaces See, especially, the java.util package
52
What about our inductively defined lists?
53
Inductive definitions
An integer list is either an empty list, or an integer paired with an integer list
54
Lists of integers public interface IntList {
the length of the list */ public int length (); /** Add n to the end of the list */ public IntList add (int n); an Iterator */ public IntIterator iterator (); … }
55
Empty lists public class EmptyIntList implements IntList {
public int length () { return 0; } public IntList add (int n) { return new IntListCell(n); } public IntIterator iterator () { …
56
Non-empty lists public class IntListCell implements IntList {
private int element; private IntList next; public IntListCell (int n) { element = n; next = new EmptyIntList(); } public int length () { return 1 + next.length(); public IntList add (int n) { next = next.add(n); return this; public IntIterator iterator () { …
57
More inductive definitions
The length of a list L is 0, if L is the empty list 1 + length of the tail of L, otherwise Base case Inductive case
58
Length Base case Inductive case
public class EmptyIntList implements IntList { … public int length() { return 0; } } public class IntListCell implements IntList { return 1 + next.length(); } Base case Inductive case
59
Reverse The reversal of a list L is: L, if L is empty
n appended to M, otherwise where n is the first element of L, and M is the reversal of the tail of L
60
Reverse public class EmptyIntList implements IntList { …
public IntList reverse() { return this; } } public class IntListCell implements IntList { IntList t = next.reverse(); return t.add(element); }
61
Implementing iterators
The iterator() method requires the creation of an IntIterator object This means we need to define a new class Furthermore, this class will want to get at the private parts of the list object So, we will define the iterator class inside the list classes
62
Inner classes public class EmptyIntList implements IntList { …
public IntIterator iterator() { return new Itr(); } class Itr implements IntIterator{ public boolean hasNext () { return false; public int next () { throw new IntIteratorException();
63
Inner classes public class IntListCell implements IntList { …
public IntIterator iterator() { return new Itr(); } class Itr implements IntIterator{ private int nextCell; … left as an exercise …
64
Stacks and Queues, Revisited
65
A Stack interface public interface Stack { public void push(Object x);
public void pop(); public Object top(); public boolean isEmpty(); public void makeEmpty(); }
66
Stacks are LIFO Push operations: e d c b a
67
Stacks are LIFO Pop operation: e
Last element that was pushed is the first to be popped. d c b a
68
A Queue interface public interface Queue {
public void enqueue(Object x); public Object dequeue(); public boolean isEmpty(); public void makeEmpty(); }
69
Queues are FIFO back front k r q c m
70
Queues are FIFO Enqueue operation: back front k r q c m y
71
Queues are FIFO Enqueue operation: back front y k r q c m
72
Queues are FIFO Dequeue operation: back front y k r q c m
73
Implementing stacks, 1 Linked representation.
All operations constant time. c b a
74
Implementing stacks, 2 An alternative is to use an array-based representation. What are some advantages and disadvantages of an array-based representation? a b c top
75
Array representation of stacks
But what to do when the array overflows? Can we still get constant-time operations? We could have a linked list of arrays. Is there another way?
76
An idea Let’s try doubling the size of the array every time it overflows. In more detail: Start off with an array of size n. When the array overflows, allocate a new array of size 2n, copy all of the previous n elements to it. Set n=2n, and then continue by using the new array.
77
A Big Hint Whenever you see something doubling, immediately think of powers of 2: 20, 21, 22, 23, 24, … Opportunity for induction…
78
Question 1 In the worst case, what is the running time of the push operation? I.e., how many copying operations might be needed? Answer: linear time. If a push() operation causes an overflow, then potentially all n elements on the stack must be copied.
79
Question 2 In the worst case, when starting with an empty stack, what is the average running time for all of the push operations in any given sequence of legal stack operations?
80
What contributes What contributes to the running time? The push itself
Possibility of creating a new array Copying all of the elements to a new array Constant-time overhead This is the overhead that we have to worry about
81
Informal analysis Let’s try counting a sequence of push operations, starting with array of size 0. push: 0 copies (create new array of size 20) push: 1 copy (into a new array of size 21) push: 2 copies (into a new array of size 22) push push: 4 copies (into a new array of size 23) push push push push: 8 copies (into a new array of size 24)
82
Informal analysis, cont’d
For 2n push operations, a grand total of 2n-1+2n-2+2n-3…+20 = 2n copy operations are needed. So, on average, one copy is needed for every push operation.
83
Question 2, again In the worst case, when starting with an empty stack, what is the average running time for all of the push operations in any given sequence of legal stack operations? Answer: constant time Each push might require the creation of a new array plus, on average, a copy.
84
Amortized running time
We say that our array-based implementation of stacks runs in amortized constant time. The J2SE java.util.ArrayList class is based on this representation.
85
A question to ponder… Question: How would you use an implementation of the Stack interface to implement the Queue interface? What would be the running time of the operations?
86
A queue from two stacks Enqueue: Dequeue: j a
What happens when the stack on the right becomes empty? i b h c g d f e
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.