Loading...

DESIGN & ANALYSIS OF ALGORITHMS  

LEARNING OUTCOME 1

Programming Paradigms

Programming paradigm refers to a fundamental style or approach to organizing code. Different paradigms dictate how data is structured and how operations are performed.

Imperative Programming

This paradigm focuses on describing the sequence of steps (commands) to be executed. It's like providing a recipe with explicit instructions. Variables are used to store data, and control flow statements (if-else, loops) guide the execution.

Object-Oriented Programming (OOP)

OOP models the real world as objects, each with properties (data) and behaviours (methods). It promotes code reusability through inheritance and encapsulation. Objects interact by sending messages to each other.

Functional Programming

Functional programming emphasizes functions as the building blocks. It avoids side effects and treats data as immutable. Functions are considered first-class citizens, meaning they can be passed as arguments and returned as values.

Event-Driven Programming

This paradigm is reactive, waiting for events to occur before responding. It's commonly used in graphical user interfaces (GUIs) and network programming. When an event happens (e.g., button click), a specific function is triggered.

Difference Between OOP and POP

Feature OOP POP
Focus: Objects and their interactions Sequence of instructions
Data: Encapsulated within objects Stored in variables
Code Organization: Classes and objects Procedures and functions
Abstraction: Achieved through inheritance and polymorphism Achieved through procedures and functions
Reusability: Promoted through inheritance and encapsulation Promoted through modularization
Modularity: Encouraged by classes and objects Encouraged by procedures and functions
Control Flow: Often event-driven or message-based Primarily sequential
Side Effects: Minimized through encapsulation May be more prevalent

OOP CONCEPTS

Encapsulation

Encapsulation is the bundling of data and methods (functions) that operate on that data within a single unit, called a class. This hides the internal implementation details from the outside world, providing data security and preventing accidental modifications.

Polymorphism

Polymorphism allows objects of different types to be treated as if they were of the same type. This enables code to be written in a generic way, making it more flexible and reusable.

Inheritance

Inheritance is the mechanism where one class (derived class) acquires the properties and behaviors of another class (base class). This promotes code reusability and hierarchical relationships between classes.

Data Abstraction

Data abstraction is the process of hiding unnecessary details from the user and providing a simplified view of the data. This helps in managing complexity and focusing on essential features.

Class and Object

A class is a blueprint or template for creating objects. It defines the properties (data members) and behaviors (methods) that objects of that class will have. An object is an instance of a class. It represents a concrete realization of the class with its own values for the data members.

Examples that demonstrate the concepts above

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include <iostream>
#include <vector> // Include for std::vector

// Encapsulation: Data and methods are bundled into a single unit (class)
class Circle {
private:
  double radius;

public:
  void setRadius(double r) {
    radius = r;
  }

  double getArea() {
    return 3.14159 * radius * radius;
  }
};

// Inheritance: Creating a new class (Derived) from an existing one (Base)
class Cylinder : public Circle {
private:
  double height;

public:
  void setHeight(double h) {
    height = h;
  }

  double getVolume() {
    return getArea() * height; // Inherits getArea() from Circle
  }
};

// Polymorphism: Objects of different types can be treated as objects of a common base type
class Shape {
public:
  virtual void draw() = 0; // Pure virtual function (abstract class)
};

class Rectangle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing a rectangle\n";
  }
};

class Triangle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing a triangle\n";
  }
};

// Data Abstraction: Hiding Implementation details and exposing only the necessary interface
class BankAccount {
private:
  double balance; // Hidden data member

public:
  void deposit(double amount) {
    // Implementation details hidden (e.g., validation, transaction logging)
    balance += amount;
  }

  void withdraw(double amount) {
    // Implementation details hidden (e.g., checking balance, transaction fees)
    if (balance >= amount) {
      balance -= amount;
    } else {
      std::cout << "Insufficient funds!\n";
    }
}

  double getBalance() const {
    return balance;
  }
};

int main() {
  // Encapsulation
  Circle circle;
  circle.setRadius(5.0);

  std::cout << "Circle area: " << circle.getArea() << std::endl;

  // Inheritance
  Cylinder cylinder;
  cylinder.setRadius(3.0);
  cylinder.setHeight(10.0);

  std::cout << "Cylinder volume: " << cylinder.getVolume() << std::endl;

  // Polymorphism
  std::vector<Shape*> shapes = {new Rectangle(), new Triangle()};

  for (Shape* shape : shapes) {
    shape->draw();
  }

  // Important: Clean up memory to prevent leaks
  for (Shape* shape : shapes) {
    delete shape;
  }
shapes.clear(); // Good practice to clear the vector after deleting

  // Data Abstraction
  BankAccount account;

  account.deposit(1000.0);
  account.withdraw(500.0);

  std::cout << "Account balance: " << account.getBalance() << std::endl; // Access balance

  return 0;
}

Algorithms

What is an Algorithm?

An algorithm is a sequence of well-defined instructions designed to solve a specific problem or perform a task. Think of it as a recipe, where each step guides you towards a desired outcome. In computer science, algorithms are the backbone of problem-solving and software development.

Key Components of an Algorithm:

Creating an Algorithm:

If you want to create an effective algorithm you have to use the thinking process that I'm going to use below. You have to break the question into parts until you get the whole picture of the answer before you even write it down.

Let's consider a simple example: finding the average of a list of numbers.

