CS2200 Sahni Chapter 2 Page tex2html_wrap171

Sorting

internal main memory

external use auxiliary storage

stable retains original order if keys are the same

oblivious perform the same amount of work regardless of actual input

sort by address use indirect addressing so don't have to move record (key address pairs)

Rank Sort

Count number of elements less than each item plus the number of equal elements which occur to its left. Then place the item at the correct location.

Try pseudocode at seats.

For efficiency, record which is higher for each compare.

template<class T>
void Rank(T a[], int n, int rank[])
{// Rank the n elements a[0:n-1].
   for (int i = 0; i < n; i++)
      rank[i] = 0;  // initialize
   for (i = 1; i < n; i++)
      for (int j = 0; j < i; j++)
         if (a[j] <= a[i]) rank[i]++;
         else rank[j]++;
}

Note that this only counts the number of items less than (or to the left and equal) for each item. There must still be a separate routine which actually moves each item into the proper location based on rank.

How are equal keys handled? If there are three copies of the same key, do they all have the same rank? Try computing ranks for 8 1 5 3 5

Count number of compares as 1 + 2 + 3 + 4 + ... + n-1 = n(n+1)/2 (Derive by listing sum backwards and adding together.)

Selection Sorting

selection sort: select elements one at a time and place in proper final position.
Repeatedly find the smallest.

 3 6 43  1 9	
 1 6 43  3 9	
 1 3 43  6 9	
 1 3  6 43 9	
 1 3  6  9 43

Analysis: n + n-1 + n-2 + tex2html_wrap_inline172 + 1 = n(n-1)/2

Only n moves - use when records are long

Requires tex2html_wrap_inline174 compares even when already sorted - oblivious.

Bubble Sort

Compares adjacent elements - exchanges if out of order.

Lists gets smaller each time - at least one is placed in final order.

Place of last swap is as much as you have to look at.

tex2html_wrap_inline176

Insertion Sort

Sorts by inserting records into an already existing sorted file.

Two groups of keys - sorted and unsorted.

Insert n times - each time move 1/2 elements to insert.

The number of elements in the list changes so

tex2html_wrap_inline178

 11   5 17  1 21
 5   11 17  1 21
 5   11 17  1 21
 1    5 11 17 21
 1    5 11 17 21

Will perform better when the degree of unsortedness is low - recommended for data that is nearly sorted.

Improvements

  1. use binary search O(n log n) compares, but number of moves doesn't change so no real gain.
  2. linked list storage - can't use binary search - still tex2html_wrap_inline176

    Could use sentinel containing the key
    search until tex2html_wrap_inline182

    sentinel is extra item added to one end of the list so ensure that the loop will terminate without having to include a separate check.

Quicksort from Chapter 14

Partition the set into: j, those elements less than j, those elements greater than j.

Often does as: Use Two pointers - top pointer looks for a value smaller than j, bottom pointer looks for a value larger than j. Then interchange. (This does only a third as many swaps.)

Apply recursively

Analysis: At each level, all elements of the array are examined. The number of levels depends on how equally the pieces are divided. Best case: log n levels yielding O(n log n)

Worst case Let n be the size of the array to be sorted:

C(n) = n-1 + C(n-1) = n(n-1)/2

Space requirement: depends on recursive stacking.

Improvements

  1. Use median of three so don't get a bad pivot.
  2. Use sentinel at each end so don't have to check (avoid left and right crossing)
  3. Switch to insertion sort when size gets small - can do one big insertion sort on all. (Insertion sort will be covered shortly)

Mergesort - from Chapter 14

Chop the lists into two sublists. Sort the two pieces. Combine by merging. (Some techniques just get sublists of larger and larger powers of two.) The pieces may ALSO be sorted via Mergesort, so it is recursive.

In merging sublists of length n, clearly no more than 2n compares are required. Actually it is less than this, but it is easy to count this way.

Each level takes exactly n compares and there are lg n levels, the complexity is O(n lg n).

A closer count is n lg n -1.25n + 1 (by running test cases)

If linked lists, no problem with storage space. If we have an array of items to be stored, the mergesort requires an auxiliary storage array.

Notice that quicksort and mergesort have similar structure

quicksort(a[],low,high) {
  pivot = partition(a,low,high)
  quicksort(a,low,pivot-1)
  quicksort(a,pivot+1,high)
}

mergesort(a[],low,high) {
  mid=(low+high)/2
  mergesort(a,low,mid)
  mergesort(a,mid+1,high)
  sequentialmerge(a,low,mid,high)
}

