0% found this document useful (0 votes)
135 views42 pages

Algorithm and Complexity

The document discusses algorithm complexity analysis. It explains that complexity analysis determines the resources like time and storage needed to execute an algorithm. The complexity is usually related to the input size and can be estimated using big O, omega, and theta notation. The document provides examples of analyzing the time and space complexity of insertion sort and discusses lower bounds for comparison-based sorting algorithms using decision trees. It also introduces divide-and-conquer algorithms and analyzes the time complexity of merge sort.

Uploaded by

Richy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
135 views42 pages

Algorithm and Complexity

The document discusses algorithm complexity analysis. It explains that complexity analysis determines the resources like time and storage needed to execute an algorithm. The complexity is usually related to the input size and can be estimated using big O, omega, and theta notation. The document provides examples of analyzing the time and space complexity of insertion sort and discusses lower bounds for comparison-based sorting algorithms using decision trees. It also introduces divide-and-conquer algorithms and analyzes the time complexity of merge sort.

Uploaded by

Richy
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 42

CENP3207 : ALGORITHM AND COMPLEXITY

(for Software Engineering)

Mbiethieu Cezar,

mbiethieucezar@gmail.com

http://www-public.it-sudparis.eu/~gibson/Teaching/MAT7003/

Complexity & Algorithm Analysis


Complexity

To analyze an algorithm is to determine the resources (such as time


and storage) necessary to execute it.

Most algorithms are designed to work with inputs of arbitrary


length/size.

Usually, the complexity of an algorithm is a function relating the


input length/size to the number of fundamental steps (time
complexity) or fundamental storage locations (space complexity).

The fundamental steps and storage locations are, of course,


dependent on the « physics of the underlying computation
machinery ».
Complexity
In theoretical analysis of algorithms it is common to estimate their complexity in
the asymptotic sense: estimate the complexity function for arbitrarily large input.

Big O notation, omega notation and theta notation are often used to this end.

For instance, binary search is said to run in a number of steps proportional to the
logarithm of the length of the list being searched, or in O(log(n)) ("in logarithmic
time“)

Usually asymptotic estimates are used because different implementations of the


same algorithm may differ in complexity.

However the efficiencies of any two "reasonable" implementations of a given


algorithm are related by a constant multiplicative factor called a hidden constant.

Exact (not asymptotic) measures of complexity can sometimes be computed but


they usually require certain assumptions concerning the particular implementation
of the algorithm, called model of computation.
Complexity

Time complexity estimates depend on what we define to be a


fundamental step.

For the analysis to correspond usefully to the actual execution time,


the time required to perform a fundamental step must be guaranteed to
be bounded above by a constant.

One must be careful here; for instance, some analyses count an


addition of two numbers as one step.

This assumption will be false in many contexts. For example, if the


numbers involved in a computation may be arbitrarily large, the time
required by a single addition can no longer be assumed to be constant.
Complexity

Space complexity estimates depend on what we define to be a fundamental storage


location.

Such storage must offer reading and writing functions as fundamental steps

Most computers offer interesting relations between time and space complexity.

For example, on a Turing machine the number of spaces on the tape that play a
role in the computation cannot exceed the number of steps taken.

In general, space complexity is bounded by time complexity.

Many algorithms that require a large time can be implemented using small space
Complexity: why not just measure empirically?

Because algorithms are platform-independent, e.g. a given algorithm can be


implemented in an arbitrary programming language on an arbitrary computer
(perhaps running an arbitrary operating system and perhaps without unique access
to the machine’s resources)

For example, consider the following run-time measurements of 2 different


implementations of the same function on two different machines

Based on these metrics, it


would be easy to jump to
the conclusion that
Computer A is running an
algorithm that is far superior
in efficiency to what
Computer B is running.
However, …
Complexity: why not just measure empirically?

Extra data now shows us


that our original
conclusions were false.

Computer A, running the


linear algorithm, exhibits
a linear growth rate.

