Sorting Algorithms

Sorting data (i.e., placing the data into some particular order, such as ascending or descending) is one of the most important computing applications. A bank sorts all checks by account number so that it can prepare individual bank statements at the end of each month. Telephone companies sort their lists of accounts by last name and, further, by first name to make it easy to find phone numbers. Virtually every organization must sort some data, and often, massive amounts of it. Sorting data is an intriguing, computer-intensive problem that has attracted intense research efforts.

An important point to understand about sorting is that the end resultthe sorted vectorwill be the same no matter which algorithm you use to sort the vector. The choice of algorithm affects only the runtime and memory use of the program. In previous chapters, we have introduced the selection sort and insertion sortsimple algorithms to implement, but inefficient. The next section examines the efficiency of these two algorithms using Big O notation. The last algorithmmerge sort, which we introduce in this chapteris much faster but is harder to implement.


20.3.1. Efficiency of Selection Sort

Selection sort is an easy-to-implement, but inefficient, sorting algorithm. The first iteration of the algorithm selects the smallest element in the vector and swaps it with the first element. The second iteration selects the second-smallest element (which is the smallest element of the remaining elements) and swaps it with the second element. The algorithm continues until the last iteration selects the second-largest element and swaps it with the second-to-last element, leaving the largest element in the last index. After the ith iteration, the smallest i elements of the vector will be sorted into increasing order in the first i elements of the vector.

The selection sort algorithm iterates n 1 times, each time swapping the smallest remaining element into its sorted position. Locating the smallest remaining element requires n 1 comparisons during the first iteration, n 2 during the second iteration, then n 3, ..., 3, 2, 1. This results in a total of n(n 1)/2 or (n2 n)/2 comparisons. In Big O notation, smaller terms drop out and constants are ignored, leaving a final Big O of O(n2).


20.3.2. Efficiency of Insertion Sort

Insertion sort is another simple, but inefficient, sorting algorithm. The first iteration of this algorithm takes the second element in the vector and, if it is less than the first element, swaps it with the first element. The second iteration looks at the third element and inserts it into the correct position with respect to the first two elements, so all three elements are in order. At the ith iteration of this algorithm, the first i elements in the original vector will be sorted.

Insertion sort iterates n 1 times, inserting an element into the appropriate position in the elements sorted so far. For each iteration, determining where to insert the element can require comparing the element to each of the preceding elements in the vector. In the worst case, this will require n 1 comparisons. Each individual repetition statement runs in O(n) time. For determining Big O notation, nested statements mean that you must multiply the number of comparisons. For each iteration of an outer loop, there will be a certain number of iterations of the inner loop. In this algorithm, for each O(n) iteration of the outer loop, there will be O(n) iterations of the inner loop, resulting in a Big O of O(n* n) or O(n2).

20.3.3. Merge Sort (A Recursive Implementation)

Merge sort is an efficient sorting algorithm, but is conceptually more complex than selection sort and insertion sort. The merge sort algorithm sorts a vector by splitting it into two equal-sized subvectors, sorting each subvector and then merging them into one larger vector. With an odd number of elements, the algorithm creates the two subvectors such that one has one more element than the other.

The implementation of merge sort in this example is recursive. The base case is a vector with one element. A one-element vector is, of course, sorted, so merge sort immediately returns when it is called with a one-element vector. The recursion step splits a vector of two or more elements into two equal-sized subvectors, recursively sorts each subvector, then merges them into one larger, sorted vector. [Again, if there is an odd number of elements, one subvector is one element larger than the other.]

Suppose the algorithm has already merged smaller vectors to create sorted vectors A:

 4 10 34 56 77

and B:

 5 30 51 52 93

Merge sort combines these two vectors into one larger, sorted vector. The smallest element in A is 4 (located in the zeroth index of A). The smallest element in B is 5 (located in the zeroth index of B). In order to determine the smallest element in the larger vector, the algorithm compares 4 and 5. The value from A is smaller, so 4 becomes the first element in the merged vector. The algorithm continues by comparing 10 (the second element in A) to 5 (the first element in B). The value from B is smaller, so 5 becomes the second element in the larger vector. The algorithm continues by comparing 10 to 30, with 10 becoming the third element in the vector, and so on.

