Introduction to Computer Science Recursive Link Operations MergeSort Unit 18

Preview:

Citation preview

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

Recommended