Both have O(n) work to either (1) divide into chunks or (2) put the chunks back together.
The pictures we draw (for expected case) look the same.
The formula analysis looks the same.

We see it doesn't matter whether we do the work before the recursion or after. The work is the same.

Program Performance

Performance: memory and time required

performance analysis: analytical
performance measurement: experimental

Space complexity (usually less important):

  1. space may be limited
  2. may use to determine largest problem size we can solve

Time complexity:

  1. real time constraints
  2. may need to interact with user

Components of space complexity:

  1. instruction space - needed to store the compiled version
  2. data space: variables and constants
  3. environment stack space: for recursion, return values

Log Review

For a binary tree with 63 nodes, how many levels are there?

If I have an array of size 120, how many times can I split the array in half?

tex2html_wrap_inline186
tex2html_wrap_inline188
tex2html_wrap_inline190
tex2html_wrap_inline192
tex2html_wrap_inline194
tex2html_wrap_inline196

Components of Time Complexity:

  1. amount of time spent in each operation - difficult to measure
  2. estimate of number of times a key operation is performed

Asymptotics

asymptotics study of functions of n as n gets large (without bound)

If the running time of an algorithm is proportional to n, when we double n we double the running time.

If the running time is proportional to lg n (c log n), when we double n we only change the running time by c (c is constant of proportionality).

c log 2n = c(log 2 + log n) = c(1 + log n) = c + c log n
Since original time was c log n, doubling n only increased the time by c.

List examples of something being proportional to something else.

Operation Counts

Operation Count: how many times you add, multiply, compare, etc.

Step Counts: Attempt to account for time spent in all parts of the program/function as a function of the characteristics of the program.

Example 2.20

template<class T>
void Add( T **a, T **b, T **c, int rows, int cols)
{// Add matrices a and b to obtain matrix c.
   for (int i = 0; i < rows; i++) {
      count++; // preceding for loop
      for (int j = 0;  j < cols; j++) {
         count++; // preceding for loop
         c[i][j] = a[i][j] + b[i][j];
         count++; // assignment
         }
      count++; // last time of j for loop
      }
   count++; // last time of i for loop
}

Can also assign counts on a per statement basis.

void Add( T **a, T **b, T **c, int rows, int cols)
{
   for (int i = 0; i < rows; i++)              rows+1
      for (int j = 0;  j < cols; j++)          rows(cols+1)
         c[i][j] = a[i][j] + b[i][j];          rows*cols
}

TOTAL: 2(rows*col) + 2rows + 1

Key reason for operation or step counts is to compare two programs which compute the same results.

O Notation (Big Oh)

We want to give an upper bound on the amount of time it takes to solve a problem.

defn: tex2html_wrap_inline212 constants c and tex2html_wrap_inline216 such that tex2html_wrap_inline218 whenever tex2html_wrap_inline220

Termed complexity: has nothing to do with difficulty of coding or understanding, just time to execute.

Important tool for analyzing and describing the behavior of algorithms

Is an tex2html_wrap_inline222 algorithm always better than a tex2html_wrap_inline224 algorithm? No, it depends on the specific constants, but if n is large enough, an tex2html_wrap_inline228 is always slower than an tex2html_wrap_inline222 algorithm.

Complexity Class: O(1), O(log n), O(n), O(n log n), O(tex2html_wrap_inline222), O(tex2html_wrap_inline234)

For small n, c may dominate.

Intractable: all known algorithms are of exponential complexity

Measuring Complexity

  1. Additive Property: If two statements follow one another, the complexity of the two of them is the larger complexity.

    In this example there are two values which effect the complexity: m and n

    for (i=0;i < n;i++) x++
    for (j=0;j < m;i++) x++
    The first statement has complexity O(n). The second statement has complexity O(m).

    Therefore, the additive property indicates:

    O(n) + O(m) = O(max(n,m))

  2. If/Else: For the fragment

    if cond then S1
    else S2

    The complexity is the running time of the cond plus the larger of the complexities of S1 and S2.

  3. Multiplicative Property:
    For Loops: the complexity of a for loop is at most the complexity of the statements inside the for loop times the number of iterations. However, if each iteration is different in length, it is more accurate to sum the individual lengths rather than just multiply.

Nested For Loops

Analyze nested for loops inside out. The total complexity of a statement inside a group of nested for loops is the complexity of the statement multiplied by the product of the size of each for loop.

Example 1

for (int i=0;i  < m;i++)
  for (int j=0;j  < n;j++)
     x++;

O(mn)

