Notes for Lecture 13 - March 1, 2007 ==================================== * Overview of PS5. * More on linked lists. ** Useful to distinguish between cell and list types A cell is a structure with two fields: value and next, which is a pointer to another cell. Cells can be linked together into a chain. A list is a structure with a field head that points to the first cell of a chain if the list is non-empty, or is NULL if the list is empty. The reason for the separate list structure is to have a single uniform object that can represent all lists, including the empty list. The list structure might also contain other useful fields, e.g., an integer that is always kept equal to the current length of the list. This allows the length to be found quickly instead of having to scan down the chain of cells one at a time to find the end. A list might also contain a field "tail" that points to the last cell in the chain of a non-empty list. This is useful for adding elements to the end of a list, or for implementing the operation of concatenating two lists together. * Sorting a list ** Merge operation Example: merge of sorted lists 1,3,4,8,12 with 2,5,6,9 results in the sorted list 1,2,3,4,5,6,8,9,12 Easy recursive definition. Easy iterative implementation. Operations needed on a list x for merge: isempty(x), first(x), rest(x) (which returns a list containing all but the first element of x), append(x,a) (which appends element a to the end of list x). ** Basic merge sort algorithm: split, sort pieces, merge Split list x into two pieces x1 and x2 of roughly equal length. Sort x1 recursively. Sort x2 recursively. Merge x1 and x2. Base case of recursion is when list length <= 1, since all such lists are already sorted. *** Problem of knowing list length Must know list length to know where to split. Can either scan list to find length or (more efficiently) store it in the list header. *** Problem of finding the place to split Must locate place to split. No easy way to do this other than walk down the list. ** Recursive list-merge-sort By making a more general recursive algorithm, the special scan to split the list can be avoided. cell* sort_front( cell* x, int n, cell** yp ); Returns two results: x1 is the result of sorting the first len cells of x. x2 is the unsorted tail of x that remains after the first len cells are removed x1 is returned via the arg list (i.e., by storing into *yp); x2 is the normal function return value. To sort a list x of length n, one simply calls sort_front( x, n, &y ) and returns y. sort_front() has an easy recursive definition. if (n==0) { *yp = NULL; return x; } if (n==1) { *yp = first(x); return rest(x); } int n1 = n/2; int n2 = n-n1; cell* x1; cell* x2; cell* y; cell* z; y = sort_front( x, n1, &x1 ); z = sort_front( y, n2, &x2 ); *yp = z; return merge(x1, x2); Here, first(x) returns the first cell of x as a chain of length 1; rest(x) is the tail of x after the first element. For efficiency, first() and merge() can be implemented destructively, that is, they destroy their arguments in the course of computing their result. However, if this is done, then care must be taken to find rest(x) before splitting off the first cell of x from the chain in order to computer first(x). See the demo code for an example of how to do this. ** Listsort demo (See demo 13.1_listsort) # Local Variables: # mode: outline # End: