1 Data Structures CSCI 132, Spring 2014 Lecture 31 Merge Sort Analysis Quick Sort
2 Merge Sort Divide the list into 2 equal halves. Keep dividing until list length = 1. Merge sublists into sorted list:
3 Implementation of Merge Sort template void Sortable_list :: merge_sort( ) { recursive_merge_sort(head); } template void Sortable_list :: recursive_merge_sort(Node * &sub_list) { if (sub_list != NULL && sub_list->next != NULL) { Node *second_half = divide_from(sub_list); recursive_merge_sort(sub_list); recursive_merge_sort(second_half); sub_list = merge(sub_list, second_half); } }
4 divide_from( ) returns a pointer to the middle of the list position midpointsecond_half sub_list positionmidpoint sub_list To find the midpoint, move the midpoint pointer one step for every two steps moved by the position pointer. Return second_half, which points to the node following the midpoint.
5 divide_from( ) template Node *Sortable_list :: divide_from(Node *sub_list) { Node *position, // traverses the entire list *midpoint, // moves at half speed of position to midpoint *second_half; if ((midpoint = sub_list) == NULL) return NULL; // List is empty. position = midpoint->next; while (position != NULL) { // Move position twice for midpoint’s one move. position = position->next; if (position != NULL) { midpoint = midpoint->next; position = position->next; } } second_half = midpoint->next; midpoint->next = NULL; return second_half; }
6 merge( ) combines two sorted sublists first second first second combined last_sorted Compare values in first node of the two lists. Add whichever is smaller to combined list. Keep track of last_sorted, first and second pointers. Return combined.next
merge( ) template Node *Sortable_list :: merge(Node *first, Node *second) { Node *last_sorted; // points to the last node of sorted list Node combined; // dummy first node, points to merged list last_sorted = &combined; while (first != NULL && second != NULL) { // Attach node with smaller key if (first->entry entry) { last_sorted->next = first; last_sorted = first; first = first->next; // Advance to the next unmerged node. } else { last_sorted->next = second; last_sorted = second; second = second->next; } } if (first == NULL) // After one list ends, attach the remainder of the other. last_sorted->next = second; else last_sorted->next = first; return combined.next; }
8 Recursion Tree for Merge Sort
9 Counting Comparisons for Merge Sort The only function that does comparisons is merge( ). With each comparison, one item is put into the correct position. Therefore, merge does a maximum of n comparisons when merging a total of n items. (In fact, it does a maximum of n-1 comparisons).
10 How many items are merged? Recursion tree for n = 16: Total items merged at each level: 16 0
11 Number of Comparisons In general, for list length n: n items are merged at each level of the recursion tree, requiring at most n comparisons. The number of levels in the recursion tree is lg(n). (Because the tree is a 2-tree with n leaves). Therefore, the number of key comparisons is no greater than n*(number of levels) = n lg(n) Refinement of the count shows that merge sort does n lg(n) *n + 1 comparisons on average. This is close to the theoretical lower bound for number of comparisons done by a sorting algorithm.
12 Quick Sort Merge Sort can be a problem for contiguous lists. It is difficult to merge 2 contiguous sublists efficiently, unless you create a separate array to hold the merged list. But, creating a separate array uses more memory space. Quick Sort avoids this problem by dividing the list into 2 sublists in which all the entries in one are less than some value (pivot) and all the entries in the other are greater than or equal to the pivot.
13 Quick Sort example pivot partition low high partition combine with pivot
14 Implementation of Quick Sort template void Sortable_list :: quick_sort( ) { recursive_quick_sort(0, count - 1); } template void Sortable_list :: recursive_quick_sort(int low, int high) { int pivot_position; if (low < high) { // Otherwise, no sorting is needed. pivot_position = partition(low, high); recursive_quick_sort(low, pivot_position - 1); recursive_quick_sort(pivot_position + 1, high); } }
15 The partition algorithm pivot = pivot x ? lowlast smalli swap if x < pivot pivot = pivot a ? lowlast smalli
16 Implementation of partition template int Sortable_list :: partition(int low, int high){ Record pivot; int i; // used to scan through the list int last_small; // position of the last key less than pivot swap(low, (low + high)/2); pivot = entry[low]; // First entry is now pivot. last_small = low; for (i = low + 1; i <= high; i++) { if (entry[i] < pivot) { last_small = last_small + 1; swap(last_small, i); } } swap(low, last_small); // Put the pivot into its proper position. return last_small; }
17 Running time of Quick Sort Best case: Partition list into 2 equal halves each round. Run time = O(n lg(n) ) Worst case: Partition list into (n-1) list and 0 (pivot not included) Run time= (n-1) + (n-2) = 0.5n n Average case: Run time = 1.4 n lg(n) + O(n)
18 Comparison of Sorting Algorithms Trade off: Use of storage space. Use of computer time. Programming effort. Quick Sort and Merge Sort use more space (because they are recursive functions), but run faster. For contiguous lists, quick sort has better use of space. Insertion sort and Selection sort are relatively slow (O(n 2 )). But, they use less memory space and are somewhat easier to program.