Analysis of Algorithms#

TL;DR

The analysis of algorithms and data structures are two fundamental areas of computer science that are closely related. An algorithm is a step-by-step procedure for solving a computational problem, while a data structure is a way of organizing and storing data in a computer program. The choice of algorithm and data structure can significantly impact the performance of a program.

The analysis of algorithms involves the study of the efficiency and performance of algorithms, typically measured in terms of time complexity and space complexity. Time complexity refers to the amount of time it takes for an algorithm to solve a problem as the input size grows larger. Space complexity refers to the amount of memory an algorithm requires to solve a problem.

Data structures are used to store and organize data in a way that allows efficient access and manipulation. The choice of data structure can have a significant impact on the performance of an algorithm. For example, if a program needs to frequently search and retrieve items from a collection, a hash table or a binary search tree might be a more efficient data structure than a linked list.

Together, the analysis of algorithms and data structures are essential for designing and implementing efficient computer programs. By choosing the best algorithms and data structures for a given problem, programmers can significantly improve the performance and scalability of their software.

Analysis of algorithms#

Algorithms

“Any well-defined computational procedure that takes some value, or set of values, as input and produces some value, or set of values, as output.”

—[Cormen et al., Introduction to Algorithms, 3rd.Ed.]

Resources necessary to execute an algorithm?
  • Time Complexity (running time)

  • Space Complexity (memory)

Resources typically depend on input size

Developing a usable algorithm#

The building blocks of algorithms

An algorithm is a step by step process that describes how to solve a problem in a way that always gives a correct answer. When there are multiple algorithms for a particular problem (and there often are!), the best algorithm is typically the one that solves it the fastest.

As computer programmers, we are constantly using algorithms, whether it’s an existing algorithm for a common problem, like sorting an array, or if it’s a completely new algorithm unique to our program. By understanding algorithms, we can make better decisions about which existing algorithms to use and learn how to make new algorithms that are correct and efficient. An algorithm is made up of three basic building blocks: sequencing, selection, and iteration.

Khan Academy

../../_images/w1_algo_design.png

Fig. 87 COS 226 lectures, Princeton University#

Sequencing: An algorithm is a step-by-step process, and the order of those steps are crucial to ensuring the correctness of an algorithm.

Khan Academy

Selection: Algorithms can use selection to determine a different set of steps to execute based on a Boolean expression.

Khan Academy

Iteration: Algorithms often use repetition to execute steps a certain number of times or until a certain condition is met.

Khan Academy

Why analyze algorithms?#

  • Classify algorithms/problems

  • Predict performance/resources

  • Provide guarantees

  • Understand underlying principles of problems

  • and…

../../_images/w3_01.png

Fig. 88 What do these all have in common?#

Analyzing computational cost#

Empirical Model
  • Run algorithm

  • Measure actual time

Mathematical Model
  • Analyze algorithm

  • Develop Model

Empirical analysis (timing)#

  • Implement algorithm

  • Run on different input sizes

  • Record actual running times

  • Calculate hypothesis

  • Predict and validate

Timing Algorithms#

Example 1

… mathematical constant that is the base of the natural logarithm. It is approximately equal to 2.71828.

\[\begin{split} \begin {align} e & = \lim_{x\to \infty} \bigg(1 + \frac{1}{n} \bigg)^n \\ & = \frac{1}{0!} + \frac{1}{1!} + \frac{1}{2!} + \frac{1}{3!} + \frac{1}{4!} + \dotsb \\ & = 1 + \frac{1}{1} + \frac{1}{1 * 2} + \frac{1}{1 * 2 * 3} + \frac{1}{1 * 2 * 3 * 4} + \dots + \frac{1}{n!} \\ \end {align} \end{split}\]
Applications?

Euler used the letter \(e\) for exponents, but the letter is now widely associated with his name. It is commonly used in a wide range of applications, including population growth of living organisms and the radioactive decay of heavy elements like uranium by nuclear scientists. It can also be used in trigonometry, probability, and other areas of applied mathematics.

https://www.investopedia.com/terms/e/eulers-constant.asp

Leonhard Euler (1707–1783)
https://personajeshistoricos.com/wp-content/uploads/2018/04/Leonhard-Euler-2-785x1024.jpg

Fig. 89 a Swiss mathematician, physicist, astronomer, geo grapher, logician and engineer who made important and influential discoveries in many branches of mathematics.#

Algorithms#

Which is more efficient?
Algorithm 1
 1long double euler1 (int n) {
 2    long double sum = 0;
 3    long double fact;
 4    for (int i = 0; i <= n; i++) {
 5        fact = 1;
 6        for (int j = 2; j <= i; j++) {
 7            fact *= j; 
 8        }
 9        sum += (1.0/fact);
10    }
11    return sum;
12}
Algorithm 2
1long double euler2 (int n) {
2    long double sum = 0;
3    long double fact = 1;
4    for (int i = 0; i <= n; i++) {
5        sum += (1.0/fact);
6        fact *= (i + 1); 
7    }
8    return sum;
9}

Example 2

https://azcoinnews.com/wp-content/uploads/2019/11/1-8.jpg

Fig. 90 fibonacci#

\[\begin{split} F_0 & = 0 \\ F_1 & = 1 \\ F_n & = F_{n-1} + F_{n-2} \\ & = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 \dotsb \end{split}\]
Iterative
 1uint64_t fibI(uint16_t n){
 2    uint64_t sum;
 3    uint64_t prev[] = {0,1};
 4    if (n < 2) return n;
 5    for (uint16_t i = 2; i <= n; i++) {
 6        sum = prev[0] + prev[1];
 7        prev[0] = prev[1];
 8        prev[1] = sum;
 9    }
10    return sum;
11}
Recursive
1uint64_t fibR (uint16_t n) {
2    if (n < 2) return n;
3    else return fibR(n-1) + fibR(n-2);
4}
Timing…
 1void time_func(uint16_t n, const char *name) {
 2    uint64_t val;
 3    Clock::time_point tic,toc;
 4    if (! strcmp(name,"Iter")) {
 5        tic = Clock::now();
 6        val = fib_iter(n);
 7        toc = Clock::now();
 8    }
 9    if (! strcmp(name,"Rec")) {
10        tic = Clock::now();
11        val = fib_rec(n);
12        toc = Clock::now();
13    }
14    std::cout << name << "fib(" << n << "):\t" 
15              << std::fixed << std::setprecision(4) 
16              << Seconds(toc-tic).count() << "sec.\tOutput:"
17              << val << std::endl; 
18}
19    
20int main (int argc, char** argv) {
21    if (argc != 3) {
22        std::cout << "Usage:./fib<n><alg>\n";
23        std::cout << "\t<n>\tn-th term to be calculated\n";
24        std::cout << "\t<alg>\t algorithm to be used(RecorIter)\n";
25        return 0;
26    }
27    uint16_t n = (uint16_t)
28    atoi(argv[1]);
29    time_func(n,argv[2]);
30}

Limitations of empirical analysis#

Requires implementing several algorithms for the same problem

  • may be difficult and time consuming

  • implementation details also play a role (one particular algorithm may be “better written”)

Requires extensive testing

  • time consuming

  • choice of test cases might favor one of the algorithms

Variations in hardware, software, and operating system affect analysis