Problem Definition:

Thinking Process:

  1. Understand the Problem: We need to calculate the sum of all numbers and divide it by the total count of numbers.
  2. Identify Variables:
    • sum: To store the sum of numbers.
    • count: To store the number of elements in the list.
    • average: To store the calculated average.
  3. Initialize Variables:
    • Set sum: To 0.
    • Set count: To 0.
  4. Iterate Through the List:
    • For each number in the list:
      • Add: The number to sum.
      • Increment: count.
  5. Calculate the Average:
    • Divide: sum by count and store the result in average.
  6. Return the Average:
    • Return: The calculated average.

Algorithm: Finding the Average of a List of Numbers

  1. Initialization:
    • Set: A variable sum to 0 to store the total sum of numbers.
    • Set: A variable count to 0 to store the number of elements in the list.
  2. Iteration:
    • Iterate: Through the list of numbers:
      • For each number:
        • Add: The number to sum.
        • Increment: count.
  3. Calculate the Average:
    • Divide: sum by count to get the average.
  4. Return the Average:
    • Return: The calculated average.

Pseudocode:

Step Description
1 Initialize sum and count to 0.
2 Iterate through the list of numbers.
3 Add each number to sum and increment count.
4 Calculate average by dividing sum by count.
5 Return the average.

In exams, write like this:

			FIND-AVERAGE(A)
			sum = 0
			count = 0
			for each number in A do
			    sum = sum + number
			    count = count + 1
			end for
			average = sum / count
			return average
				

C++ Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;

double calculateAverage(int arr[], int n) {
int sum = 0;
int count = n;

for (int i = 0; i < n; i++) {
sum += arr[i];
}

double average = (double)sum / count;
return average;
}

int main() {
int arr[] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);

double avg = calculateAverage(arr, n);
cout << "Average: " << avg << " endl;";

return 0;
}

Characteristics of an Algorithm

Benefits of Using Algorithms

Factors to Consider When Designing an Algorithm

Types of Algorithms

Brute Force

This approach tries all possible solutions to a problem and selects the best one. It's often inefficient for large problems.

Example: To find the maximum element in an array, brute force would compare each element with the current maximum.

Greedy

A greedy algorithm makes locally optimal choices at each step, hoping to arrive at a globally optimal solution. It doesn't always guarantee the best solution.

Example: To make change using the fewest coins, a greedy algorithm would choose the largest coin that is less than or equal to the remaining amount at each step.

Recursive

A recursive algorithm calls itself to solve smaller instances of the same problem. It's often used for problems that can be divided into smaller subproblems.

Example: To calculate the factorial of a number, a recursive algorithm can be defined as:

factorial(n) = n * factorial(n-1) if n > 1
 = 1 if n == 1

Backtracking

Backtracking explores different paths and backtracks if a path leads to a dead end. It's often used for problems with constraints.

Example: To solve the N-Queens problem (placing N queens on an N×N chessboard without attacking each other), backtracking can be used to explore different placements and backtrack if a queen is attacked.

Divide and Conquer

This approach divides a problem into smaller subproblems, solves the subproblems recursively, and combines the solutions to solve the original problem.

Example: To sort an array using merge sort, it's divided into two halves, sorted recursively, and then merged.

Dynamic Programming

Dynamic programming is used for problems that exhibit overlapping subproblems and optimal substructure. It avoids recomputing the same subproblems by storing their solutions in a table.

Example: To calculate the Fibonacci numbers, dynamic programming can be used to store the values of smaller Fibonacci numbers and use them to calculate larger ones.

Data Structures

Data structure is a way of organizing and storing data in a computer's memory so that it can be accessed and manipulated efficiently. It provides a logical representation of data and defines the relationship between different data elements.

Advantages of Using Data Structures

Linear vs. Non-Linear Data Structures

Feature Linear Data Structures Non-Linear Data Structures
Arrangement Elements are arranged sequentially. Elements are not arranged sequentially.
Access Elements are accessed one after another. Elements can be accessed in multiple ways.
Examples Arrays, linked lists, stacks, queues Trees, graphs, sets, maps
Traversal Elements are traversed in a linear fashion. Elements can be traversed in different ways (e.g., depth-first, breadth-first).
Memory allocation Contiguous memory allocation is often required. Memory allocation can be more flexible.
Complexity Operations like insertion, deletion, and search can have different complexities depending on the specific data structure. Operations can be more complex due to the non-linear arrangement.
Applications Used in various applications, including sorting, searching, and data processing. Used in applications like graph algorithms, database systems, and artificial intelligence.

Static vs. Dynamic Data Structures

Feature Static Data Structures Dynamic Data Structures
Memory allocation Memory is allocated at compile time. Memory is allocated at runtime.
Size The size of the data structure is fixed. The size can change dynamically.
Efficiency Can be more efficient for certain operations due to fixed memory allocation. Can be less efficient due to the overhead of memory allocation and deallocation.
Flexibility Less flexible as the size is fixed. More flexible as the size can change.
Examples Arrays Linked lists, stacks, queues, trees, graphs
Use cases Suitable for applications where the size of the data is known in advance. Suitable for applications where the size of the data is not known or can change during execution.

Major Operations

End of Outcome Quiz

1 of 20

    Quiz Score

    Percentage: 0%

    Answered Questions: 0

    Correct Answers: 0

    Faults: