Algorithm is a process or set of rules to be followed in calculations or other problem-solving operations, especially by a computer.

It is a definite way to accomplish a task with finite number of steps.

Pseudocode is a notation resembling a simplified programming language, used in program design.

For example:

Problem is :

Print the type of a grade of students in a class. If a student failed, remove the student from the class. If he has "excellent" or "good" grade, give him a bonus in the grade (each type of grade will have a different bonus).

The pseudocode is:

1. for student in class.students

1.1. if student.isFailed()

1.1.1 print "fail"

1.1.2 class.removeStudent(student)

1.2. else if student.isEnough()

1.2.1 print "enough"

1.3. else if student.isGood()

1.3.1 print" good"

1.3.2 class.giveBonus(student, "good")

1.4. else

1.4.1 print "excellent"

1.4.2 class.giveBonus(student, "excellent")

A method for problem solving and functions creation:

- Define the problem/function
- Write a solution in pseudocode
- Implement using programming language - Divide to functions and then implement the functions

Also there are these steps for writing a good solution:

- Inputs analysis - what can I learn from them like about the order of the data or another property/attribute
- Conditions analysis - what are the minimum conditions I can get from the inputs
- Minimum use of memory storage
- Minimum actions to achieve the solution
- Nesting Level - Keep a low nesting level and try to be with less than O(N^2) of the performance
- Dividing tasks to sub-tasks
- Generalization - The solution need to work with all use cases of the give problem.

Recursion is a method of solving problems that involves breaking a problem down into smaller and smaller subproblems until you get to a small enough problem that it can be solved trivially. Usually recursion involves a function calling itself.

These are the steps for solving recursion problem:

- Problem definition
- Definition of problem with one step less (with less difficulty)
- Adding solution to current step
- Stop condition

For example, the given problem is to compute factory of a given integer greater than 0.(let's call it "n").

With iterative solution you would solve it like this:

function factory(n)

1. result = 1

2. for (i = 1; i <= n; i++)

2.1 result = result * i

3. return result

In recursive solution you would solve it like this:

function factory(n)

1. if n equals 1

1.1 return 1

2. return factory(n - 1) * n

Here, the stop condition is if n equals 1. If n is 1 then return 1 because factory of 1 is 1.

Then we define the problem of the problem with one less step (that it is factory of number less by 1) and then we adding solution to current step (which is multiplying by the current number).

Recursion is used with data structures such as binary trees and graphs.

You can also use divide and conquer solution method which is dividing the problem to to smaller sub-problems, solving them and eventually combining them to get the desired output. The performance of this solution is very efficient.

Use strategies to divide big tasks into smaller tasks:

**Bottom-Up**- solving the smallest tasks and then combining them to get a solution**Top-Down**- breaking the task to smaller tasks and solving them at each step**Middle-Out**- a combination of Top-Down and Bottom-Up approaches, where you start "in the middle" and then work up to the top and down to the bottom from there.

Performance is very important for programming. The efficiency of an algorithm depends on the amount of time, storage and other resources required to execute it.

It may not have the same performance for different type of inputs.

There are different asymptotic analysis for different use cases of the algorithm such as:

**Big-O notation**- worst case complexity**Omega notation -**best case complexity**Theta notation -**average case complexity

They represent the growth rate of an algorithm as the input size increases.

For example, if an algorithm has a time complexity of O(n^2), it means that the algorithm's time requirements grow no faster than the square of the input size.

Usually, you should use the Big-O notation to analysis your algorithm in order to make an algorithm to be the most efficient in the worst cases.

Here are more examples about different time complexities:

**O(1) - Constant Time Complexity**: For example, Accessing an element in an array by its index. No matter how large the array is, the time taken to access an element remains constant.******O(log n) - Logarithmic Time Complexity**: For example, binary search algorithm. As the input size grows, the number of steps required to find an element increases but at a decreasing rate due to halving the search space in each step.**O(n) - Linear Time Complexity**: For example, calculating the sum of all elements in an array. The time taken to compute the sum increases linearly with the size of the array.**O(n log n) - Linearithmic Time Complexity**: For example, efficient sorting algorithms like Merge Sort and Heap Sort. They divide the input into smaller parts recursively, resulting in a time complexity of n log n.**O(n^2) - Quadratic Time Complexity**: For example, nested loops iterating over the same array. For each element in the array, it compares or performs an operation with every other element, resulting in a quadratic time complexity.

Here are more tips:

- Assume there is infinite number of data until EOD (end of data) in order to get a generalized solution
- Assume you are in current state and continue accordingly. You can implement that you are in that condition if neccessary.

For example, instead of:

function big(int a, int b)

1. if a greater than b

1.1 return a

2. else

2.1 return b

do:

function big(int a, int b)

1. if a greater than b

1.1 return a

2. return b - Switch without break in one of the cases.

These are some important categories of algorithms used with data structures:

**Search**− Algorithm to search an item in a data structure.**Sort**− Algorithm to sort items in a certain order.**Insert**− Algorithm to insert item in a data structure.**Update**− Algorithm to update an existing item in a data structure.**Delete**− Algorithm to delete an existing item from a data structure.

Each process has input, output, and error output.

**Standard input** is the file handle that a process reads to get information from (can be information from a user).**Standard output** is the file handle where the process writes conventional output to.**Standard error** is the file handle where the process writes diagnostic output to.

You can modify a program to redirect stderr into stdout.

For example in JavaScript you do this by running npm start 2>&1.

Building upon your algorithmic knowledge, we then explore the world of data structures. Data structures are essential for organizing and managing data efficiently, enabling us to store, retrieve, and manipulate information with ease.