Data Structure: Algorithms
Data Structure: Algorithms
Algorithms
In computer science, an algorithm is a step-by-step procedure for solving a problem in a finite amount of time.
Algorithms
● A data structure is a systematic way of organizing and accessing data,
● These concepts are central to computing, but to be able to classify some data structures
and algorithms as “good,” we must have precise ways of analysing them.
Algorithm sorting(X, n)
Input array X of n integers
Output array X sorted in a non-decreasing order
for i = 0 to n - 1 do
for j = i+1 to n do
if (X[i]>X[j]) then
{ s=X[i];
X[i]=X[j];
X[j]=s; }
return X
Agendas
● Algorithms
● Complexity Analysis
● Experimental Approach
● Algorithm Analysis
● Worst- / average- / best-case
● Time Complexity
● Asymptotic Complexity
● Big-O Notation
● Comparing Common Growth Functions
Complexity Analysis – Why we need it?
● There are often many different algorithms which can be used to solve the same problem.
● Thus, it makes sense to develop techniques that allow us to:
o Compare different algorithms with respect to their “efficiency”
o Choose the most efficient algorithm for the problem
Complexity Analysis
● Algorithm analysis or Complexity Analysis is a process of determining the amount of time, resource, etc.
required when executing an algorithm.
● The efficiency of any algorithmic solution to a problem is a measure of the:
○ Time efficiency: the time it takes to execute.
○ Space efficiency: the space (primary or secondary memory) it uses.
For example:
for A[10] = (1,2,3,4,5,6,7,8,9,10)
X[10] =(1,1.5,2,2.5,3,3.5,4,4.5,5,5.5)
Complexity Analysis
● Sol 1
At each step i, compute the element X[i] by traversing the array A and determining the sum of its
elements, respectively the average
● Sol 2
1. At each step i update a sum of the elements in the array A
2. Compute the element X[i] as sum/I
Agendas
● Algorithms
● Complexity Analysis
● Experimental Approach
● Algorithm Analysis
● Worst- / average- / best-case
● Time Complexity
● Asymptotic Complexity
● Big-O Notation
● Comparing Common Growth Functions
Experimental Approach
● Write a program that implements the algorithm
● Run the program with data sets of varying size.
● Determine the actual running time using a system call to measure time (e.g. system date)
Experimental Approach
● To measure time in Java use:
long before = System.currentTimeMillis();
● Code to measure performance
long after = System.currentTimeMillis();
System.out.println((after - before) + " milliseconds");
Experimental Approach - Example
● The first algorithm we consider performs repeated string concatenation, based on the +
operator.
Experimental Approach - Example
● The second algorithm relies on Java’s StringBuilder class
Experimental Approach - Example
● As an experiment, we used System.currentTimeMillis( ), in the style of Code above, to
measure the efficiency of both repeat1 and repeat2 for very large strings.
● We executed trials to compose strings of increasing lengths to explore the relationship
between the running time and the string length.
Experimental
Approach - Results
Experimental Approach - Problems
● It is necessary to implement and test the algorithm in order to determine its running
time.
● Experiments can be done only on a limited set of inputs, and may not be indicative of
the running time for other inputs.
● The same hardware and software should be used in order to compare two algorithms. –
condition very hard to achieve!
Agendas
● Algorithms
● Complexity Analysis
● Experimental Approach
● Algorithm Analysis
● Worst- / average- / best-case
● Time Complexity
● Asymptotic Complexity
● Big-O Notation
● Comparing Common Growth Functions
Algorithm Analysis
● Machine independence
● Our goal is to develop an approach to analysing the efficiency of algorithms that:
1. Allows us to evaluate the relative efficiency of any two algorithms in a way that is
independent of the hardware and software environment.
2. Is performed by studying a high-level description of the algorithm without need for
implementation.
3. Takes into account all possible inputs.
Algorithm Analysis
● Counting Primitive Operations
● To analyse the running time of an algorithm without performing experiments, we
perform an analysis directly on a high-level description of the algorithm
● We define a set of primitive operations such as the following list (next slide):
Algorithm Analysis
Primitive Operations
● Assigning a value to a variable
● Following an object reference
● Performing an arithmetic operation (for example, adding two numbers)
● Comparing two numbers
● Accessing a single element of an array by index
● Calling a method
● Returning from a method
Algorithm Analysis
● The amount of time that any algorithm takes to run almost always depends on the
amount of input that it must process.
● We expect, for instance, that sorting 10,000 elements requires more time than sorting 10
elements.
● Hence, the efficiency of an algorithm is the number of primitive operations it performs.
● This number is a function of the input size n.
Algorithm Analysis
● We will associate, with each algorithm, a function f(n) that characterizes the number of
primitive operations that are performed as a function of the input size n.
Agendas
● Algorithms
● Complexity Analysis
● Experimental Approach
● Algorithm Analysis
● Worst- / average- / best-case
● Time Complexity
● Asymptotic Complexity
● Big-O Notation
● Comparing Common Growth Functions
Worst- / average- / best-case
Best-case running time : B(n), the minimum time needed to execute an algorithm for an
input of size n
● Sort a set of numbers in increasing order; and the data is already in increasing order.
Average-case running time, the minimum time needed to execute an algorithm for an input
of size n
● Average-case complexity: A(n), the average time needed to execute an algorithm for an
input of size n
● May be difficult to define what “average” means.
Worst- / average- / best-case
Worst- / average- / best-case
Time Complexity T(n), or the running time of a particular algorithm on input of size n, is
taken to be the number of times the instructions in the algorithm are executed.
Time Complexity - Example
Loops: The running time of a loop is at most the running time of the statements inside of
that loop times the number of iterations.
Nested Loops: Running time of a nested loop containing a statement in the inner most loop
is the running time of statement multiplied by the product of the sized of all loops.
Consecutive Statements: Just add the running times of those consecutive statements.
If/Else: Never more than the running time of the test plus the larger of running times of S1
and S2.
Time Complexity - Loops
● We start by considering how to count operations in for-loops.
○ We use integer division throughout.
● First of all, we should know the number of iterations of the loop; say it is x.
○ Then the loop condition is executed x + 1 times.
○ Each of the statements in the loop body is executed x times
○ The loop-index update statement is executed x times.
We analyse Time complexity of getMax, Assume that the array is filled with n elements, where n >= 2
● We approximate T(n) by a function g(n) in a way that does not substantially change the
magnitude of T(n). --the function g(n) is sufficiently close to f(n) for large values of
the input size n.
Asymptotic Complexity
● This "approximate" measure of efficiency is called asymptotic complexity.
● Thus the asymptotic complexity measure does not give the exact number of operations
of an algorithm, but it shows how that number grows with the size of the input.
● This gives us a measure that will work for different operating systems, compilers and
CPUs.
Agendas
● Algorithms
● Complexity Analysis
● Experimental Approach
● Algorithm Analysis
● Worst- / average- / best-case
● Time Complexity
● Asymptotic Complexity
● Big-O Notation
● Comparing Common Growth Functions
Big-O Notation
● The most commonly used notation for specifying asymptotic complexity is the big-O
notation.
● The Big-O notation, O(g(n)), is used to give an upper bound (worst-case) on a positive
runtime function f(n) where n is the input size.
Rules for using big-O
● For large values of input n , the constants and terms with lower degree of n are ignored.
The Big O notation estimates the execution time of an algorithm in relation to the input
size. If the time is not related to the input size, the algorithm is said to take constant time
with the notation O(1).
For example, a method that retrieves an element at a given index in an array takes constant
time, because it does not grow as the size of the array increases.
Big O - Repetition: Simple Loops
Nested Loops:
Complexity of inner loop * complexity of outer loop.
Examples: i = 1;
while(i <= n) { O(n log n)
j = 1;
sum = 0 while(j <= n){
O(n2) statements of constant
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++) complexity
sum += i * j ; j = j*2;
}
i = i+1;
}
Big O - Sequence of statements
else
System.out.println("Error! Enter '+' or 'x'!"); O(n)
Complexity is = O(n3)
Big O – Bonus!!