Concepts in Computing
CS4 - Winter 2007
Instructor: Fabio Pellacini

Lecture 6: Intro to Algorithms

Overview

  • Recipes and algorithms
  • The types of operations used in an algorithm
  • Remembering things: variables
  • Combining operations: sequence, conditional, and iteration

Recipes

Computer science is primarily the study of problems that can be solved automatically by some kind of machine. For example:

  • Finding someone's number in the phone book
  • Sorting emails by date, sender, subject, etc.
  • Computing the total amount of time taken by the songs in your music library
  • Deciding whether or not there is a restaurant at which everyone would like to eat
  • Determining the shortest route from here to your home

But a computer, by itself, is nothing more than a blind calculating machine -- we have to tell it precisely what to do. In order for a computer to solve a problem automatically, we need to specify a "recipe". What do recipes consist of?

  • Ingredients: The list of things you need.
  • Method: A list of steps to be followed, in order.
  • Results: What you get, how many it serves, etc.

Example: Making a peanut butter and jelly sandwich.

Algorithms

Computer recipes are called algorithms, named after an 8th C. Persian mathematician. As we saw with recipes, we must specify the "ingredients" needed (the input to the algorithm), a clear and unambiguous set of instructions, and what we'll get when we're finished.

An algorithm is an ordered collection of unambiguous and effectively computable operations that, when followed produces an observable result, and completes (halts) in a finite amount of time.

Ordered collection
We can always tell which instruction is to be followed next.
Unambiguous
There is only one possible interpretation of the instruction.
Effectively computable
You can figure out how to do it automatically with a machine.
Observable result
You can tell what has happened, at the end.
Halts in a finite amount of time
It doesn't go on forever without stopping.

Operations

In order to design algorithms to solve specific problems, we need to develop a kind of language we can use to speak precisely about the "unambiguous and effectively computable operations" we will use to accomplish our tasks. This will give us a common understanding of what the computer can and cannot do. Depending on the application, different operations are the "primitives". The most basic ones include:

  • Add, subtract, multiply, or divide.
  • Display something for the user to see.
  • Compare two values, and choose what to do based on the results of the comparison.
  • Repeat the same sequence of actions again and again.
  • Halt. Stop computing and return some value.

Variables

As the computer executes an algorithm, it computes various values it needs to remember for later. In order for this to work, a computer has a "memory", where values can be stored away and retrieved later on. When we are writing an algorithm, the values we store in memory are represented by variables. A variable, in the computer sense, is a name that corresponds to some location in memory. For instance, we can use names like "x", "y", "name", "address", etc. as variables.

Sequence

An algorithm is generally specified by listing a sequence of operations. Generally speaking, the computer begins at the first operation, and executes the operations one at a time, in order, until it reaches the end of the sequence.

Here is a simple algorithm to compute the sum of the squares of the integers 1 through 3, that is: 1*1 + 2*2 + 3*3:

instructionmeaning
sum = 0;stores the value zero in variable "sum"
square = 1 * 1;stores the value 1 in variable "square"
sum = sum + square;adds the value in "square" to "sum"
square = 2 * 2;stores the value 1 in variable "square"
sum = sum + square;adds the value in "square" to "sum"
square = 3 * 3;stores the value 1 in variable "square"
sum = sum + square;adds the value in "square" to "sum"
return sum;halt and return "sum"

We can do this by hand, and see that 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14. So, let's verify that the algorithm computes this correctly:

After stepValue of "sum"Value of "square"
10?
201
311
414
554
659
7149

The last step causes the algorithm to halt and return the value of "sum". This value is 14, as we'd expect.

Notation

The previous algorithm can be written as:

function SumSequence() {
    var sum;
    var square;
    sum = 0;
    square = 1 * 1;
    sum = sum + square;
    square = 2 * 2;
    sum = sum + square;
    square = 3 * 3;
    sum = sum + square;
    return sum;
}