Computer B, running the


logarithmic algorithm,
exhibits a logarithmic
growth rate

B is a much better
solution for large input
Complexity: Orders of growth – Big O notation
Informally, an algorithm can be said to exhibit a growth rate on the order of a
mathematical function if beyond a certain input size n, the function f(n) times
a positive constant provides an upper bound or limit for the run-time of that
algorithm.

In other words, for a given input size n greater than some no and a constant c,
an algorithm can run no slower than c × f(n). This concept is frequently
expressed using Big O notation

For example, since the run-time of insertion sort grows quadratically as its
input size increases, insertion sort can be said to be of order O(n²).

Big O notation is a convenient way to express the worst-case scenario for a


given algorithm, although it can also be used to express the average-case —
for example, the worst-case scenario for quicksort is O(n²), but the average-
case run-time is O(n lg n).

Note: Average-case analysis is much more difficult that worst-case analysis


Complexity: Orders of growth –big/little-omega, big theta, little-o,

Just as Big O describes the upper bound, we use Big Omega to describe the
lower bound

Big Theta describes the case where the upper and lower bounds of a
function are on the same order of magnitude.
Optimality

Once the complexity of an algorithm has been estimated, the question


arises whether this algorithm is optimal. An algorithm for a given
problem is optimal if its complexity reaches the lower bound over all
the algorithms solving this problem.

Reduction

Another technique for estimating the complexity of a problem is the


transformation of problems, also called problem reduction. As an
example, suppose we know a lower bound for a problem A, and that
we would like to estimate a lower bound for a problem B. If we can
transform A into B by a transformation step whose cost is less than
that for solving A, then B has the same bound as A.
Classic Complexity curves

250

f(n) = n
f(n) = log(n)
f(n) = n log(n)
f(n) = n^2
f(n) = n^3
f(n) = 2^n

0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Iterative Algorithm Analysis Example : Insertion Sort
Insertion Sort

for (int i = 1; i < a.length; i++)


{
// insert a[i] into a[0:i-1]
insert(a, i, a[i]);
}

public static void insert


(int[] a, int n, int x)
{
// insert t into a[0:i-1]
int j;
for (j = i - 1;
j >= 0 && x < a[j]; j--)
a[j + 1] = a[j];
a[j + 1] = x;
}
Insertion Sort - Simple Complexity Analysis

How many compares are done?


1+2+…+(n-1), O(n^2) worst case
(n-1)* 1 , O(n) best case

How many element shifts are done?


1+2+...+(n-1), O(n2) worst case
0 , O(1) best case

How much space/memory used?


In-place algorithm
Proving a Lower Bound for any comparison
based algorithm for the Sorting Problem

A decision tree can model the execution of any comparison


sort:
• One tree for each input size n.
• View the algorithm as splitting whenever it compares
two elements.
• The tree contains the comparisons along all possible
instruction traces.
• The running time of the algorithm = the length of the
path taken.
• Worst-case running time = height of tree.
Any comparison sort can be turned into a Decision tree

class InsertionSortAlgorithm {

for (int i = 1; i < a.length; i++) {

int j = i; 1:2
while ((j > 0) && (a[j-1] > a[i])) {

a[j] = a[j-1];

j--; }
2:3 1:3
a[j] = B; }}

123 1:3 213 2:3

132 312 231 321


Theorem. Any decision tree that can sort n
elements must have height Ω(n lg n) .
Proof. The tree must contain ≥ n! leaves, since
there are n! possible permutations. A height-h
binary tree has ≤ 2h leaves. Thus, n! ≤ 2h .
∴ h ≥ lg(n!) (lg is mono. increasing)
≥ lg ((n/e)n) (Stirling’s formula)
= n lg n – n lg e
= Ω(n lg n) .
Divide-and-conquer algorithms

The divide-and-conquer strategy solves a problem by:

1. Breaking it into subproblems that are themselves smaller


instances of the same type of problem
2. Recursively solving these subproblems
3. Appropriately combining their answers

The real work is done piecemeal, in three different places: in the


partitioning of problems into subproblems; at the very tail end of the
recursion, when the subproblems are so small that they are solved
outright; and in the gluing together of partial answers. These are held
together and coordinated by the algorithm's core recursive structure.
Analysis of Merge Sort – a typical divide-and-conquer algorithm

Algorithm Effort
MergeSort(A, left, right) { T(n)
if (left < right) { Θ(1)
mid = floor((left + right) / 2); Θ(1)
MergeSort(A, left, mid); T(n/2)
MergeSort(A, mid+1, right); T(n/2)
Merge(A, left, mid, right); Θ(n)
}
}

So T(n) = Θ(1) when n = 1, and

2T(n/2) + Θ(n) when n > 1


Recurrences
Solving Recurrence Using Iterative Method

Counting the number of repetitions of n in the sum at the end, we see that there
are lg n + 1 of them. Thus the running time is n(lg n + 1) = n lg n + n.

We observe that n lg n + n < n lg n + n lg n = 2n lg n for n>0, so the running


time is O(n lg n).
Proof By Induction
Recurrence relations master theorem

Divide-and-conquer algorithms often follow a generic pattern: they


tackle a problem of size n by recursively solving, say, a sub-problems
of size n/b and then combining these answers in O(n^d) time, for some
a; b; d > 0

Their running time can therefore be captured by the equation:

We can derive a closed-form solution to this general recurrence so


that we no longer have to solve it explicitly in each new instance.
General Recursive Analysis

TO DO: Can you


prove this theorem
Binary Search – Example of Applying the Master Theorem

2012: J Paul Gibson T&MSP: Mathematical Foundations MAT7003/L9-Complexity&AA.28


Greedy Algorithms

Greedy algorithms are simple and straightforward. They are shortsighted in their
approach in the sense that they make choices on the basis of information at hand
without worrying about the effect these choices may have in the future. They are
easy to invent, easy to implement and most of the time quite efficient. Many
problems cannot be solved correctly by greedy approach. Greedy algorithms are
often used to solve optimization problems

Greedy Approach
Greedy Algorithm works by making the choice that seems most promising at any
moment; it never reconsiders this choice, whatever situation may arise later.

Greedy-Choice Property:
It says that a globally optimal solution can be arrived at by making a locally
optimal choice.

Complexity Analysis – the analysis is usually specific to the algorithm


(applying generic reasoning techniques)
Dijkstra’s Shortest Path Algorithm is a typical example of greediness

Initial State
Step 1
Final Step
Shortest Path In A Graph – typical implementation
Shortest Path Algorithm (Informal) Analysis
Every time the main loop executes, one vertex is extracted from the queue.

Assuming that there are V vertices in the graph, the queue may contain O(V) vertices.
Each pop operation takes O(lg V) time assuming the heap implementation of priority
queues. So the total time required to execute the main loop itself is O(V lg V).

In addition, we must consider the time spent in the function expand, which applies the
function handle_edge to each outgoing edge. Because expand is only called once per
vertex, handle_edge is only called once per edge.