Figure 20.5 defines class MergeSort and lines 3134 of Fig. 20.6 define the sort function. Line 33 calls function sortSubVector with 0 and size - 1 as the arguments. The arguments correspond to the beginning and ending indices of the vector to be sorted, causing sortSubVector to operate on the entire vector. Function sortSubVector is defined in lines 3761. Line 40 tests the base case. If the size of the vector is 1, the vector is already sorted, so the function simply returns immediately. If the size of the vector is greater than 1, the function splits the vector in two, recursively calls function sortSubVector to sort the two subvectors, then merges them. Line 55 recursively calls function sortSubVector on the first half of the vector, and line 56 recursively calls function sortSubVector on the second half of the vector. When these two function calls return, each half of the vector has been sorted. Line 59 calls function merge (lines 64108) on the two halves of the vector to combine the two sorted vectors into one larger sorted vector.



Figure 20.5. MergeSort class definition.

(This item is displayed on page 986 in the print version)

 1 // Fig 20.5: MergeSort.h
 2 // Class that creates a vector filled with random integers.
 3 // Provides a function to sort the vector with merge sort.
 4 #include 
 5 using std::vector;
 6
 7 // MergeSort class definition
 8 class MergeSort
 9 {
10 public:
11 MergeSort( int ); // constructor initializes vector
12 void sort(); // sort vector using merge sort
13 void displayElements() const; // display vector elements
14 private:
15 int size; // vector size
16 vector< int > data; // vector of ints
17 void sortSubVector( int, int ); // sort subvector
18 void merge( int, int, int, int ); // merge two sorted vectors
19 void displaySubVector( int, int ) const; // display subvector
20 }; // end class SelectionSort

Figure 20.6. MergeSort class member-function definition.

(This item is displayed on pages 986 - 988 in the print version)

 1 // Fig 20.6: MergeSort.cpp
 2 // Class MergeSort member-function definition.
 3 #include 
 4 using std::cout;
 5 using std::endl;
 6 
 7 #include 
 8 using std::vector;
 9 
10 #include  // prototypes for functions srand and rand
11 using std::rand;
12 using std::srand;
13 
14 #include  // prototype for function time
15 using std::time;
16 
17 #include "MergeSort.h" // class MergeSort definition
18 
19 // constructor fill vector with random integers
20 MergeSort::MergeSort( int vectorSize )
21 {
22 size = ( vectorSize > 0 ? vectorSize : 10 ); // validate vectorSize
23 srand( time( 0 ) ); // seed random number generator using current time
24 
25 // fill vector with random ints in range 10-99
26 for ( int i = 0; i < size; i++ )
27 data.push_back( 10 + rand() % 90 );
28 } // end MergeSort constructor
29 
30 // split vector, sort subvectors and merge subvectors into sorted vector
31 void MergeSort::sort()
32 {
33 sortSubVector( 0, size - 1 ); // recursively sort entire vector
34 } // end function sort
35 
36 // recursive function to sort subvectors 
37 void MergeSort::sortSubVector( int low, int high )
38 { 
39 // test base case; size of vector equals 1
40 if ( ( high - low ) >= 1 ) // if not base case
41 {
42 int middle1 = ( low + high ) / 2; // calculate middle of vector
43 int middle2 = middle1 + 1; // calculate next element over 
44 
45 // output split step
46 cout << "split: ";
47 displaySubVector( low, high );
48 cout << endl << " ";
49 displaySubVector( low, middle1 );
50 cout << endl << " ";
51 displaySubVector( middle2, high );
52 cout << endl << endl;
53 
54 // split vector in half; sort each half (recursive calls)
55 sortSubVector( low, middle1 ); // first half of vector 
56 sortSubVector( middle2, high ); // second half of vector 
57 
58 // merge two sorted vectors after split calls return
59 merge( low, middle1, middle2, high ); 
60 } // end if
61 } // end function sortSubVector
62 
63 // merge two sorted subvectors into one sorted subvector 
64 void MergeSort::merge( int left, int middle1, int middle2, int right )
65 {
66 int leftIndex = left; // index into left subvector 
67 int rightIndex = middle2; // index into right subvector 
68 int combinedIndex = left; // index into temporary working vector
69 vector< int > combined( size ); // working vector 
70 
71 // output two subvectors before merging
72 cout << "merge: "; 
73 displaySubVector( left, middle1 ); 
74 cout << endl << " "; 
75 displaySubVector( middle2, right ); 
76 cout << endl; 
77 
78 // merge vectors until reaching end of either 
79 while ( leftIndex <= middle1 && rightIndex <= right ) 
80 { 
81  // place smaller of two current elements into result 
82  // and move to next space in vector 
83  if ( data[ leftIndex ] <= data[ rightIndex ] ) 
84  combined[ combinedIndex++ ] = data[ leftIndex++ ]; 
85  else 
86  combined[ combinedIndex++ ] = data[ rightIndex++ ];
87 } // end while 
88 
89 if ( leftIndex == middle2 ) // if at end of left vector 
90 { 
91  while ( rightIndex <= right ) // copy in rest of right vector
92  combined[ combinedIndex++ ] = data[ rightIndex++ ]; 
93 } // end if 
94 else // at end of right vector 
95 { 
96  while ( leftIndex <= middle1 ) // copy in rest of left vector
97  combined[ combinedIndex++ ] = data[ leftIndex++ ]; 
98 } // end else 
99 
100 // copy values back into original vector
101 for ( int i = left; i <= right; i++ ) 
102  data[ i ] = combined[ i ]; 
103
104 // output merged vector 
105 cout << " "; 
106 displaySubVector( left, right );
107 cout << endl << endl; 
108 } // end function merge
109
110 // display elements in vector
111 void MergeSort::displayElements() const
112 {
113 displaySubVector( 0, size - 1 );
114 } // end function displayElements
115
116 // display certain values in vector
117 void MergeSort::displaySubVector( int low, int high ) const
118 {
119 // output spaces for alignment
120 for ( int i = 0; i < low; i++ )
121 cout << " ";
122
123 // output elements left in vector
124 for ( int i = low; i <= high; i++ )
125 cout << " " << data[ i ];
126 } // end function displaySubVector

