Presentation is loading. Please wait.

Presentation is loading. Please wait.

Introduction to Computer Science Recursive Link Operations MergeSort Unit 18.

Similar presentations


Presentation on theme: "Introduction to Computer Science Recursive Link Operations MergeSort Unit 18."— Presentation transcript:

1 Introduction to Computer Science Recursive Link Operations MergeSort Unit 18

2 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;} }

3 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

4 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 _value 7 _tail _value 3 >>>> _tail _value null 2 >>>> We create: _tail _value 5 >>>> We print: 11 7 5 3 2 theList

5 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); }

6 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; }

7 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

8 18- 8 Trace It (1) tail value null 2 front heap 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

9 18- 9 Trace It (2) tail value null tail value >>>> 32 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 front heap inputval = sinp.readInt( ); while ( !sinp.eof( ) ) { front = new ListNode(inputval, front); System.out.print("Enter number: ”); inputval = sinp.readInt( ); }

10 18- 10 Iterative printReverseList( ) static void printReverseList(ListNode head) { while ( head != null ) { System.out.print(head.getValue() + " "); head = head.getTail( ); } System.out.println(); }

11 18- 11 Now Recursive – Add some more Methods to the ListNode class Let's add a new method int 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; } }

12 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)

13 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 method String toString ( ) to the ListNode class that prints the elements of the list, separated by commas

14 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.

15 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

16 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.

17 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, method void addToEndM (int n) to the ListNode class that adds a new cell (initialized with n) to the end of the list

18 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.

19 18- 19 OK, One More Mutating List Operation Let's add one more new, mutating, method ListNode 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

20 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; } }

21 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)

22 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 >>>> np _tail _value >>>> …

23 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 >>>> …

24 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; }

25 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; }

26 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

27 18- 27 MergeSort _tail _value null _tail _value >>>> 115 _tail _value >>>> 4 _tail _value >>>> _tail _value >>>> 39 _tail _value >>>> 17 … 1. Split into two equal sized lists 2. Sort recursively3. Sort recursively 4. Merge the two sorted lists

28 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

29 18- 29 Merging Two Sorted Linked Lists _tail _value null _tail _value >>>> 58 _tail _value >>>> 3 _tail _value >>>> _tail _value >>>> 11 _tail _value >>>> 0 _tail _value >>>> 2 _tail _value null _tail _value >>>> 48 _tail _value >>>> 2 _tail _value >>>> 1 Gives us:

30 18- 30 merge( ) will be nonmutating We'll write merge as a nonmutating method: ListNode merge (ListNode L) One list node will be the receiver: The other list node will be the 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)

31 18- 31 merge( ) ListNode merge (ListNode L) { if (L == null) return this; if (_value < L._value) if (_tail == null) return new ListNode(_value, L); else return new ListNode(_value, _tail.merge(L)); else return new ListNode(L._value, merge(L._tail)); }

32 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

33 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 >>>> 11 _tail _value >>>> 0 receiver: L: null _tail _value null 8 _tail _value >>>> _tail _value >>>> 11 _tail _value >>>> 0 return:

34 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 _value 0 receiver: L: null _tail _value null _tail _value >>>> 48 _tail _value >>>> 2 _tail _value >>>> 1 _tail _value >>>> 0 return: _tail _value null _tail _value >>>> 48 _tail _value >>>> 2 _tail _value >>>> 1 L

35 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

36 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

37 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

38 18- 38 Splitting a list _tail _value null _tail _value >>>> 2236 _tail _value >>>> 21 _tail _value >>>> _tail _value >>>> 13 _tail _value >>>> 0 _tail _value >>>> 7 Becomes: _tail _value >>>> _tail _value >>>> 321 _tail _value >>>> 0 _tail _value null 36 _tail _value null _tail _value >>>> 722 _tail _value >>>> 1

39 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

40 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…

41 18- 41 Odd becomes Even… _tail _value null _tail _value >>>> 2236 _tail _value >>>> 21 _tail _value >>>> _tail _value >>>> 13 _tail _value >>>> 0 _tail _value >>>> 7 First list, odds _tail _value null _tail _value >>>> 2236 _tail _value >>>> 21 _tail _value >>>> _tail _value >>>> 13 _tail _value >>>> 0 _tail _value >>>> 7 First list, first item removed, now holds evens

42 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; } }

43 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( ) );}

44 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 8 Original list: return: null _tail _value null 8

45 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( ) );

46 18- 46 Split the tail of the list _tail _value null _tail _value >>>> 2236 _tail _value >>>> 21 _tail _value >>>> _tail _value >>>> 13 _tail _value >>>> 0 _tail _value >>>> 7 Original list: Result of recursive call, p = tail.split( ); p _tail _value >>>> _tail _value >>>> 17 _tail _value null 22 _tail _value >>>> _tail _value >>>> 321 _tail _value null 36 ListNodePair p = _tail.split( ); p.x( ) p.y( )

47 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 >>>> 17 _tail _value null 22 _tail _value >>>> _tail _value >>>> 321 _tail _value null 36 _tail _value >>>> 0 return: New ListNode New ListNodePair p.y( )p.x( )

48 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 parts ListNodePair p = L.split( ); // …then sort and merge the two parts return mergeSort(p.x( )).merge(mergeSort(p.y( ))); } } mergeSort first list…and merge…with mergeSort of second list

49 18- 49 Complexity of mergeSort( ) MergeSort is O(nlog 2 n) It always has this performance QuickSort, in contrast, has this performance on average, but can also have quadratic ( O(n 2 ) ) performance, worst case However, there is overhead in using linked lists instead of arrays


Download ppt "Introduction to Computer Science Recursive Link Operations MergeSort Unit 18."

Similar presentations


Ads by Google