introduction to computer science recursive link operations mergesort unit 18
Post on 21-Dec-2015
235 views
TRANSCRIPT
Introduction to Computer Science
• Recursive Link Operations
• MergeSort
Unit 18Unit 18
18- 2
Let's Use This Encapsulated Definition of a Node Object
class ListNode {private int _value;private ListNode _tail;
public ListNode (int v, ListNode next) {_value = v; _tail = next;
}
public int getValue ( ) {return _value; }
public ListNode getTail ( ) {return _tail;}}
18- 3
Recursive Functions Over Lists
• Linked Lists (and other data structures using links) are well suited for recursive treatment, just as arrays are
• But first, let’s review an iterative treatment of handling lists
• Example: Read in a list of numbers from the user (any length), then print them in reverse order
18- 4
User Enters:
Enter number: 2
Enter number: 3
Enter number: 5
Enter number: 7
Enter number: 11
Enter number: ^D or ^Z
_tail
_value
>>>>
11_tail
_value7
_tail
_value3
>>>>_tail
_value
null
2
>>>>
We create:
_tail
_value5
>>>>
We print:11 7 5 3 2
theList
18- 5
Outline of Solution
class PrintReversed {
static ListNode readReverseList ( ) { … }
static void printReverseList(ListNode n) { … }
public static void main (String[ ] args) {
ListNode theList = readReverseList( );
printReverseList(theList);
}
}
18- 6
Iterative readReverseList( )
static ListNode readReverseList ( ) {int inputval;ListNode front = null;SimpleInput sinp = new SimpleInput(System.in);System.out.print("Enter number: ");inputval = sinp.readInt( );while ( !sinp.eof( ) ) {
front = new ListNode(inputval, front);System.out.print("Enter number: ");inputval = sinp.readInt( );
}System.out.println( );return front;
}
18- 7
Trace It
inputval = sinp.readInt( );while ( !sinp.eof( ) ) {
front = new ListNode(inputval, front);System.out.print("Enter number: ”);inputval = sinp.readInt( );
}
front is null
null
18- 8
Trace It (1)
tail
value
null
2
frontheap
inputval = sinp.readInt( );while ( !sinp.eof( ) ) {
front = new ListNode(inputval, front);System.out.print("Enter number: ”);inputval = sinp.readInt( );
}
Two things are happening:the use of front (and inputval) to initialize the new
node, then the resetting of front to point to the node
18- 9
Trace It (2)
tail
value
nulltail
value
>>>>
3 2
Two things are happening:the use of front (and inputval) to initialize the new
node, then the resetting of front to point to the node
frontheap
inputval = sinp.readInt( );while ( !sinp.eof( ) ) {
front = new ListNode(inputval, front);System.out.print("Enter number: ”);inputval = sinp.readInt( );
}
18- 10
Iterative printReverseList( )
static void printReverseList(ListNode head) {
while ( head != null ) {System.out.print(head.getValue() + " ");head = head.getTail( );
}System.out.println();
}
18- 11
Now Recursive – Add some more Methods to the ListNode class
• Let's add a new methodint length ( )to the ListNode class that computes the length of the list (recursively)
class ListNode {private int _value;private ListNode _tail;
public ListNode (int v, ListNode next) {_value = v; _tail = next;
}
public int length ( ) { … }
public int getValue ( ) { return _value; }
public ListNode getTail ( ) { return _tail; }}
18- 12
It's Easy When You Think Recursively
public int length ( ) {if (_tail == null)
return 1;else
return ( 1 + _tail.length( ) );}
We are sending the length message (recursively) to the tail of the current object (i.e., the object pointed to
by the current object's tail)
18- 13
class ListNode {private int _value;private ListNode _tail;
public ListNode (int v, ListNode next) {_value = v; _tail = next;
}
public String toString ( ) { … }
public int length ( ) { … }
public int getValue ( ) { return _value; }
public ListNode getTail ( ) { return _tail; }}
Let's Try it Again
• Let's add another new methodString toString ( )to the ListNode class that prints the elements of the list, separated by commas
18- 14
It's Easy When You Think Recursively
public String toString ( ) {String myValue = Integer.toString(_value);if (_tail == null) return myValue;else return (myValue + ", " +
_tail.toString( ) );}
Integer.toString( ) converts an integer to a String object. Normally we wouldn't need to do the conversion manually,
except when tail == null and it's the last item in the list.
18- 15
class ListNode {private int _value;private ListNode _tail;
public ListNode (int v, ListNode next) {_value = v; _tail = next;
}
public ListNode nth (int n) { … }
public String toString ( ) { … }
public int length ( ) { … }
public int getValue ( ) { return _value; }
public ListNode getTail ( ) { return _tail; }}
We're Not Done Yet
• Let's add another new method, ListNode nth ( )to the ListNode class that returns a reference to the nth cell in the list
18- 16
It's Easy When You Think Recursively
public ListNode nth (int n) {if (n == 0)
return this;else if (_tail == null)
return null;else
return _tail.nth(n - 1);}
If n is 0, we return the head of the list, namely the object that got the message, i.e., "this". If n is not 0, but the tail is null, the list is too short, and I return
null. Otherwise, I request the n-1th element from my tail object.
18- 17
class ListNode {private int _value;private ListNode _tail;
public ListNode (int v, ListNode next) {_value = v; _tail = next;
}
public void addToEndM (int n) { … }
public ListNode nth (int n) { … }
public String toString ( ) { … }
…}
Let's Add a Mutating List Operation (alters the list)
• Let's add another new, mutating, methodvoid addToEndM (int n) to the ListNode class that adds a new cell (initialized with n) to the end of the list
18- 18
No need to return a value, since it simply alters the end of the list
public void addToEndM (int n) {if (_tail != null)// we're a cell in the middle of the list
_tail.addToEndM(n);else // we're the last cell
_tail = new ListNode(n, null);}
When we're not at the end of the list (that is, _tail != null), we just pass the addToEndM(n) message down
to our tail object. When we are the last cell, we create a new object, initialize it with n and null, then set our
(formerly null) tail to it.
18- 19
OK, One More Mutating List Operation
• Let's add one more new, mutating, methodListNode addInorderM (int n)to the ListNode class that adds a new cell (initialized with n) into the list in the correct numerical order (assuming the list was ordered to begin with)
• Do not insert duplicates
• If we always use addInorderM to add cells to the list, it will remain ordered
18- 20
No need to return a value, since it simply alters the end of the list
public ListNode addInorderM (int n) {if (n < _value)
return ( new ListNode(n, this) );else if (n == _value)
return this;else if (_tail == null) {
_tail = new ListNode(n, null);return this;
}else {
_tail = _tail.addInorderM(n);return this;
}}
18- 21
Case Analyis
• There are four possible situations to consider
• They depend on whether we are in the middle of the list or the end of the list
• They also depend on whether the current cell's value (call it p) compares with the value to be inserted (call it n)
18- 22
Case 1: p is greater than n
We initialize a new node with the value n, point it at the current node ("this"), and return a pointer to it
if (n < _value)return ( new ListNode(n, this) );
_tail
_value
>>>>_tail
_value
>>>>
n p_tail
_value
>>>>
…
18- 23
Case 2: p equals n
We ignore n, since the problem statement said not to insert duplicates
else if (n == _value)return this;
_tail
_value
>>>>
p_tail
_value
>>>>
…
18- 24
Case 3: p is less than n, but the current object's tail is null
We initialize a new node with the value n and the tail null, point the current cell's tail to it, and return a pointer to the current cell
_tail
_value
>>>>_tail
_value
null
np
else if (_tail == null) {_tail = new ListNode(n, null);return this;
}
18- 25
Case 4: p is less than n, and the current object's tail is not null
We pass along the call to the current object's tail, set the current object's tail to whatever is returned, and return a reference to the current object
_tail
_value
>>>>_tail
_value
>>>>
p …_tail
_value
>>>>
…
else {_tail = _tail.addInorderM(n);return this;
}
18- 26
MergeSort
• Exploits the same divide-and-conquer strategy as QuickSort
• Better suited for linked lists
• Divide the linked list in 2 nearly equal-sized parts
• Recursively MergeSort the 2 halves
• Merge the two halves back together
18- 27
MergeSort
_tail
_value
null_tail
_value
>>>>
1 15_tail
_value
>>>>
4_tail
_value
>>>>_tail
_value
>>>>
3 9_tail
_value
>>>>
17 …
1. Split into two equal sized lists
tail
value
>>>>tail
value
>>>>
… …tail
value
>>>>
…tail
value
>>>>tail
value
>>>>
… …tail
value
>>>>
…
tail
value
>>>>tail
value
>>>>
… …tail
value
>>>>
…tail
value
>>>>tail
value
>>>>
… …tail
value
>>>>
…
2. Sort recursively 3. Sort recursively
4. Merge the two sorted liststail
value
nulltail
value
>>>>
15 17tail
value
>>>>
9tail
value
>>>>tail
value
>>>>
3 4tail
value
>>>>
1 …
18- 28
What Makes Up MergeSort?
• We need to be able to carry out two operations
• Splitting a linked list into two pieces
• Merging two ordered linked lists into a single ordered linked list
• Let's look at merging
18- 29
Merging Two SortedLinked Lists
_tail
_value
null_tail
_value
>>>>
5 8_tail
_value
>>>>
3_tail
_value
>>>>_tail
_value
>>>>
1 1_tail
_value
>>>>
0_tail
_value
>>>>
2
_tail
_value
null_tail
_value
>>>>
4 8_tail
_value
>>>>
2_tail
_value
>>>>
1
Gives us:
tail
value
>>>>tail
value
>>>>
2 3tail
value
>>>>
2tail
value
>>>>tail
value
>>>>
1 1tail
value
>>>>
0tail
value
>>>>
1tail
value
nulltail
value
>>>>
8 8tail
value
>>>>
5tail
value
>>>>
4
18- 30
merge( ) will be nonmutating
• We'll write merge as a nonmutating method: ListNode merge (ListNode L)
• One list node will bethe receiver:
• The other list node will bethe argument:
• The returned value will (generally) be a new list node, creating a new list (though parts of the old lists may appear in it)
tail
value
nulltail
value
>>>>
5 8tail
value
>>>>
3tail
value
>>>>tail
value
>>>>
1 1tail
value
>>>>
0tail
value
>>>>
2
tail
value
nulltail
value
>>>>
4 8tail
value
>>>>
2tail
value
>>>>
1
tail
value
>>>>tail
value
>>>>
2 3tail
value
>>>>
2tail
value
>>>>tail
value
>>>>
1 1tail
value
>>>>
0tail
value
>>>>
1tail
value
nulltail
value
>>>>
8 8tail
value
>>>>
5tail
value
>>>>
4
18- 31
merge( )
ListNode merge (ListNode L) { if (L == null)
return this; if (_value < L._value)
if (_tail == null)return new ListNode(_value, L);
elsereturn new ListNode(_value,
_tail.merge(L)); else
return new ListNode(L._value,
merge(L._tail));}
18- 32
Case Analysis of merge( )
• There are four possible situations to consider–The argument L is null
–My value is less than the head of L's value, but my tail is null
–My value is less than the head of L's value, and my tail is not null
–My value is greater than or equal to the head of L's value
18- 33
The argument L is null
Just return myself, i.e., the rest of the first list:
return this;
_tail
_value
null
8_tail
_value
>>>>_tail
_value
>>>>
1 1_tail
_value
>>>>
0
receiver: L:null
_tail
_value
null
8_tail
_value
>>>>_tail
_value
>>>>
1 1_tail
_value
>>>>
0
return:
18- 34
My value is less than the head of L's value, but my tail is null
Build a new ListNode, with my value in it and tail pointing to L
return new ListNode(_value, L);
_tail
_value0
receiver: L:null
_tail
_value
null_tail
_value
>>>>
4 8_tail
_value
>>>>
2_tail
_value
>>>>
1
_tail
_value
>>>>
0
return:
_tail
_value
null_tail
_value
>>>>
4 8_tail
_value
>>>>
2_tail
_value
>>>>
1
L
18- 35
My value is less than the head of L's value, and my tail is not null
Build a new ListNode, with my value in it and tail pointing to result of merge( ), sent to my tail, with L as the argument
return new ListNode(_value, _tail.merge(L));
L:
_tail
_value
>>>>
4_tail
_value
>>>>
2_tail
_value
>>>>
1
_tail
_value
>>>>
0
return: L, the argument
_tail
_value
>>>>
1_tail
_value
>>>>
0
receiver:
… …
_tail
_value
>>>>
4_tail
_value
>>>>
2_tail
_value
>>>>
1
…_tail
_value
>>>>
1
…
merge( ) recursive call to merge
18- 36
My value is greater than or equal to the head of L's value
Build a new ListNode, with L head's value in it and tail pointing to result of merge( ) sent to myself, with L's tail as argument
return new ListNode(L._value, merge(L._tail));
L:
_tail
_value
>>>>
4_tail
_value
>>>>
2_tail
_value
>>>>
1
_tail
_value
>>>>
1
return: L's tail, the argument
_tail
_value
>>>>
4_tail
_value
>>>>
3
receiver:
… …
_tail
_value
>>>>
4_tail
_value
>>>>
2_tail
_value
>>>>
4
…_tail
_value
>>>>
3
…
merge( ) recursive call to merge
18- 37
Splitting a list
• We want to take a linked list, and split it in two, without having to go all the way through, counting nodes, then going half-way through to split
• One list will be 1st, 3rd, 5th, … members
• The second list will be 2nd, 4th, 6th, … members
• Maintain their original relative order
18- 38
Splitting a list
_tail
_value
null_tail
_value
>>>>
22 36_tail
_value
>>>>
21_tail
_value
>>>>_tail
_value
>>>>
1 3_tail
_value
>>>>
0_tail
_value
>>>>
7
Becomes:
_tail
_value
>>>>_tail
_value
>>>>
3 21_tail
_value
>>>>
0_tail
_value
null
36
_tail
_value
null_tail
_value
>>>>
7 22_tail
_value
>>>>
1
18- 39
First Key Idea
• We will call the split( ) method on a list (node), and return two lists
• For this purpose, we'll define a new type of object, ListNodePair, that holds (pointers) to two ListNodes
A ListNodePair object variable
18- 40
Second Key Idea
• When splitting, the roles of even-indexed and odd-indexed positions become interchanged
• The first list consists of the 1, 3, 5, 7 nodes, and the second of the 2, 4, 6 nodes, but when the first node is removed, the rest of the first list consists of even nodes, and the second of the odd nodes…
18- 41
Odd becomes Even…
_tail
_value
null_tail
_value
>>>>
22 36_tail
_value
>>>>
21_tail
_value
>>>>_tail
_value
>>>>
1 3_tail
_value
>>>>
0_tail
_value
>>>>
7
First list,odds
_tail
_value
null_tail
_value
>>>>
22 36_tail
_value
>>>>
21_tail
_value
>>>>_tail
_value
>>>>
1 3_tail
_value
>>>>
0_tail
_value
>>>>
7
First list,first item removed, now holds evens
18- 42
class ListNodePair
class ListNodePair {private ListNode _a, _b;
public ListNodePair (ListNode a,ListNode b) {
this._a = a;
this._b = b;}
public ListNode x( ) { return _a; }
public ListNode y( ) { return _b; }}
18- 43
split( )
ListNodePair split ( ) {if (_tail == null)
return new ListNodePair(this, null);else {
ListNodePair p = _tail.split( );
return new ListNodePair(
new ListNode(_value, p.y( )),
p.x( ) );
}}
18- 44
Analysis of split( );tail of list is null
Return a new ListNodePair, with the first list being the original list, the second list being null
return new ListNodePair(this, null);
_tail
_value
null
8Original list:
return:null
_tail
_value
null
8
18- 45
If tail of list is not null,do two things
• Split the tail of the list, putting one part in the first cell of a ListNodePair p, the second part in the second cell of that ListNodePair
• Add a new ListNode, with my value, at the front of the second part, switching the two parts' location (because of the even/odd flipping that occurs at each recursive level)
ListNodePair p = _tail.split( );return new ListNodePair(
new ListNode(_value, p.y( )), p.x( ) );
18- 46
Split the tail of the list
_tail
_value
null_tail
_value
>>>>
22 36_tail
_value
>>>>
21_tail
_value
>>>>_tail
_value
>>>>
1 3_tail
_value
>>>>
0_tail
_value
>>>>
7
Original list:
Result of recursive call, p = tail.split( );
p
_tail
_value
>>>>_tail
_value
>>>>
1 7_tail
_value
null
22_tail
_value
>>>>_tail
_value
>>>>
3 21_tail
_value
null
36
ListNodePair p = _tail.split( );
p.x( ) p.y( )
18- 47
Add a new ListNode, with my value, at the front of the second part; switch first and second parts
return new ListNodePair(new ListNode(_value, p.y( )), p.x( ) );
_tail
_value
>>>>_tail
_value
>>>>
1 7_tail
_value
null
22_tail
_value
>>>>_tail
_value
>>>>
3 21_tail
_value
null
36_tail
_value
>>>>
0
return:
New ListNode
New ListNodePair
p.y( ) p.x( )
18- 48
mergeSort( ),exploit divide-and-conquer
static ListNode mergeSort ( ListNode L ) {// Sort L by recursively splitting and merging
if ( (L == null) || (L.getTail( ) == null) )return L; // Zero or one item
else { // Two or more items//Split it in two partsListNodePair p = L.split( );// …then sort and merge the two partsreturnmergeSort(p.x( )).merge(mergeSort(p.y( )));
}} mergeSort first list…and merge…with mergeSort of second
list
18- 49
Complexity of mergeSort( )
• MergeSort is O(nlog2n)
• It always has this performance
• QuickSort, in contrast, has this performance on average, but can also have quadratic ( O(n2) ) performance, worst case
• However, there is overhead in using linked lists instead of arrays