Lines 7987 in function merge loop until the program reaches the end of either subvector. Line 83 tests which element at the beginning of the vectors is smaller. If the element in the left vector is smaller, line 84 places it in position in the combined vector. If the element in the right vector is smaller, line 86 places it in position in the combined vector. When the while loop has completed (line 87), one entire subvector is placed in the combined vector, but the other subvector still contains data. Line 89 tests whether the left vector has reached the end. If so, lines 9192 fill the combined vector with the elements of the right vector. If the left vector has not reached the end, then the right vector must have reached the end, and lines 9697 fill the combined vector with the elements of the left vector. Finally, lines 101102 copy the combined vector into the original vector. Figure 20.7 creates and uses a MergeSort object. The output from this program displays the splits and merges performed by merge sort, showing the progress of the sort at each step of the algorithm.

Figure 20.7. MergeSort test program.

(This item is displayed on pages 989 - 991 in the print version)

 1 // Fig 20.7: Fig20_07.cpp
 2 // MergeSort test program.
 3 #include 
 4 using std::cout;
 5 using std::endl;
 6
 7 #include "MergeSort.h" // class MergeSort definition
 8
 9 int main()
10 {
11 // create object to perform merge sort
12 MergeSort sortVector( 10 );
13
14 cout << "Unsorted vector:" << endl;
15 sortVector.displayElements(); // print unsorted vector
16 cout << endl << endl;
17
18 sortVector.sort(); // sort vector
19
20 cout << "Sorted vector:" << endl;
21 sortVector.displayElements(); // print sorted vector
22 cout << endl;
23 return 0;
24 } // end main
 
 Unsorted vector:
 30 47 22 67 79 18 60 78 26 54

 split: 30 47 22 67 79 18 60 78 26 54
 30 47 22 67 79
 18 60 78 26 54

 split: 30 47 22 67 79
 30 47 22
 67 79

 split: 30 47 22
 30 47
 22

 split: 30 47
 30
 47

 merge: 30
 47
 30 47

 merge: 30 47
 22
 22 30 47

 split: 67 79
 67
 79

 merge: 67
 79
 67 79

 merge: 22 30 47
 67 79
 22 30 47 67 79

 split: 18 60 78 26 54
 18 60 78
 26 54

 split: 18 60 78
 18 60
 78

 split: 18 60
 18
 60

 merge: 18
 60
 18 60

 merge: 18 60
 78
 18 60 78

 split: 26 54
 26
 54

 merge: 26
 54
 26 54

 merge: 18 60 78
 26 54
 18 26 54 60 78

 merge: 22 30 47 67 79
 18 26 54 60 78
 18 22 26 30 47 54 60 67 78 79

 Sorted vector:
 18 22 26 30 47 54 60 67 78 79
 

