1321 - College of Computing

CS1321:
Introduction to
Programming
Georgia Institute of Technology
College of Computing
Lecture 19
October 29, 2001
Fall Semester
Today’s Menu
1. Generative Recursion: QuickSort
2. Generative Recursion: Fractals
Last time: Generative
Recursion
Up until the last lecture, we had been working with a
form of recursion that had been driven by the
“structure” or characteristics of the data types passed
to it.
Functions that processed a list of TAs or a tree full of
numbers were “shaped” by the data definitions that
they involved. A List or a Tree has built within it the
idea that we will recur as we process the data as well
as the idea that we will eventually terminate as we
process the data.
Processing a List
(define (process-lon in-lon)
(cond ((empty? in-lon) …)
(else …(first in-lon)
…(process-lon
(rest in-lon))))
By simply having our
code reflect the data
definition, most of our
code was written for us!
These functions are
driven by the “structure”
of the data definition.
Processing a Binary Tree
(define (process-BT in-BT)
(cond ((not in-BT) …)
(else …(node-data in-BT)
…(process-BT (node-left in-BT))
…(process-BT (node-right in-BT)))))
Last time: Generative
Recursion
As we began to discover last time, not all functions are
shaped by the structure of the data being passed in.
There exists a class of recursive functions for which the
structure of the data being passed in is merely a side
note to the recursive process. These functions
generated “new” data at each recursive step and relied
on an algorithmic process to determine whether or not
they should terminate.
Last Time: Our Examples
Last Time: Our Examples
Our recursion in this case
is driven by the location
of the ball relative to the
pocket, not by any
intrinsic properties of the
data definition.
Last Time: Our Examples
98 23 45 14
6
67 33 42
98 23 45 14
98 23 45 14
98 23
98
23
23 98
45 14
45
14 45
14 23 45 98
6
14
6
67 33 42
6
67 33 42
6
67
6
67
33 42
98 23 45 14
33
42
6
67
6
33 42 67
6
67 33 42
33 42
14 23 33 42 45 67 98
6
14 23 33 42 45 67 98
Merge Sort,
rather than
being driven
by definition
of the list, is
driven by the
size of the
data being
passed in.
Sorting
Last time we explored the idea behind merge-sort, a
generative sorting algorithm that employed a “divideand-conquer” methodology to sort a sequence of data
elements into ascending order.
The algorithm for merge-sort involved splitting our list
into two components of equal size, sorting each half
recursively, and merging the result.
Sorting
(define (merge-sort lst)
(cond [(empty? lst) empty]
[else (local ((define halves (divide lst)))
(cond [(empty? (first halves))
(first (rest halves))]
[(empty? (first (rest halves)))
(first halves)]
[else (merge
(merge-sort (first halves))
(merge-sort (first
(rest halves))))]))]))
If we examine the
code, we find that
the actual
“sorting” of our
elements didn’t
take place until
we had reached
our termination
condition (the list
is empty), and
were returning
from our
recursive calls.
Sorting
(define (merge-sort lst)
(cond [(empty? lst) empty]
[else (local ((define halves (divide lst)))
(cond [(empty? (first halves))
(first (rest halves))]
(merge
[(empty? (first (rest halves)))
(merge-sort
(first
halves)] (first halves))
[else
(merge
(merge-sort
(first
(merge-sort
(first halves))
(rest halves))))]))]))
(merge-sort (first
(rest halves))))]))]))
If we examine the
code, we find that
the actual
“sorting” of our
elements didn’t
take place until
we had reached
our termination
condition (the list
is empty), and
were returning
from our
recursive calls.
Quick Sort
Quick sort is another generative “divide and conquer”
algorithm that involves splitting our sequence of data
into parts and sorting each component recursively.
Unlike merge sort, however, quick sort performs the
actual sorting as the sequence is being split. In terms
of performance (which we’ll formally define in the next
few weeks), quick sort is considered to be a “better”
algorithm than merge sort.
Quick Sort: The basic premise
The basic idea of quick sort involves splitting up our
list around a pivot item.
1. We arbitrarily chose an element of our list to be a
pivot item.
2. We then separate our list into two components:
•
those elements that are smaller than our pivot
•
those elements that are larger than our pivot
3. We recursively sort the two components, and join
the results together with our pivot item to form a
sorted list.
36 23 45 14
6 67 33 42
Here we start off with a list of unsorted elements. We
arbitrarily choose a pivot point about which to organize
our list. For the sake of expediency, let’s just choose the
first element* of our list to be our pivot.
* In many theory books, there are
whole chapters dedicated to the
process of choosing which element
to choose as the optimal pivot point.
We’re just keeping things simple.
36 23 45 14
6 67 33 42
Now, we have to organize our list about our pivot point.
36 23 45 14
23 14
6 33
6 67 33 42
36
45 67 42
We start recurring on our two lists.
36 23 45 14
23 14
6 33
6 67 33 42
36
45 67 42
The pivot points for
each of our sub-lists.
36 23 45 14
23 14
14
6
23
6 33
33
6 67 33 42
36
45 67 42
42
45
67
36 23 45 14
23 14
14
6
6
14
23
6 33
33
6 67 33 42
36
45 67 42
42
45
67
36 23 45 14
23 14
14
6
6
14
6
14
23
6 33
33
6 67 33 42
36
45 67 42
42
45
67
36 23 45 14
23 14
14
6
6
23
6 33
33
6 67 33 42
36
45 67 42
42
45
14
6
14
6
14 23 33
42 45 67
67
36 23 45 14
23 14
14
6
6
23
6 33
33
6 67 33 42
36
45 67 42
42
45
14
6
14
6
14 23 33
6
42 45 67
14 23 33 36 42 45 67
67
36 23 45 14
6
6 67 33 42
14 23 33 36 42 45 67
Quicksort: the code
1. We arbitrarily chose an element of our list to be a
pivot item.
2. We then separate our list into two components:
•
those elements that are smaller than our pivot
•
those elements that are larger than our pivot
3. We recursively sort the two components, and join
the results together with our pivot item to form a
sorted list.
Quicksort: the code
(define (quick-sort in-lon)
1. We arbitrarily chose an element of our list to be a
pivot item.
2. We then separate our list into two components:
•
those elements that are smaller than our pivot
•
those elements that are larger than our pivot
3. We recursively sort the two components, and join
the results together with our pivot item to form a
sorted list.
Quicksort: the code
(define (quick-sort in-lon)
(cond ((empty? in-lon) empty)
(else (local ((define pivot (first in-lon)))
2. We then separate our list into two components:
•
those elements that are smaller than our pivot
•
those elements that are larger than our pivot
3. We recursively sort the two components, and join
the results together with our pivot item to form a
sorted list.
Quicksort: the code
(define (quick-sort in-lon)
(cond ((empty? in-lon) empty)
(else (local ((define pivot (first in-lon)))
…(smaller-items in-lon pivot)
…(larger-items in-lon pivot)
))))
3. We recursively sort the two components, and join
the results together with our pivot item to form a
sorted list.
Quicksort: the code
(define (quick-sort in-lon)
(cond ((empty? in-lon) empty)
(else (local ((define pivot (first in-lon)))
(append
(quick-sort
(smaller-items in-lon pivot))
(list pivot)
(quick-sort
(larger-items in-lon pivot))
)))))
Quicksort: the code
The result
of (quick-sort in-lon)
(define
quick-sort is a
sorted (cond
list of ((empty? in-lon) empty)
numbers. If one
(else (local ((define pivot (first in-lon)))
list contains items
(append
smaller than the
pivot, and one
(quick-sort
contains items
(smaller-items in-lon pivot))
bigger than the
pivot, the result
(list pivot)
we’re looking for
(quick-sort
should be:
Smaller + pivot +
larger
(larger-items in-lon pivot))
)))))
smaller-items & larger items
smaller-items is a function that takes in a list of
numbers and target item and creates a list of numbers
that contains only elements that are smaller than the
target item.
larger-items is a function that takes in a list of numbers
and target item and creates a list of numbers that
contains only elements that are larger than the target
item.
My goodness, those functionalities are awfully
similar…
Maybe you should ponder this…
Fractals
Perhaps one of the best-known (or at least most
widely recognized) generative recursion example
lies in the idea of fractals.
A formal definition of fractal is a geometrical figure
consisting of an identical motif that repeats itself on
an ever decreasing scale.
The images produced by fractals range greatly…
From the bizarre
From the bizarre
From the bizarre
From the bizarre
From the bizarre
From the bizarre
To the Beautiful
To the Beautiful
To the things we see every day
To the things we see every day
But how does it work?
Back to the reality of the situation, how does this
work and why is it called “generative recursion”.
At each step…
At each step in the recursive process, we
“generate” a new set of data based on established
rules. At each step, we check to see if we’ve met a
termination condition set up by our algorithm.
Example: Sierpinski’s Triangle
Many of you may have
already been exposed to
Sierpinski’s triangle.
It has a very
simple set of
rules:
Given a triangle…
Given a triangle with corners A, B, & C, calculate the
coordinates of the midpoints of each side of the
triangle. Draw lines connecting each of these
midpoints.
For each new triangle generated except for the
center triangle, repeat the process.
Do this until you can no longer draw triangles (you
can’t see them).
We determine
the midpoints
of each of the
three sides
through
simple
mathematics
We repeat our
algorithm for
each of the
“corner
triangles”,
generating new
images.
After only a few iterations…
The Code
For the most part, the code for this function exists on
pages 383 and 384 of your text.
Last Fractal Examples:
Sierpinksi’s triangle and
Mandelbrot
Thanks to James Hays for this code.
Another Example
(define-struct box
(p1 p2 p3 p4))
Let’s take a square. Can we represent this in Scheme?
Another Example
(define-struct box
(p1 p2 p3 p4))
Instead of our previous
model of an upper-left
posn and an edge length,
we’ll just use 4 posns.
Let’s take a square. Can we represent this in Scheme?
Another Example
90%
10%
Now, let’s take a fixed value, some percentage of the
box edge length.
Another Example
90%
10%
Do this for all sides . . .
Another Example
90%
10%
Do this for all sides . . . and use the resulting points
as the data for a new square.
Another Example
Wash, rinse, repeat.
Another Example
How would we write this in code?
Another Example
Implementation...
(define-struct box
(p1 p2 p3 p4))
(define
(draw-box in-box)...
The structure is
already given.
We know there must be a function
to draw a box. The details of this
are not interesting.
Another Example
We also need a
function that
takes in a box,
and returns a new,
smaller box.
(define (move-box b %move)
(local ((define p1 (box-p1 b))
(define p2 (box-p2 b))
(define p3 (box-p3 b))
(define p4 (box-p4 b))
(define (adjust-points a b %move)
(make-posn (+ (* %move (posn-x a))
(* (- 1 %move) (posn-x b)))
(+ (* %move (posn-y a))
(* (- 1 %move) (posn-y b))))))
(make-box (adjust-points p1 p2 %move)
(adjust-points p2 p3 %move)
(adjust-points p3 p4 %move)
(adjust-points p4 p1 %move))))
Another Example
Another Example
We know that
there’s a recursive
process at work.
That’s the most
interesting part of
this problem.
Wash, rinse, repeat.
When does it terminate?
Another Example
Another Example
We know that
there’s a recursive
process at work.
That’s the most
interesting part of
this problem.
Wash, rinse, repeat.
When does it terminate? It likely terminates when
the size of the box is too small to bother with.
Another Example
Another Example
Wash, rinse, repeat.
(define (draw-spirals in-box %move)
(cond
[ <trivial-case?> true ]
[ else
<draw-the-box>
(draw-spirals
<update-the-box>
%move) ]))
When does it terminate? It likely terminates when
the size of the box is too small to bother with.
Another Example
Another Example
Wash, rinse, repeat.
(define (draw-spirals in-box %move)
(cond
[ <trivial-case?> true ]
[ else
(draw-box in-box)
(draw-spirals
(move-box in-box %move)
%move) ]))
We already have functions for drawing the box and
updating the box.
Another Example
Another Example
Wash, rinse, repeat.
(define (draw-spirals in-box %move)
(cond
[ <trivial-case?> true ]
[ else
(draw-box in-box)
(draw-spirals
(move-box in-box %move)
%move) ]))
The ‘trivial’ case function really just tests if the
current recursive call has generated another need to
recur.
Another Example
Another Example
Wash, rinse, repeat.
(define (draw-spirals in-box %move)
(cond
[ (too-small? in-box) true ]
[ else
(draw-box in-box)
(draw-spirals
(move-box in-box %move)
%move) ]))
(define (too-small? in-box)
(> 20 (distance (posn-x (box-p1 in-box))
(posn-x (box-p2 in-box))
(posn-y (box-p1 in-box))
(posn-y (box-p2 in-box)))))
(define (distance x1 x2 y1 y2)
(sqrt (+ (square (- x1 x2))
(square (- y1 y2)))))