Note that we are using a precise notation to indicate various things.

  • function name() indicates that to perform the task name the instructions that follow are to be performed. In parenthesis we also explicitly indicate what we need to know to perform the action (see later)
  • var name indicates that name is a variable
  • we group sequence of instructions using { instructions }
  • we indicate when an instruction ends by ;
  • we assign a value to a variable by the = sign

Conditional

Another thing we want an algorithm to be able to do is to choose between different courses of action. For example, suppose you want to design an algorithm that prints out a greeting message based on the date. When the date is something special (e.g., July 4), the greeting message should be different:

to PrintMessage given month, day:
If month is 7 and day is 4 then
  print "Happy fourth of July!"
Else
  print "Good afternoon!"

The key here is the ability to "test" certain conditions. Breaking down this algorithm, what it says is:

  • In order to execute the PrintMessage algorithm, you must provide values for the "month" and "day" variables.
  • If "month" is equal to 7 (corresponding to July), and "day" is equal to 4 (corresponding to the 4th), then Step (2) should be taken;
  • Otherwise (it's a different month or a different day), step (2) should be skipped, and Step (4) should be taken instead.

Notation

Or in our notation we can express the above algorithm as:

function PrintMessage(month, day) {
    if (month == 7 and day == 4) {
        print("Happy fourth of July");
    } else {
    	print("Good afternoon!");
    }
}
  • function name(arguments) indicates that to perform the task name the value of the variables arguments are to be known
  • we execute other algorithms by writing their names, followed by the values of their arguments, e.g. print("message")
  • a conditional has the structure if (test) { consequent } else { alternate }
    A conditional The test yields either "true" or "false". If it is "true", then the consequent is executed; otherwise, the alternate is.
  • we use == to check if two variables are the same (since we are already using = for assignment)

Iteration

A computer should also have the ability to repeat the same set of instructions over and over again. Analogy: When you're searching for your keys, you keep looking in places you haven't checked yet, until you either find the keys, or run out of places. In algorithmic terms, that's like saying:

to FindKeys:
While (there is another place to search), do
   Move to next unsearch place
   If (your keys are here) then
     Halt and return "true"  -- your keys are found!
End
Halt and return "false"     -- your keys are lost!

This idea of repeating a sequence of instructions over and over again until some condition is met is very common in algorithms. This kind of repetition is called iteration or "looping", since you are going over a "loop" of instructions over and over.

Notation

In our notation, the previous algorithm can be written as
function FindKey(listOfPlaces) {
    while(exitAnotherPlace(listOfPlaces)) {
        var place = moveToUnsearchedPlace(listOfPlaces);
        if(keysAreInPlace(place)) {
            return true;
        }
    }
    return false;
}
  • an interation has this basic structure
    while (test) { instructions }
  • you can combine variable declaration with their assignment like var name = value

also note that we are using lots of other functions in our algorithm (e.g. moveToUnsearchedPlace), which we do not necessarily know how to implement. This is why we often refer to programs written this way as pseudocode in that they are formal enough to reason about, but not to fully execute (we do not know how to "moveToUnsearchedPlace"). Later in the course, we will remove all fuzziness from our algorithms, but for now this is enough.

Let's write an algorithm called SumUp, that when you give it a positive integer "target", computes the sum of all the even positive integers between 1 and target inclusive:

function SumUp(target) {
    var counter = 1;
    var sum = 0;
    
   	// lines starting with '//' are comments
   	// <= is "less than"
    while (counter <= target) {
    	// x % y computes the reminder of x / y
        if(counter % 2 == 0) { // % indicates the modulus operation
            sum = sum + counter;
        }
        counter = counter + 1;
    }
    
    return sum;
}

How could you shorten this algorithm? (Hint: Can you eliminate the "if" instruction?)

Synopsis

Some problems can be solved automatically; you figure out a solution once, and it can be applied mechanically by a machine.

Computer science is especially interested in these sorts of problems, and there turn out to be all kinds of interesting things that happen. We start from a set of "primitive operations" that a computer is capable of, and build algorithms to solve specific problems.

In the next couple of lectures, we'll look at more complex algorithms for some common problems, and later next week, we'll start learning how to actually program the computer to execute some algorithms, using JavaScript.