Consider a pictorial view of the work. Let each row represent the work done in one iteration of the outermost loop. The number of rows represents the number of times the outermost loop executes. The area of the figure will then represent the total work.

tex2html_wrap242

Example 2

for (beg = 1;beg < m; beg++)
   for (j = beg; j < m; j++)
     x++;

In this example, our complexity picture is triangular. The outermost loops executes m times, but since each time the j loop is called it is has a different beginning location, the rows are of different length.

tex2html_wrap244

Recursive Algorithms

The complexity for recursive algorithms requires additional techniques.

Example 3

void doit(int n)
{
   if (n==1) return;
   for (int i=0; i < n; i++)
      x = x + i;
   doit(n/2);
   doit(n/2);
}

If we let T(n) represent the time to solve doit(n), the running time is represented recursively as T(n) = n+ 2 T(n/2) . In other words, the time for method doit to execute when n is the parameter is n (because of the for loop) plus two times the running time of T(n/2) (since doit is called twice recursively with a parameter of n/2).

Since T is defined in terms of T, this is called a recurrence relation

In our pictorial view, we let each line represent a layer of recursions (The first call is the first row, the two calls at the second level (doit calls doit) comprise the second row, the four third level calls (doit calls doit calls doit) represent the third row. The length of the row represents the call itself (ignoring costs incurred by the recursive calls). In other words, to determine the size of the first row, measure how much work is done in that call not counting the recursive calls it makes.

The number of rows is determined by

tex2html_wrap248

Example 4

void  doit(int n)
{  if (n<=0) return;
   for (int i=0; i < n; i++)
      x = x + i
   doit(n/2)
}

If we let T(n) represent the time to solve this problem, the time is represented recursively as T(n) = T(n/2) +n.

tex2html_wrap252

In our pictorial view, we let each line represent a layer of recursions (The first call is the first row, the one call at the second level (doit calls doit) is the second row, the one third level call (doit calls doit calls doit) represents the third row. The length of the row represents the call itself (ignoring costs incurred by the recursive calls). In other words, to determine the size of the first row, measure how much work is done in that call not counting the recursive calls it makes.

Example 5

void doit(int n){
   if (n <=1) return;
   int x = x + i;
   doit(n/2);
}

If we let T(n) represent the time to solve this problem, the time is represented recursively as
T(n) = T(n/2) +1.

In this case, a single call to doit(n) (ignoring recursive calls) takes constant time. We draw that as a square of length 1. Since there are log n levels representing the log n calls, the picture looks like:

tex2html_wrap256

Example 6

void doit(n){
   if (n <=1) return;
   int x = x + i;
   doit(n/2);
   doit(n/2);
}

If we let T(n) represent the time to solve this problem, the time is represented recursively as
T(n) = 2T(n/2) +1.

Again, the time to execute doit(n) ignoring recursive calls is constant. However, the number of calls required at each level doubles. Our picture is

tex2html_wrap260

A formula Approach

Mathematicians have developed a formula approach to determining complexity.

Theorem:

T(n)= a T(n/b) + O(tex2html_wrap_inline262)

if tex2html_wrap_inline264 the complexity is O(tex2html_wrap_inline266)

if tex2html_wrap_inline268 the complexity is O(tex2html_wrap_inline270)

if tex2html_wrap_inline272 the complexity is O(tex2html_wrap_inline262)

In this case:

Let's use the theorem to revisit the same problems we solved pictorially.

Example 3

void doit(int n)
{
   if (n==1) return;
   for (int i=0; i < n; i++)
      x = x + i;
   doit(n/2);
   doit(n/2);
}

There are two recursive calls made: a=2
Each recursive call does half of the work: b=2
The work done in a single call is n. k is the power on n: k=1 (as work is n)

Since tex2html_wrap_inline284, we are in the ``equals'' case.

if tex2html_wrap_inline268 O(tex2html_wrap_inline270) = O(tex2html_wrap_inline290) = O(nlog n), which is exactly what our pictures told us.

Example 4

void  doit(int n)
{  if (n<=0) return;
   for (int i=0; i < n; i++)
      x = x + i
   doit(n/2)
}

There is one recursive calls made: a=1 The recursive call does half of the work: b=2 The work done in a single call is n. k is the power on n: k=1 (as work is n)

Since tex2html_wrap_inline294, we are in the ``less than'' case.

if tex2html_wrap_inline272 O(tex2html_wrap_inline298) = O(tex2html_wrap_inline262)= O(n), which is exactly what our pictures told us.

Example 5

void doit(int n){
   if (n <=1) return;
   int x = x + i;
   doit(n/2);
}

There is one recursive calls made: a=1 The recursive call does half of the work: b=2 The work done in a single call is independent of n. k is the power on n: k=0 (as work is 1)

since tex2html_wrap_inline268 as tex2html_wrap_inline306 we are in the ``equals'' case.

if tex2html_wrap_inline268 O(tex2html_wrap_inline270) = O(tex2html_wrap_inline312) = O(log n), which is exactly what our pictures told us.

Example 6

void doit(n){
   if (n <=1) return;
   int x = x + i;
   doit(n/2);
   doit(n/2);
}

There are two recursive calls made: a=2 The recursive call does half of the work: b=2 The work done in a single call is independent of n. k is the power on n: k=0 (as work is 1)

Since tex2html_wrap_inline264 we are in the ``greater than'' case.

if tex2html_wrap_inline264 the complexity is O(tex2html_wrap_inline266 = O(tex2html_wrap_inline322) = O(tex2html_wrap_inline298) which is exactly what our pictures told us.

Recurrence Relations

In analyzing algorithms, we often encounter progressions. Most calculus books list the formulas below, but you should be able to derive them.

Arithmetic Progression

Example: tex2html_wrap_inline326
Writing the same sum backwards:
tex2html_wrap_inline328
If we add the two S's together,


tabular122

Geometric Progression

Example: tex2html_wrap_inline330
Multiplying both sides by the base:
tex2html_wrap_inline332

Subtracting S from 2S we get

tex2html_wrap_inline334

Determining Complexity from Experimental Evidence

At times we may want to verify the complexity of written code.

To do this we can either use

  1. actual running times: this can be done using system timing commands. The only downside is that sometimes the timing methods are too crude to give accurate information unless huge amounts of data are used.
  2. Counts of operations performed: This is done by placing a counter at key points throughout the program (inside all loops, inside if/else constructs) to determine (roughly) how many operations are performed.

To be able to determine complexity from experimental evidence, you must be able to have data for several different problem sizes. Since we do not know the constant, we can determine nothing from a single data point.

For example, if our runtime information consists of


tabular130

The complexity could be anything

  1. O(1) with c=800
  2. O(n) with c = 25
  3. O(log n) with c = 160
  4. O(tex2html_wrap_inline222) with c = .78

Even with two pieces of data it is not completely determined as the timing doesn't have to be exact (you could have been lucky and finished faster).

The easiest way to visually see the complexity is to have four or five data points in which the problem size keeps doubling.

Consider the following timings obtained from running code. What is the complexity?

  1. Experimental Table 1


    tabular136

    This complexity is O(1) - the run time is basically constant.

  2. Experimental Table 2


    tabular139

    This complexity is O(n) with a c of 5. There is a bit of variability, however.

  3. Experimental Table 3


    tabular142

    This is O(log n) with a c =5. The first entry doesn't fit the pattern, but remember that it may take a while for the pattern to emerge.

  4. Experimental Table 4


    tabular145

    This is O(tex2html_wrap_inline234) with c=2

  5. Experimental Table 5


    tabular148

    This is O(n log n) with c=1;

How do you figure out the complexity from experimental data when you know neither the constant or the complexity?

I ``eyeball it'' to come up with a good guess. Then I figure out the constant for one entry. Next I see if that constant works for all the entries. This is basically my ``approximate-then-finalize'' approach.

How do I guess?

  1. O(1) is basically constant
  2. O(log n) grows slowly, by a constant between entries
  3. O(n) doubles between entries
  4. O(n log n) slightly more than doubles between entries
  5. O(tex2html_wrap_inline222) quadruples between entries
  6. O(tex2html_wrap_inline234) grows exponentially

Practical Complexities

Assume P is tex2html_wrap_inline344 for some constant tex2html_wrap_inline346 and Q is tex2html_wrap_inline348 for some constant tex2html_wrap_inline350. Program p is faster when n > max(tex2html_wrap_inline346, tex2html_wrap_inline350, c/d).

Note, the coefficients matter in this comparison. Eventually the lower complexity wins out, but n might be quite large.

Study Figure 2.25. Note that even for small n (1000), time is measured in years for O(tex2html_wrap_inline234).


tabular155

One problem that concerns us is, ``Is the tex2html_wrap_inline222 always worse that n log n, no matter what the constants?'' The following table helps us answer that question:


tabular158

Notice that while the constants make make n log n worse that tex2html_wrap_inline222 for a while, eventually (for large enough n) the higher complexity will be worse.



Vicki Allan
Fri Jun 14 11:39:51 MDT 2002