It might call push(v'), but there can be at most V such calls during the entire
execution, so the total cost of that case arm is at most O(V lg V). The other case arm
may be called O(E) times, however, and each call to increase_priority takes O(lg V)
time with the heap implementation.

Therefore the total run time is O(V lg V + E lg V), which is O(E lg V) because V is
O(E) assuming a connected graph.
Complexity: Some PBL

Purse problem – bag of coins, required to pay an exact price

The complexity analysis, is to be based on the fundamental operations of your


chosen machine/language, used to implement the function Pay, specified below.

Input:
Bag of integer coins, Target integer Price
Output:
empty bag to signify that price cannot be paid exactly
or
A smallest bag of coins taken from the original bag and whose sum is equal to
the price to be paid.

EG Pay ([1,1,2,3,3,4,18], 6) = [2,4] or [3,3]


Pay ([1,1,2,4,18], 15) = []
TO DO: Implement the function Pay, execute tests and analyze
run times.
Complexity Classes: P vs NP
The class P consists of all those decision problems that can be solved on a
deterministic sequential machine – like a Turing machine, and a typical PC - in
an amount of time that is polynomial in the size of the input

The class NP consists of all those decision problems whose positive solutions
can be verified in polynomial time given the right information, or equivalently,
whose solution can be found in polynomial time on a non-deterministic
machine.

NP does not stand for "non-polynomial". There are many complexity classes
that are much harder than NP.

Arguably, the biggest open question in theoretical computer science concerns


the relationship between those two classes:

Is P equal to NP?

We have already seen some problems in P so now let us consider NP


Some well-known problems in NP

• k-clique: Given a graph, does it have a size k clique? (i.e. complete


subgraph on k vertices)

• k-independent set: Given a graph, does it have a size k independent set? (i.e. k
vertices with no edge between them)

• k-coloring: Given a graph, can the vertices be colored with k colors such
that adjacent vertices get different colors?

• Satisfiability (SAT): Given a boolean expression, is there an assignment of truth


values (T or F) to variables such that the expression is satisfied (evaluated to T)?

• Travel salesman problem (TSP): Given an edge-weighted complete graph and an


integer x, is there a Hamiltonian cycle of length at most x?

• Hamiltonian Path (or Hamiltonian Cycle): Given a graph G does it have a


Hamiltonian path? (i.e. a path that goes through every vertex exactly once)
NP-Completeness
A problem X is hard for a class of problems C if every problem in C can be
reduced to X

Forall complexity classes C, if a problem X is in C and hard for C, then X is said


to be complete for C

A problem is NP-complete if it is NP and no other NP problem is more than a


polynomial factor harder.

Informally, a problem is NP-complete if answers can be verified quickly, and a


quick algorithm to solve this problem can be used to solve all other NP problems
quickly.

The class of NP-complete problems contains the most difficult problems in NP, in
the sense that they are the ones most likely not to be in P.

Note: there are many other interesting complexity classes, but


we do not have time to study them further
Complexity Classes: NP-Complete

The concept of NP-complete was introduced in 1971 by Stephen


Cook in The complexity of theorem-proving procedures, though
the term NP-complete did not appear anywhere in his paper.

Lander (1975) - On the structure of polynomial time reducibility -


showed the existence of problems outside P and NP-complete
Complexity Analysis – Many Open Questions: Factoring Example

It is not known exactly which complexity classes contain the decision version of the
integer factorization problem.

It is known to be in both NP and co-NP. This is because both YES and NO answers
can be trivially verified given the prime factors

It is suspected to be outside of all three of the complexity classes P, NP-complete,


and co-NP-complete.

Many people have tried to find classical polynomial-time algorithms for it and
failed, and therefore it is widely suspected to be outside P.

In contrast, the decision problem "is N a composite number?" appears to be much


easier than the problem of actually finding the factors of N. Specifically, the former
can be solved in polynomial time (in the number n of digits of N).

In addition, there are a number of probabilistic algorithms that can test primality
very quickly in practice if one is willing to accept the small possibility of error.
Proving NP-Completeness of a problem – general approach

Approach:

•Restate problem as a decision problem

•Demonstrate that the decision problem is in the class NP

•Show that a problem known to be NP-Complete can be reduced


to the current problem in polynomial time.

This technique for showing that a problem is in the class NP-


Complete requires that we have one NP-Complete problem to begin
with.

Boolean (circuit) satisifiability was the first problem to be shown to


be in NP-Complete.
In 1972, Richard Karp published"Reducibility Among Combinatorial
Problems", showing – using this reduction technique starting on the
satisifiability result - that many diverse combinatorial and graph theoretical
problems, each infamous for its computational intractability, are NP-complete.

You might also like