Efficiency of Merge Sort

Merge sort is a far more efficient algorithm than either insertion sort or selection sort (although that may be difficult to believe when looking at the rather busy Fig. 20.7). Consider the first (nonrecursive) call to function sortSubVector. This results in two recursive calls to function sortSubVector with subvectors each approximately half the size of the original vector, and a single call to function merge. This call to function merge requires, at worst, n 1 comparisons to fill the original vector, which is O(n). (Recall that each element in the vector is chosen by comparing one element from each of the subvectors.) The two calls to function sortSubVector result in four more recursive calls to function sortSubVector, each with a subvector approximately one quarter the size of the original vector, along with two calls to function merge. These two calls to function merge each require, at worst, n/2 1 comparisons, for a total number of comparisons of O(n). This process continues, each call to sortSubVector generating two additional calls to sortSubVector and a call to merge, until the algorithm has split the vector into one-element subvectors. At each level, O(n) comparisons are required to merge the subvectors. Each level splits the size of the vectors in half, so doubling the size of the vector requires one more level. Quadrupling the size of the vector requires two more levels. This pattern is logarithmic and results in log2 n levels. This results in a total efficiency of O(n log n). Figure 20.8 summarizes many of the searching and sorting algorithms covered in this book and lists the Big O for each of them. Figure 20.9 lists the Big O values we have covered in this chapter along with a number of values for n to highlight the differences in the growth rates.


Figure 20.8. Searching and sorting algorithms with Big O values.


Algorithm

Location

Big O

Searching Algorithms

Linear search

Section 7.7

O(n)

Binary search

Section 20.2.2

O(log n)

Recursive linear search

Exercise 20.8

O(n)

Recursive binary search

Exercise 20.9

O(log n)

Sorting Algorithms

Insertion sort

Section 7.8

O(n2)

Selection sort

Section 8.6

O(n2)

Merge sort

Section 20.3.3

O(n log n)

Bubble sort

Exercises 16.3 and 16.4

O(n2)

Quick sort

Exercise 20.10

Worst case: O(n2)

   

Average case: O(n log n)


Figure 20.9. Approximate number of comparisons for common Big O notations.

n

Approximate decimal value

O(log n)

O(n)

O(n log n)

O(n2)

210

1000

10

210

10 · 210

220

220

1,000,000

20

220

20 · 220

240

230

1,000,000,000

30

230

30 · 230

260


Introduction to Computers, the Internet and World Wide Web

Introduction to C++ Programming

Introduction to Classes and Objects

Control Statements: Part 1

Control Statements: Part 2

Functions and an Introduction to Recursion

Arrays and Vectors

Pointers and Pointer-Based Strings

Classes: A Deeper Look, Part 1

Classes: A Deeper Look, Part 2

Operator Overloading; String and Array Objects

Object-Oriented Programming: Inheritance

Object-Oriented Programming: Polymorphism

Templates

Stream Input/Output

Exception Handling

File Processing

Class string and String Stream Processing

Web Programming

Searching and Sorting

Data Structures

Bits, Characters, C-Strings and structs

Standard Template Library (STL)

Other Topics

Appendix A. Operator Precedence and Associativity Chart

Appendix B. ASCII Character Set

Appendix C. Fundamental Types

Appendix D. Number Systems

Appendix E. C Legacy Code Topics

Appendix F. Preprocessor

Appendix G. ATM Case Study Code

Appendix H. UML 2: Additional Diagram Types

Appendix I. C++ Internet and Web Resources

Appendix J. Introduction to XHTML

Appendix K. XHTML Special Characters

Appendix L. Using the Visual Studio .NET Debugger

Appendix M. Using the GNU C++ Debugger

Bibliography



C++ How to Program
C++ How to Program (5th Edition)
ISBN: 0131857576
EAN: 2147483647
Year: 2004
Pages: 627

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net