
Computer Science entails the exploration of computers and computational systems. In contrast to professionals in electrical and computer engineering, computer scientists primarily engage with software and its various systems. This encompasses the realms of theory, design, advancement, and practical implementation.
Key domains of inquiry within the field of Computer Science encompass artificial intelligence, computer systems and networks, cybersecurity, database systems, human-computer interaction, visual processing and graphics, numerical analysis, programming languages, software engineering, bioinformatics, and the theoretical foundations of computing.
In this introductory instructional video on Programming with C++, the salient attributes and capabilities of C++ are elucidated, offering students an overarching view of the problem-solving potential inherent in this lower-level programming language.
The video effectively underscores certain noteworthy features, including:
Its classification as a Low-Level Language, closely aligned with machine language intricacies.
Profound Object-Oriented Support, facilitating structured programming paradigms.
Lineage tracing back to the C programming language.
Abundant and comprehensive built-in libraries such as <iostream>, <cmath>, <cstdlib>, and <fstream>.
Evident High Execution Speed, contributing to expedient program performance.
The discourse culminates by delineating the domains in which C++ exhibits remarkable prowess, notably encompassing:
Operating Systems
Video Game Design
Graphical User Interface (GUI) Development
Web Browsing Applications
Embedded Systems
Throughout the video, these concepts are expounded upon using numerous illustrative examples and thorough analytical dissections, ensuring optimal comprehension and assimilation.
In this instructional video centered on Programming with C++, the objective is to enhance one's grasp of C++ operators, cultivating a more refined comprehension of their functions and applications.
An operator, in its essence, represents a symbol employed to execute various operations, encompassing a broad spectrum including arithmetic, logical, and bitwise operations.
The C++ language accommodates a spectrum of operator categories designed to execute diverse operations, which encompass:
- Arithmetic Operators such as +, -, and *
- Relational Operators including <, >, <=, and >=
- Logical Operators represented by && and ||
- Bitwise Operators such as ^, |, and &
- Assignment Operator signified by =, =+, and =-
- Unary Operators in the forms of + and -
- Ternary or Conditional Operator recognized as ?:
- Miscellaneous Operator category
The precedence of operators determines the sequence in which they are evaluated, signifying which operator takes precedence over others. Associativity, on the other hand, denotes the direction of evaluation—whether proceeding from left to right or right to left.
Throughout the video presentation, these concepts are adeptly expounded upon, fortified by a profusion of illustrative examples and comprehensive analysis, thereby facilitating a more accessible and thorough assimilation of the subject matter.
"In this Programming with C++ video, we will brush up on if-else conditionals and switch statements.
If statement is used to test the condition. There are various types of if statements in C++.
- if statement
- if-else statement
- nested if statement
- if-else-if ladder
The C++ if statement tests the condition. It is executed if condition is true.
#include <iostream>
using namespace std;
int main () {
int num = 10;
if (num % 2 == 0)
{
cout<<""It is even number"";
}
return 0;
}
#include <iostream>
using namespace std;
int main () {
int num = 11;
if (num % 2 == 0)
{
cout<<""It is even number"";
}
else
{
cout<<""It is odd number"";
}
return 0;
}
C++ If-else Ladder
if(condition1){
//code to be executed if condition1 is true
}else if(condition2){
//code to be executed if condition2 is true
}
else if(condition3){
//code to be executed if condition3 is true
}
...
else{
//code to be executed if all the conditions are false
}
Switch Statements - The C++ switch statement executes one statement from multiple conditions. It is like if-else-if ladder statement in C++.
int main () {
int num;
cout<<""Enter a number to check grade:"";
cin>>num;
switch (num)
{
case 10: cout<<""It is 10""; break;
case 20: cout<<""It is 20""; break;
case 30: cout<<""It is 30""; break;
default: cout<<""Not 10, 20 or 30""; break;
}
}
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding."
Within this educational presentation on Programming with C++, an in-depth exploration of loops is undertaken, aiming to provide a comprehensive grasp of fundamental concepts related to iteration in the C++ programming language.
The curriculum entails a thorough examination of diverse loop types, including the For loop, While loop, Do-While loop, and Nested loop, elucidated through practical and hands-on exemplifications.
For Loop: The For loop structure is expounded upon through the following illustration, demonstrating its application in generating sequential outputs:
for (int i = 0; i < 5; i++) {
cout << i << "\n";
}
This example showcases the utilization of a For loop to print a table of numbers ranging from 1 to 10.
While Loop: The While loop is introduced, with the ensuing code snippet showcasing its employment to facilitate user input reception until a negative number is entered:
int i = 0;
while (i < 5) {
cout << i << "\n";
i++;
}
This illustration effectively conveys the utilization of a While loop to persistently solicit user input until a negative integer is provided.
Do-While Loop: The Do-While loop is thoroughly explicated, as demonstrated in the subsequent code excerpt that encapsulates its functionality:int i = 0;
do {
cout << i << "\n";
i++;
}
while (i < 5);
This exemplification underscores the characteristic of the Do-While loop, wherein the loop body is executed at least once. This is showcased through the creation of a menu-driven program, allowing users to make selections.
Throughout the presentation, these concepts are augmented with a plethora of practical examples and comprehensive analyses, affording a comprehensible and accessible understanding of the subject matter.
This educational segment on Programming with C++ elucidates three pivotal control flow keywords, unveiling their roles and functionalities.
Break Statement: The "break" keyword is employed to terminate the execution of a loop or switch statement. This abrupt termination occurs when a specified condition is met, disrupting the ongoing program flow. It's noteworthy that, within nested loops, "break" exclusively terminates the innermost loop.
int main() {
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
cout << i << "\n";
}
}
Continue Statement: The "continue" statement serves to advance the flow of a loop, effectively skipping the remaining code within the loop body for a particular iteration. Similar to "break," in nested loops, "continue" pertains exclusively to the inner loop.for (int i = 1; i <= 10; i++) {
if (i == 5) {
continue;
}
cout << i << "\n";
}
Goto Statement: The "goto" keyword, functioning as a jump statement, is utilized to shift control to a designated section of the program. This unconditional jump entails transferring program execution to a specified label. "Goto" is particularly employed for navigating out of deeply nested loops or reaching specific switch case labels.int main() {
ineligible:
cout << "You are not eligible to vote!\n";
cout << "Enter your age:\n";
int age;
cin >> age;
if (age < 18) {
goto ineligible;
} else {
cout << "You are eligible to vote!";
}
}
Throughout the instructional content, the material is accentuated through an assortment of illustrative examples and meticulous analysis, ensuring an accessible and comprehensive understanding of the concepts at hand.
This educational presentation on Programming with C++ delves into two important topics: writing comments in C++ and defining functions.
Comments in C++: Comments are statements that are not executed by the compiler but serve as explanatory annotations within the code. Comments can elucidate the purpose of code snippets, variables, methods, or classes. There are two categories of comments in C++:
Single Line Comment:
int x = 11; // x is a variable
Multi-Line Comment:
/* This is a multi-line comment
used to declare and print variables in C++. */
int x = 35;
These comment types are invaluable for enhancing code comprehension and can be used to both clarify the code's functionality and hide certain portions of it.
Functions: Functions are instrumental in executing tasks within a program and can be invoked multiple times, promoting modularity and code reusability. Two key categories of functions are discussed:
Library Functions: These functions are predefined in C++ header files and encompass operations such as ceil(x), cos(x), exp(x), among others.
User-defined Functions: Crafted by programmers to enhance code organization and optimize complexity in substantial programs.
An example of a user-defined function is provided:
void func() {
static int i = 0;
int j = 0;
i++;
j++;
cout << "i=" << i << " and j=" << j << endl;
}
int main() {
func();
}
Throughout the content, the presentation combines ample illustrative instances and thorough analyses to facilitate a comprehensive grasp of the material.
This instructional segment on Programming with C++ delves into two methodologies for passing parameters to functions: Call by Value and Call by Reference.
Call by Value: In this method, the value passed to a function is stored locally within the function parameter's stack memory location. Alterations made to the function parameter value solely affect the current function instance. The original value remains unaffected.
void change(int data);
int main() {
int data = 3;
change(data);
cout << "Value of the data is: " << data << endl;
return 0;
}
void change(int data) {
data = 5;
}
// Output: Value of the data is: 3
Call by Reference: This approach entails passing the address of the value as a function parameter. Consequently, both the actual and formal arguments share the same memory address. Any changes made to the value via the formal parameter directly impact the actual parameter.
void swap(int *x, int *y);
int main() {
int x = 500, y = 100;
swap(&x, &y);
cout << "Value of x is: " << x << endl;
cout << "Value of y is: " << y << endl;
return 0;
}
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
// Output: Value of x is: 100
// Value of y is: 500
Throughout the content, these concepts are elucidated using ample examples and comprehensive analyses, rendering the material accessible and deeply comprehensible.
This educational segment on Programming with C++ focuses on the concept of recursion, which is often considered challenging for beginners.
Recursion is a situation in which a function calls itself. The function that invokes itself is referred to as a recursive function.
An illustrative example of a recursive function calculates the factorial of a number:
#include <iostream>
using namespace std;
int factorial(int n) {
if (n < 0)
return -1;
if (n == 0)
return 1;
else
return n * factorial(n - 1);
}
int main() {
int fact, value;
cout << "Enter any number: ";
cin >> value;
fact = factorial(value);
cout << "Factorial of a number is: " << fact << endl;
return 0;
}
In this example, the factorial() function recursively calls itself with decreasing values of n. As n approaches 1, the recursive calls unravel, ultimately providing the final output.
Recursion is a crucial tool in solving problems related to data structures and advanced algorithms, particularly in scenarios like graph and tree traversal.
Throughout the content, the material is illuminated through comprehensive examples and in-depth analysis, facilitating a coherent and accessible understanding of the topic.
This instructional video on Programming with C++ delves into the subject of storage classes, which facilitate the declaration and management of variable types by defining their lifetime and visibility within the scope of a program.
C++ provides four primary storage classes:
Automatic: This is the default storage class for all local variables. The "auto" keyword is applied to local variables by default. These variables are automatically allocated memory when they come into scope and are deallocated when they go out of scope.
Register: A "register" variable is stored in a CPU register rather than in RAM memory. This allows for faster access compared to other variables. However, the size of a register variable is limited by the size of a CPU register.
Static: A "static" variable is initialized only once and retains its value between different function calls. It exists throughout the duration of the program and retains its value across multiple function calls. The default value for static variables is 0.
External: An "extern" variable is visible to all programs. It is used when multiple files need to share the same variable or function. An extern variable or function is declared in one file and can be accessed in other files that include its declaration.
Throughout the video, these storage class concepts are elucidated using a multitude of examples and comprehensive analyses, providing a thorough and accessible understanding of the topic.
In this instructive video on Programming with C++, we delve into a comprehensive exploration of arrays, which are fundamental linear data structures. An array in C++ constitutes a collection of elements of the same data type, residing in contiguous memory locations.
As detailed in the video, the following properties of arrays are highlighted:
Indexing Begins at 0: Array elements are accessed using indices, and the index count commences at 0. Arrays hold a predetermined number of elements in C++.
- **Random Access**: Elements within an array can be directly accessed using their respective indices.
- **Ease of Traversal, Manipulation, and Sorting**: Arrays facilitate convenient traversal, manipulation, and sorting operations.
This discussion encompasses two primary array types:
Single-Dimensional Array:
```cpp
int arr[5] = {10, 0, 20, 0, 30};
for (int i = 0; i < 5; i++) {
cout << arr[i] << "\n";
}
```
Multidimensional Array: This category, including 2D and 3D arrays, will be expounded upon in a separate video.
Throughout the presentation, these concepts are fortified with a multitude of illustrative examples and in-depth analyses, facilitating a comprehensive and accessible understanding of the material.
In this comprehensive Programming with C++ video, we delve into performing operations on arrays by passing them as function arguments, as well as exploring the fundamentals of pointers in C++.
Passing Arrays to Functions: To pass an array to a function in C++, it is sufficient to provide the array name as an argument.
void printArray(int arr[5]);
int main() {
int arr1[5] = { 10, 20, 30, 40, 50 };
int arr2[5] = { 5, 15, 25, 35, 45 };
printArray(arr1); // Passing array to function
printArray(arr2);
}
void printArray(int arr[5]) {
cout << "Printing array elements:" << endl;
for (int i = 0; i < 5; i++) {
cout << arr[i] << "\n";
}
}
As showcased in the video, another approach is utilizing pointers to accomplish the same task. Pointers play a crucial role in Dynamic Memory Allocation.
Advantages of Pointers:
Pointers enhance performance by enabling direct memory access.
They allow functions to return multiple values through pointer references.
Pointers enable access to any memory location within the computer's memory.
int main() {
int number = 30;
int *p;
p = &number;
cout << "Address of number variable is: " << &number << endl;
cout << "Address of p variable is: " << p << endl;
cout << "Value of p variable is: " << *p << endl;
return 0;
}
Throughout the presentation, these concepts are elucidated using a multitude of illustrative examples and comprehensive analyses, rendering the material accessible and deeply comprehensible.
In this informative Programming with C++ video, we will delve into the intricacies of multidimensional arrays, a topic that has been previously mentioned in one of our earlier videos.
Multidimensional arrays, often referred to as rectangular arrays, can encompass two dimensions or even extend to three dimensions. The data within these arrays is arranged in a tabular form, resembling a matrix structure with rows and columns.
An illustrative example is provided to elucidate this concept:
int main() {
int test[3][3] = {
{2, 5, 5},
{4, 0, 3},
{9, 1, 8}
};
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
cout << test[i][j] << " ";
}
cout << "\n"; // New line at each row
}
return 0;
}
In this example, a 3x3 matrix named test is initialized with values. A nested loop structure iterates through the rows and columns, printing the array elements and creating a new line after each row.
Throughout the video, these concepts are illustrated using various examples and detailed analyses, providing a comprehensive and accessible understanding of the topic.
In this comprehensive Programming with C++ video, we will delve into three crucial concepts: pointers, the sizeof() operator, and the concept of void pointers.
Pointers in the C++ language are variables that serve as locators or indicators, pointing to the memory address of a value.
Advantages of Pointers:
Enhances performance by enabling direct memory access.
Facilitates returning multiple values from functions via pointers.
Enables accessing any memory location in a computer's memory.
Address Operator (&): The ampersand sign is the address operator used to determine the memory address of a variable.
Indirection Operator (*): The asterisk sign represents the indirection operator, used to access the value stored at a particular memory address.
int main() {
int number = 30;
int *p;
p = &number;
cout << "Address of number variable is: " << &number << endl;
cout << "Address of p variable is: " << p << endl;
cout << "Value of p variable is: " << *p << endl;
return 0;
}
sizeof() Operator: The sizeof() operator calculates the size of data types, constants, and variables.
std::cout << "Size of integer data type: " << sizeof(int) << std::endl;
std::cout << "Size of float data type: " << sizeof(float) << std::endl;
Void Pointers: A void pointer is a versatile pointer that can store the address of any data type. However, it is not associated with any specific data type.
void *ptr; // void pointer declaration
int *ptr1; // integer pointer declaration
int data = 10; // integer variable initialization
ptr = &data; // storing the address of data variable in void pointer variable
ptr1 = (int *)ptr; // assigning void pointer to integer pointer
std::cout << "The value of *ptr1 is: " << *ptr1 << std::endl;
return 0;
Throughout the presentation, these concepts are reinforced with various illustrative examples and in-depth analyses, ensuring a comprehensive and accessible understanding of the material.
"In this Programming with C++ video, we will study about a variety of intermediate topics relating to pointers and references. In previous videos we have gone through standard variables and pointers but here you are being introduced to a new concept of references.
It is a variable that behaves as an alias for another variable. They are created using the ampersand (&) operator. When we create a variable, then it occupies some memory location. We can create a reference of the variable; therefore, we can access the original variable by using either name of the variable or reference
CODE:
//Example 1
int a=10;
int &ref=a;
//Example 2
int a=10; // 'a' is a variable.
int &b=a; // 'b' reference to a.
int &c=a; // 'c' reference to a.
- Must be initialized during declaration.
- Cannot be reassigned
- Can also be passed as a function parameter.
Now we study references vs pointers:-
POINTERS
- A pointer can be initialized to any value anytime after it is declared.
- A pointer can be assigned to point to a NULL value.
- Pointers need to be dereferenced with a *
- A pointer can be changed to point to any variable of the same type.
REFERENCES
- A reference must be initialized when it is declared.
- References cannot be NULL.
- References can be used ,simply, by name.
- Once a reference is initialized to a variable, it cannot be changed to refer to a variable object.
In the final topic for this video, we will understand that function pointer is a pointer used to point functions. It is used to store the address of a function. We can call the function by using the function pointer, or we can also pass the pointer to another function as a parameter.
They are mainly useful for event-driven applications, callbacks, and even for storing the functions in arrays.
void printname(char *name)
{
std::cout << ""Name is :"" <<name<< std::endl;
}
int main()
{
char s[20];
void (*ptr)(char*); // function pointer declaration
ptr=printname; // storing the address of printname in ptr.
std::cout << ""Enter the name of the person: "" << std::endl;
cin>>s;
cout<<s;
ptr(s);
return 0;
}
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding."
"In this Programming with C++ video, we will do a deep-dive in memory management.
Memory Management:
As explained in the video, Memory management is a process of managing computer memory, assigning the memory space to the programs to improve the overall system performance. Sometimes, exact memory is not determined until runtime. To avoid such a situation, we declare an array with a maximum size, but some memory will be unused. To avoid the wastage of memory, we use the new operator to allocate the memory dynamically at the run time.
A new operator is used to create the object.
A delete operator is used to delete the object.
CODE:
int main()
{
int size; // variable declaration
int *arr = new int[size]; // creating an array
cout<<""Enter the size of the array : "";
std::cin >> size; //
cout<<""\nEnter the element : "";
for(int i=0;i<size;i++) // for loop
{
cin>>arr[i];
}
cout<<""\nThe elements that you have entered are :"";
for(int i=0;i<size;i++) // for loop
{
cout<<arr[i]<<"","";
}
delete arr; // deleting an existing array.
return 0;
Malloc() vs new
- The new operator constructs an object, i.e., it calls the constructor to initialize an object while malloc() function does not call the constructor. The new operator invokes the constructor.
- The new is an operator, while malloc() is a predefined function in the stdlib header file.
- In the new operator, we need to specify the number of objects to be allocated while in malloc() function, we need to specify the number of bytes to be allocated.
- For new operator, we have to use the delete operator to deallocate the memory. For malloc() function, we need the free() function.
Delete() vs free
- The delete is an operator that de-allocates the memory dynamically while the free() is a function that destroys the memory at the runtime.
- When the delete operator destroys the allocated memory, then it calls the destructor of the class in C++, whereas the free() function does not call the destructor; it only frees the memory from the heap.
- The delete() operator is faster than the free() function.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding."
"In this Programming with C++ video, we will study about concepts of Object Oriented Programming such as inheritance, data binding, polymorphism etc.
- Object - Any entity that has state and behavior is known. It is an instance of class.
- Class - Collection of objects is called class.
- Inheritance - When one object acquires all the properties and behaviors of parent object i.e. known as inheritance.
- Polymorphism - When one task is performed by different ways i.e. known as polymorphism.
- Abstraction - Hiding internal details and showing functionality is known as abstraction
- Encapsulation - Binding (or wrapping) code and data together into a single unit is known as encapsulation
Code for Creating Objects and Classes in C++:
class Student {
public:
int id;//data member (also instance variable)
string name;//data member(also instance variable)
};
int main() {
Student s1; //creating an object of Student
s1.id = 201;
s1.name = ""Board Infinity"";
cout<<s1.id<<endl;
cout<<s1.name<<endl;
return 0;
}
In future videos, we will explore all the OOP concepts in much more detail. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding."
"In this Programming with C++ video, we will study about constructors and destructors and how they make it easier to instantiate objects and assign them values rapidly.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
Constructor is a special method which is invoked automatically at the time of object creation and used to initialize the data members of new object generally.
There can be two types of constructors in C++.
- Default constructor
- Parameterized constructor
Example-
class Employee
{
public:
Employee()
{
cout<<""Default Constructor Invoked""<<endl;
}
};
int main(void)
{
Employee e1; //creating an object of Employee
Employee e2;
return 0;
}
Copy Constructor:
A Copy constructor is an overloaded constructor used to declare and initialize an object from another object. It is useful when
we initialize the object with another existing object of the same class type. For example, Student s1 = s2, where Student is the class.
class A
{
public:
int x;
A(int a) // parameterized constructor.
{
x=a;
}
A(A &i) // copy constructor
{
x = i.x;
}
};
int main()
{
A a1(20);
A a2(a1);
cout<<a2.x;
return 0;
}
Destructors:
As explained in the video, A destructor works opposite to constructor; it destructs the objects of classes. It can be defined only once in a class. Like constructors, it is invoked automatically and prefixed with a tilde sign (~).
Example-
class Employee
{
public:
Employee()
{
cout<<""Constructor Invoked""<<endl;
}
~Employee()
{
cout<<""Destructor Invoked""<<endl;
}
};
int main(void)
{
Employee e1; //creating an object of Employee
Employee e2; //creating an object of Employee
return 0;
} "
"In this Programming with C++ video, we will study about more data types that make the object oriented programming reliable and easy to use like structs and enums along with studying class related concepts like this pointer and static keyword.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
THIS POINTER:
It is a keyword that refers to the current instance of the class. There can be 3 main usage of this keyword in C++.
- It can be used to pass current object as a parameter to another method.
- It can be used to refer current class instance variable.
- It can be used to declare indexers.
CODE:
class Employee {
public:
int id; //data member (also instance variable)
string name; //data member(also instance variable)
float salary;
Employee(int id, string name, float salary)
{
this->id = id;
this->name = name;
this->salary = salary;
}
void display()
{
cout<<id<<"" ""<<name<<"" ""<<salary<<endl;
}
};
int main(void) {
Employee e1 =Employee(101, ""abc"", 890000);
Employee e2=Employee(102, ""xyz"", 59000);
e1.display();
e2.display();
return 0;
}
STATIC:
It is a is a keyword or modifier that belongs to the type not instance. So instance is not required to access the static members. In C++, static can be field, method, constructor, class, properties, operator and event. Useful when counting total number of objects created for a class.
Code Snippet -
class Account {
public:
int accno;
string name;
static int count;
Account(int accno, string name)
{
this->accno = accno;
this->name = name;
count++;
}
void display()
{
cout<<accno<<"" ""<<name<<endl;
}
};
Structs:
Structure is a collection of different data types. It is similar to the class that holds different types of data.
Structure variable can be defined as:
Student s;
Variables of structures can be accessed using dot operator just like classes.
struct Rectangle
{
int width, height;
};
int main(void) {
struct Rectangle rec;
rec.width=8;
rec.height=5;
cout<<""Area of Rectangle is: ""<<(rec.width * rec.height)<<endl;
return 0;
}
Enum:
It is a data type that contains fixed set of constants. They are static and final implicitly.
enum week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
int main()
{
week day;
day = Friday;
cout << ""Day: "" << day+1<<endl;
return 0;
}
This will output day as 5.
"
"In this Programming with C++ video, we will study about friend functions and classes and their use cases in great detail. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
If a function is defined as a friend function in C++, then the protected and private data of a class can be accessed using the function. By using the keyword friend compiler knows the given function is a friend function. For accessing the data, the declaration of a friend function should be done inside the body of a class starting with the keyword friend.
As explained in the video, it has the following characteristics:-
- It cannot access the member names directly and has to use an object name and dot membership operator with the member name.
- It is not in the scope of the class to which it has been declared as a friend.
- It cannot be called using the object as it is not in the scope of that class.
- It can be invoked like a normal function without using the object.
- It can be declared either in the private or the public part.
CODE:
class Box
{
private:
int length;
public:
Box(): length(0) { }
friend int printLength(Box); //friend function
};
int printLength(Box b)
{
b.length += 10;
return b.length;
}
int main()
{
Box b;
cout<<""Length of box: ""<< printLength(b)<<endl;
return 0;
}
Just like friend functions, we can also create friend classes. "
"In this Programming with C++ video we study about basic math functions which require the header file<math.h>
In the video, each of these functions is explained with a code example:-
- cos(x) - computes the cosine of x.
- sin(x) - computes the sine of x.
- tan(x) - computes the tangent of x.
- pow(x,y) - computes x raised to the power y.
- sqrt(x) - computes the square root of x.
- ceil(x) - rounds up the value of x.
- floor(x) - rounds down the value of x.
- round(x) - rounds off the value of x."
"In this Programming with C++ video, we will study about more advanced OOP concepts like Inheritance, its extension called as Aggregation and Polymorphism. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
INHERTIANCE:
The class which inherits the members of another class is called derived class and the class whose members are inherited is called base class. The derived class is the specialized class for the base class.
Inheritance is a process in which one object acquires all the properties and behaviors of its parent object automatically. In such way, you can reuse, extend or modify the attributes and behaviors which are defined in other class.
As demonstrated in the video, C++ offers 5 types of inheritance:
- Single inheritance - Where 'A' is the base class, and 'B' is the derived class.
- Multiple inheritance - class 'C' inherits two base classes 'A' and 'B'
- Hierarchical inheritance
- Multilevel inheritance - Class C inherits from Class B which in turn inherits from Class A
- Hybrid inheritance - Mixed inheritance from all the other types.
CODE:
class Shape
{
public:
int a;
int b;
void get_data(int n,int m)
{
a= n;
b = m;
}
};
class Rectangle : public Shape
{
public:
int rect_area()
{
int result = a*b;
return result;
}
};
class Triangle : public Shape
{
public:
int triangle_area()
{
float result = 0.5*a*b;
return result;
}
};
AGGREGATION:
As explained in the video, aggregation is a process in which one class defines another class as any entity reference. It is another way to reuse the class. It is a form of association that represents HAS-A relationship.
POLYMORPHISM:
When the same function demonstrates different behavior it is known as polymorphism and it is of two types:-
- Compile time polymorphism: The overloaded functions are invoked by matching the type and number of arguments.
- Run time polymorphism: Run time polymorphism is achieved when the object's method is invoked at the run time instead of compile time
We will be studying these in the following videos in greater detail. "
"In this Programming with C++ video, we will study about two important concepts related to Classes and inheritance. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
OVERLOADING:
Creating two or more members having the same name but different in number or type of parameter is known as overloading.
C++ allows the overloading of :-
- methods,
- constructors, and
- indexed properties
and is of two types:-
- Function overloading - having two or more function with the same name, but different in parameters.
CODE:
class Cal {
public:
static int add(int a,int b){
return a + b;
}
static int add(int a, int b, int c)
{
return a + b + c;
}
};
int main(void) {
Cal C; // class object declaration.
cout<<C.add(10, 20)<<endl;
cout<<C.add(12, 20, 23);
return 0;
}
- Operator overloading - operator is overloaded to provide the special meaning to the user-defined data type. Operator overloading is used to overload or redefines most of the operators available in C++
Some exceptions to this are:-
Scope operator (::), Sizeof, member selector(.), member pointer, selector(*), ternary operator(?:)
There are also some norms associated with overloading operators:-
- Existing operators can only be overloaded.
- The overloaded operator contains at least one operand of the user-defined data type.
- When unary operators are overloaded through a member function take no explicit arguments, but, if they are overloaded by a friend function, takes one argument.
- When binary operators are overloaded through a member function takes one explicit argument, and if they are overloaded through a friend function takes two explicit arguments.
class Test
{
private:
int num;
public:
Test(): num(8){}
void operator ++() {
num = num+2;
}
void Print() {
cout<<""The Count is: ""<<num;
}
};
int main()
{
Test tt;
++tt; // calling of a function ""void operator ++()""
tt.Print();
return 0;
}
OVERRIDING
If derived class defines same function as defined in its base class, it is known as function overriding and it enables you to provide specific implementation of the function which is already provided by its base class.
class Animal {
public:
void eat(){
cout<<""Eating..."";
}
};
class Dog: public Animal
{
public:
void eat()
{
cout<<""Eating bread..."";
}
};
int main(void) {
Dog d = Dog();
d.eat();
return 0;
} "
"In this Programming with C++ video, we will study about virtual functions and how to properly use them in the correct situation.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
A virtual function is a member function in the base class that you redefine in a derived class. It is declared using the virtual keyword.
- Used to message compiler to perform dynamic linkage or late binding on the function.
- Must be members of some class.
- Cannot be static members.
- Accessed through object pointers.
- Can be a friend of another class.
- Must be defined in the base class, even though it is not used.
- We cannot have a virtual constructor, but we can have a virtual destructor.
CODE:
#include <iostream>
{
public:
virtual void display()
{
cout << ""Base class is invoked""<<endl;
}
};
class B:public A
{
public:
void display()
{
cout << ""Derived Class is invoked""<<endl;
}
};
int main()
{
A* a; //pointer of base class
B b; //object of derived class
a = &b;
a->display();
}
Further, a ""do-nothing"" function is known as a pure virtual function. A pure virtual function is a function declared in the base class that has no definition relative to the base class.
virtual void display() = 0;
"
"In this Programming with C++ video, we will study about Abstraction. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
Abstraction is a process of providing only the essential details to the outside world and hiding the internal details, i.e., representing only the essential details in the program. It can be achieved in C++ using header files or wrapping program logic in classes.
As explained in the video, it should be employed when you want to:-
- Protect details of the class from user level errors.
- not need to write the low level code.
- avoid code duplication.
CODE:
#include <iostream>
using namespace std;
class Sum
{
private: int x, y, z;
public:
void add()
{
cout<<""Enter two numbers: "";
cin>>x>>y;
z= x+y;
cout<<""Sum of two number is: ""<<z<<endl;
}
};
int main()
{
Sum sm;
sm.add();
return 0;
}
Another example would be in-built functions and how they simply allow the usage of a feature without having the programmer to know their implementation."
"In this Programming with C++ video, we will study about interfaces and use our knowledge from the previous video about abstraction. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
An interface describes the behavior or capabilities of a C++ class without committing to a particular implementation of that class.
Interfaces are implemented using abstract classes.
As demonstrated in the video, a class is made abstract by declaring at least one of its functions as pure virtual function. A pure virtual function is specified by placing ""= 0"" in its declaration as follows −
CODE:
using namespace std;
class Shape
{
public:
virtual void draw()=0;
};
class Rectangle : Shape
{
public:
void draw()
{
cout < <""drawing rectangle..."" < <endl;
}
};
class Circle : Shape
{
public:
void draw()
{
cout <<""drawing circle..."" < <endl;
}
};
int main( ) {
Rectangle rec;
Circle cir;
rec.draw();
cir.draw();
return 0;
} "
"In this Programming with C++ video, we will study about namespaces. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
Namespaces in C++ are used to organize too many classes so that it can be easy to handle the application.
For accessing the class of a namespace, we need to use namespacename::classname. Use using keyword so that we don't have to use complete name all the time.
Commonly addressed namespace is ""std"" provided by C++ Framework.
CODE:
using namespace std;
namespace First{
void sayHello(){
cout << ""Hello First Namespace"" << endl;
}
}
namespace Second{
void sayHello(){
cout << ""Hello Second Namespace"" << endl;
}
}
using namespace First;
int main () {
sayHello();
return 0;
} "
"In this Programming with C++ video, we will study about Strings and the functions associated with them.
As seen in the video, String is an object of std::string class that represents sequence of characters. We can perform many operations on strings such as concatenation, comparison, conversion etc.
The C++ compiler automatically places the '\0' at the end of the string when it initializes the array.
CODE:
string s1 = ""Hello"";
char ch[] = { 'C', '+', '+'};
string s2 = string(ch);
cout<<s1<<endl;
cout<<s2<<endl;
We see some commonly used functions related to strings in the video like:-
- strcpy(s1, s2) - Copies string s2 into string s1.
- strcat(s1, s2) - Concatenates string s2 onto the end of string s1.
- strlen(s1) - Returns the length of string s1.
- strcmp(s1, s2) - Returns 0 if s1 and s2 are the same; less than 0 if s1<s2; greater than 0 if s1>s2.
- strchr(s1, ch) - Returns a pointer to the first occurrence of character ch in string s1.
- strstr(s1, s2) - Returns a pointer to the first occurrence of string s2 in string s1.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding. "
"In this Programming with C++ video, we will study about a new topic known as Exception Handlin which is a process to handle runtime errors.
Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
It is required so that the normal flow of the application can be maintained even after runtime errors. In C++, exception is an event or object which is thrown at runtime. All exceptions are derived from std::exception class. It is a runtime error which can be handled. If we don't handle the exception, it prints exception message and terminates the program.
Standard exceptions are defined in <exception> class that we can use inside our programs.
There are 3 keywords to perform exception handling:
- try
- catch, and
- throw
"In this Programming with C++ video, we will study about try/catch block and how they are useful to handle exceptions in C++. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
try block is used to place the code that may occur exception.
catch block is used to handle the exception.
Example Code using try/catch -
#include <iostream>
using namespace std;
float division(int x, int y) {
if( y == 0 ) {
throw ""Attempted to divide by zero!"";
}
return (x/y);
}
int main () {
int i = 25;
int j = 0;
float k = 0;
try {
k = division(i, j);
cout << k << endl;
}catch (const char* e) {
cerr << e << endl;
}
return 0;
}
This will not throw an error and halt the program but show a user-defined error and continue the flow of the program. "
"In this Programming with C++ video, we will study about a new topic known as User Defined functions. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
As explained via the video, C++ allows the programmer to define their own function.
A user-defined function groups code to perform a specific task and that group of code is given a name (identifier).
When the function is invoked from any part of the program, it all executes the codes defined in the body of the function.
CODE:
#include <iostream>
using namespace std;
// declaring a function
int add(int a, int b) {
return (a + b);
}
int main() {
int sum;
sum = add(100, 78);
cout << ""100 + 78 = "" << sum << endl;
return 0;
}"
"In this Programming with C++ video, we will study about Templates in C++. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
As seen in the video, templates allow you to define the generic classes and generic functions and thus provides support for generic programming. Templates can make a function or class work for a variety of data types.
Templates can be:-
1. Function Templates -
- Generic functions use the concept of a function template. Generic functions define a set of operations that can be applied to the various types of data.
- The type of the data that the function will operate on depends on the type of the data passed as a parameter.
CODE:
template<class T> T add(T &a,T &b)
{
T result = a+b;
return result;
}
OR
2. Class Templates - similar to the Function Template. When a class uses the concept of Template, then the class is known as generic class.
Ttype is a placeholder name which will be determined when the class is instantiated.
template<class T>
class A
{
public:
T num1 = 5;
T num2 = 6;
void add()
{
std::cout << ""Addition of num1 and num2 : "" << num1+num2<<std::endl;
}
}; "
"In this Programming with C++ video, we will study about Signal Handling in C++. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
Signals are the interrupts which are delivered to a process by the operating system to stop its ongoing task and attend the task for which the interrupt has been generated.
- These signals are defined in <csingnal> header file.
- Signals can also be generated by the operating system on the basis of system or error condition.
void (*signal (int sig, void (*func)(int)))(int);
As seen in the video, we go over some signals in C++ and learn their proper usage-
- SIGABRT (Signal Abort) Abnormal termination of the program, such as a call to abort.
- SIGFPE (Signal floating- point exception) An erroneous arithmetic operation, such as a divide by zero or an operation resulting in overflow.
- SIGILL (Signal Illegal Instruction) It is used for detecting an illegal instruction.
- SIGINT (Signal Interrupt) It is used to receipt an interactive program interrupt signal.
- SIGSEGV (Signal segmentation Violation) An invalid access to storage.
- SIGTERM (Signal Termination) A termination request sent to the program.
- SIGHUP (Signal Hang up) Hang Up, it reports that user's terminal is disconnected. It is used to report the termination of the controlling process."
"In this Programming with C++ video, we will study about Files and Streams which are useful for file handling operations in C++. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
Using the iostream standard library, we use cin and cout methods for reading from input and writing to output respectively.
To read and write from a file we are using the standard C++ library called fstream.
fstream library offers:
- fstream - To create files, write information to files, and read information from files.
- ifstream - To read information from files.
- ofstream - To create files and write information to the files.
Example of Reading and Writing to a file -
int main () {
char input[75];
ofstream os;
os.open(""testout.txt"");
cout <<""Writing to a text file:"" << endl;
cout << ""Please Enter your name: "";
cin.getline(input, 100);
os << input << endl;
cout << ""Please Enter your age: "";
cin >> input;
cin.ignore();
os << input << endl;
os.close();
ifstream is;
string line;
is.open(""testout.txt"");
cout << ""Reading from a text file:"" << endl;
while (getline (is,line))
{
cout << line << endl;
}
is.close();
return 0;
} "
"In this Programming with C++ video, we will study about getline() function in C++. Concepts are explained with plenty of examples and in-depth analysis for your ease of understanding.
As seen in the video, to accept the multiple lines, we use the getline() function. It is a pre-defined <string.h> header file used to accept a line or a string from the input stream until the delimiting character is encountered.
Syntax-
istream& getline( istream& is, string& str, char delim );
/*
is: object of the istream class that defines from where to read the input stream.
str: a string object in which string is stored.
delim: the delimiting character.
*/
In the following example we use we have used the getline() function to accept the character even when the space character is encountered.
CODE:
#include <iostream>
#include<string.h>
using namespace std;
int main()
{
string name; // variable declaration.
std::cout << ""Enter your name :"" << std::endl;
getline(cin,name);
cout<<""\nHello ""<<name;
return 0;} "
"In this video, you will learn about the array data structure, its declaration methods, syntax, implementation, and applications in DSA.
This video will make it quite easy to understand, grasp, and master Arrays. Arrays are required for almost all programming problems and hence it is a must-know data type for any DSA challenge.
- Arrays are used to store multiple values in a single variable, instead of declaring separate variables for each value.
- To declare an array, define the variable type, specify the name of the array followed by square brackets and specify the number of elements it should store.
- You access an array element by referring to the index number inside square brackets []
Syntax:
int arr[] = { 10, 20, 30, 40};
OR
int arr1[10];"
"In this video you will learn about the operations performed on array data structure, such as accessing array value using indexes, removing elements from an array, or adding elements to an array.
Here, we will compare arrays with mathematical matrices to spot the similarity and make it easier for you to grasp the concepts quickly.
An array supports the following operations:
- Traversal
- Insertion
- Deletion
- Search
Other operations include sorting ascending, sorting descending, etc. Let's follow up on these individually.
Traversal:
Visiting every element of an array once is known as traversing the array.
It is important for use cases like:
- Storing all elements – Using scanf()
- Printing all elements – Using printf()
- Updating elements.
An array can easily be traversed using a for loop in C++
Array Size:
If we create an array of length 100 using a[100]. It is possible for a program to use just 60 elements out of these 100. (But we cannot go beyond 100 elements).
Insertion:
An element can be inserted in an array at a specific position. For this operation to succeed, the array must have enough capacity.
Sorting:
Sorting means arranging an array in an orderly fashion (ascending or descending). We have different algorithms to sort arrays. In the upcoming videos, we will explore these in much more detail.
Also, you will learn how to debug possible errors related to arrays. This video will also revise for loops and iteration methods to dynamically determine array size during traversal.
Syntax:
//Initialization Operation
Int Arr1;
Arr1[0] = -10;
// Access an Element
printf(""%d\n"", arr[2]);
//Modify Operation
Arr1 [0] = 15; "
"In this video, you will learn about finding the largest element in an array using an application-based solution.
Given an array arr[] of size n, write a program to find the largest element in it.
Example 1:
Input: arr[] = [100, 50, 4]
Output: 100
Explanation: 100 is the largest element in the given array.
We accept the array elements from the user and run a for loop to check whether the next element in the unsorted array are of higher value compared to the previous elements.
If yes, we switch the element. We continue this algorithm until we reach the end of the array and store the max value in a variable that is outputted at the end.
Psuedo Code:
1. Take an array A and define its values
2. Declare largest as an integer
3. Set 'largest' to 0
4. Loop for each value of A
5. If A[n] > largest, Assign A[n] to largest
6. After the loop finishes, Display largest as the largest element of an array."
"In this video, you will learn about the fundamentals of sorting algorithms and methods to sort an unsorted array of positive/negative elements in ascending order using concepts learned earlier.
Our problem: Given an array of size n, write a program to check if it is sorted in ascending order or not. Equal values are allowed in an array and two consecutive equal values are considered sorted.
Example:
Input : 20 20 45 89 89 90
Output : Yes
Input : 20 20 78 98 99 97
Output : No
- First, check if elements are already sorted or length of the array is greater than 1.
- If it is unsorted, implement a sorting algorithm like Bubble Sort or Selection sort and compare every element with the previous element for each iteration and switch the elements until the array gets sorted.
This video contains a practical hands-on explanation of basic sorting algorithms required for brute force approaches. "
"In this video, the problem of second largest element from an unsorted array will be explained. Some cases discussed are:-
Example 1: Given input array is {12, 35, 1, 10, 34, 1}
Output: The second largest element in array is 34.
The loop will find the largest element present in the array which is smaller than the largest element.
Start traversing the array from array[1],
- If the current element in array say arr[i] is greater than first. Then update first and second as, second = first & first= arr[i]
- If the current element is in between first and second Then update second to store the value of current variable as second = arr[i]
- Return the value stored in the second."
"In this video, you will be learning about a simple approach to reverse the array in-place. In place means, we will not be using some auxiliary space.
Our Problem Statement: Given an array (or string), the task is to reverse the array/string.
Example :
Input : arr[] = {1, 2, 3}
Output : arr[] = {3, 2, 1}
To reverse an array, we will use the concept of swapping. First, the problem strategy will be discussed followed by two approaches coded in C++.
In short, set two pointers at the start and end of the array and start iterating over it till the (length of array)/2. For every idx, swap array[idx] with array[length - idx]
Psuedo Code:
void reverseArray(int[] arr) {
start = 0
end = arr.length - 1
while (start < end) {
// swap arr[start] and arr[end]
int temp = arr[start]
arr[start] = arr[end]
arr[end] = temp
start = start + 1
end = end - 1
}
}"
"In this video, you learn how to make an array consisting of only unique elements by removing all the duplicates.
Our Problem Statement - Given a sorted array, the task is to remove the duplicate elements from the array.
Example -
Input : arr[] = {2, 2, 2, 2, 2}
Output : arr[] = {2}
new size = 1
Note that the array input will be sorted.
After iterating over the array elements, we will check if the previous element is not equal to the next element (because array is sorted) and if it is we will omit that element and store the rest of the elements in a new array that will be outputted to the user.
Pseudo Code:
- Input the number of elements of the array.
- Input the array elements.
- Repeat from i = 1 to n
- if (arr[i] != arr[i+1])
- temp[j++] = arr[i]
- temp[j++] = arr[n-1]
- Repeat from i = 1 to j
- arr[i] = temp[i]
return j."
"In this video, the problem is to rotate the elements of an array towards the left by the specified number of times (D places).
Problem Statement:
Given an array of integers arr[] of size N and an integer, the task is to rotate the array elements to the left by d positions.
Example:
Input: arr[] = {1, 2, 3, 4, 5, 6, 7}, d = 2
Output: 3 4 5 6 7 1 2
First, we will try out a strategy to do it for one index and then extend this knowledge to do it N times. In the left rotation, each element of the array will be shifted to its left by one position and the first element of the array will be added to end of the list.
This process will be followed for a specified number of times.
Steps:
1. Declare and initialize an array.
2. Variable n will denote the number of times an array should be rotated toward its left.
3. The array can be left rotated by shifting its elements to a position prior to them which can be accomplished by looping through the array and perform the operation arr[j] = arr[j+1].
4. The first element of the array will be added to the last of rotated array."
"In this video, the problem is to take all the zeros from an array and place at the rightmost end.
Problem Statement:
Given an array of random numbers, Push all the zero’s of a given array to the end of the array
Example:
Input : arr[] = {1, 2, 0, 4, 3, 0, 5, 0};
Output : arr[] = {1, 2, 4, 3, 5, 0, 0, 0};
Input : arr[] = {1, 2, 0, 0, 0, 3, 6};
Output : arr[] = {1, 2, 3, 6, 0, 0, 0};
First, we discuss strategies and edge cases to handle then we study the approach that is:-
- if the current element is non-zero, place the element at the next available position in the array. After all elements in the array are processed, fill all remaining indices by 0.
Steps:
k = 0
for i in A:
if I != 0:
A[k] = i
k = k + 1
for i in range(k, len(A)):
A[i] = 0
"
"In this video, we will shift our focus from Arrays to strings and learn about methods in C++ for dynamic creation, manipulation, deletion, and other operations performed on the String data type.
Topics covered are (with practical examples):
- char data type
//string s1 = ""Hello"";
- strings from chars
//char ch[] = { 'C', '+', '+'};
- calculating string sizes
// char ary[] = ""Board Infinity"";
// cout << ""Length of String = "" << strlen(ary)<<endl;
- updating string values
- appending/inserting in strings (str1 + str2)
- deletion from strings
- accepting string from console [cin>> or getline()]
- operations such as strcat(), strcpy(), strlen() and strcmp(), begin(), end()."
"In this video we will study more string manipulation methods such as converting to lowercase, uppercase and apply previously learned functions to check if string is an pangram (contains all English letter alphabets) as well as to reverse a given string.
Multiple approaches are discussed in the video for these problems.
Code:
- Reverse String: change the order of a given string so that the last character of the string becomes the first character of the string and so on
Enter a string to be reversed: AMBULANCE
After the reverse of a string: ECNALUBMA
Code:
int n = str.length();
// Swap character
for (int i = 0; i < n / 2; i++)
swap(str[i], str[n - i - 1]);
- Check if Pangram:
A string is a Pangram if the string contains all the English alphabet letters.
Input: str = “We promptly judged antique ivory buckles for the next prize”
Output: Yes
Code:
for (int i = 0; i < str.length(); i++) {
if ('A' <= str[i] && str[i] <= 'Z')
index = str[i] - 'A';
else if ('a' <= str[i] && str[i] <= 'z')
index = str[i] - 'a';
else
continue;
mark[index] = true;
}
for (int i = 0; i <= 25; i++)
if (mark[i] == false)
return (false);
// If all characters were present
return (true);"
"In this video we will study more string methods useful for input validation.
Once we study the following functions, a program is written to display whether string input is valid/invalid based on assumptions.
Throughout the video logical operators such as || and && are also frequently used during conditional validation when more than two or more conditions are to be fulfilled.
1. isalpha(int ch); - Check for alphabet
2. isdigit(int ch); - Check for numbers
3. isalphanum(int ch); - Check for alphanumeric
4. strlen(string s) or string.length() – Calculate String Length"
"In this video we will understand palindromes and write a program to check whether a given string is a palindrome.
Problem Statement:
If the reversed integer is equal to the integer entered by user then, that number is a palindrome if not that number is not a palindrome. We need to check if a string passed is a palindrome.
Example -
“madam” is a palindromic string because it reads the same forwards and backward. Other palindromic strings are “anna,” “civic,” “level,”.
Using reverse() and strlen() this problem is simplified in the video.
Approach used is:
1. First, take a string as input.
2. Compare the first and the last character of the string. If they are equal, then compare the second and the second last character of the string, and so on, until we get to the middle of the string.
3. If the characters do not match at any iteration, then the string is not a palindrome."
"In this video, we are introducing you to string based patterns and algorithm/strategies used to determine whether a given pattern exists in a string (Similar to Regular Expressions).
The most basic case of string searching involves one (often very long) string, sometimes called the haystack, and one (often very short) string, sometimes called the needle. The goal is to find one or more occurrences of the needle within the haystack. For example, one might search for to within:
“Some books are to be tasted, others to be swallowed, and some few to be chewed and digested.”
One might request the first occurrence of ""to"", which is the fourth word; or all occurrences, of which there are 3; or the last, which is the fifth word from the end.
Our video demonstrates multiple cases taken into consideration such as number of time pattern occurs, length of pattern, recurrence, numeric, alphabetic, and symbolic patterns.
In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction."
"In this video, we implement a naïve Pattern Searching algorithm. Primary purpose is to find a pattern or substring from another bigger string.
We explore different methods to implement this algorithm. In the video, Naïve pattern searching is demonstrated which is the simplest method among other pattern searching algorithms. It checks for all characters of the main string to the pattern.
Pseudo Code:
for i := 0 to (lenStr - patLen), do
for j := 0 to patLen, do
if text[i+j] ≠ pattern[j], then
break the loop
done
if j == patLen, then
display the position i, as pattern found"
"In this video, we dive deeper and explore a more advanced and efficient pattern searching algorithm known as Rabin-Karp.
Problem Statement:
Given a text txt[0..n-1] and a pattern pat[0..m-1], write a function search(char pat[], char txt[]) that prints all occurrences of pat[] in txt[]. You may assume that n > m
Example:
Input: txt[] = ""AABAACAADAABAABA""
pat[] = ""AABA""
Output: Pattern found at index 0
Pattern found at index 9
Pattern found at index 12
Initially, we discuss the strategy which is checking the pattern by moving window one by one, but without checking all characters for all cases (finding the hash value).
- When the hash value is matched, then only it tries to check each character.
In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction.
Pseudo Code:
for i := 0 to (lenStr - patLen), do
if patHash = strHash, then
for charIndex := 0 to patLen -1, do
if text[i+charIndex] ≠ pattern[charIndex], then
break the loop
if charIndex = patLen, then
print the location i as pattern found at i position.
if i < (lenStr - patLen), then
strHash := (maxChar*(strHash – text[i]*h)+text[i+patLen]) mod prime, then
if strHash < 0, then
strHash := strHash + prime"
"In this video, we dive deeper and explore another pattern searching algorithm. We demonstrate an example by writing the KMP Algo in which we need to find a substring that is equivalent to our pattern.
Problem Statement:
Given a text txt[0..n-1] and a pattern pat[0..m-1], write a function search(char pat[], char txt[]) that prints all occurrences of pat[] in txt[]. You may assume that n > m.
Example:
Input: txt[] = ""AABAACAADAABAABA""
pat[] = ""AABA""
Output: Pattern found at index 0
Pattern found at index 9
Pattern found at index 12
- The sliding window algorithm would have a window size equal to the length of the pattern.
- We would check substrings of length N starting from the left (0 index) to right.
As explained in the video, prefixes and suffixes are used to decrease time complexity.
Psuedo Code:
while i < n do
{if the characters match}
if pattern[j] = string[i] then do
i ← i + 1
j ← j + 1
{j pointer reached end of pattern}
if j = m then
{matched index}
return i - j
j ← LPS[j - 1]
else if i<n && pattern[j] != string[i] then
{no match}
if j > 0
j ← LPS[j - 1]
else
i ← i + 1
return -1 "
"In this video, we solve the String Rotation problem where the objective is to determine whether two given Strings have equal lengths and consist of the same characters. As discussed in the theory section, check for the second string by rotating the first String around a certain character. In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction.
Problem Statement:
Given a string s1 and a string s2, write a snippet to say whether s2 is a rotation of s1
Example:
Valid example is two strings S1 = ""HELLO"", and S2 = ""LOHEL"".
The coding example can be simplified by the code snippet in a nutshell like so.
Pseudo Code:-
checkRotation(s1,s2)
if s1.length != s2.length
return False
else
temp = s1 + s1
if s2 in temp
return True
else
return False"
"In this video, we explain the concept of Anagrams, that is - both strings contain the same character set, only their order is different.
Problem Statement:
Therefore, in both strings, the frequency of each letter must be the same.
Example:
strings ""silent"" and ""listen"" are anagrams.
Then, we discuss all the possible valid test cases to write the algorithm such as length should match, duplicates are not allowed, and words should be only alphabetic in nature.
A detailed coded example is given at the end of the video with a thorough explanation and tips.
Pseudo Code:
bool checkAnagram(string S1, int m, string S2, int n)
{
if ( m != n )
return False
sort(S1, m)
sort(S2, n)
for ( i = 0 to n-1 )
if ( S1[i] != S2[i] )
return False
return True
}"
"In this video, we will explore the Linked List Data Structure. A linked list is a linear data structure that includes a series of connected nodes. Here, each node stores the data and the address of the next node.
In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction. Followed by an explanation of the need and use cases of linked lists.
Further, we discuss their 3 types:
- singly linked list
- doubly linked list
- circular linked list
Through examples, we talk about how they are different from arrays and their dynamic memory allocation, possible implementations in stack and queues.
PSEUDO CODE: -
struct node
{
int data;
struct node *next;
};
//traversing a linked list
head = one;
while (head != NULL) {
cout << head->value;
head = head->next;
}"
"In this video, we discuss the three critical operations performed on Singly Linked lists. A singly linked list is a type of linked list that is unidirectional, that is, it can be traversed in only one direction from head to the last node. To perform any of these operations, a pointer reference to head is required.
Each node is traversed in a linear fashion and not randomly like arrays. The node->next and null properties are very common when performing these operations. Memory management is to be done manually in C++ while performing operations on the Singly Linked List data structure.
Each of these operations is discussed in detail with practical examples covering all possible cases.
PSUEDO CODE:
-----Traversal - Iterating and Moving through all the elements of the linked list.
void traversal(Node* head) {
Node* temp = head;
while(temp != NULL)
{
cout<<(temp->data);
temp = temp->next;
}
}
-----Insertion - Either at the front or the back of the list. Can also be inserted at a specific position as explained in the video.
Node* insertAtBegin(Node* head, int x)
{
Node* newNode = new Node(x)
if(head == NULL)
return newNode;
else
{
newNode->next = head;
return newNode;
}
}
-----Deletion - Removing elements from either ends or from a specific position
Node deleteNode(Node* head, Node* toBeDeleted)
{
if(head == toBeDeleted)
{
return head.next;
}
Node* temp = head;
while( temp->next != NULL )
{
if(temp->next == toBeDeleted)
{
temp->next = temp->next->next;
return head;
}
temp = temp->next;
}
return head;
}"
"In this video, we go over strategies and brainstorm ways to insert an element at the right position in a linked list so that list automatically appears as sorted.
So, basically finding the proper place such that the element fits perfectly in sorted order is the object. In the video, we have explained the example via which we iterate down the list looking for the place to insert the new node.
In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction. That could be the end of the list or a point just before a larger node than the new node.
PSUEDO CODE:
void insertion_sort(struct Node** head, struct Node* newNode)
if (*head == NULL || (*head)->data >= newNode->data)
{
newNode->next = *head;
*head = newNode;
return;
}
struct Node* current = *head;
while (current->next != NULL && current->next->data < newNode->data)
current = current->next;
newNode->next = current->next;
current->next = newNode;
}"
"In this video, we will discuss both the iterative and recursive ways in which you can solve the problem of Linked List reversal.
In the introduction, we see how we can make use of pointers to reverse the list by changing the links between nodes.
Problem Statement:
We need to reverse the pointers such that the next element now points to the previous element.
Example:
Input: Head of following linked list
1->2->3->4->NULL
Output: Linked list should be changed to,
4->3->2->1->NULL
As explained in the video, for the iterative approach, we need to Create 3 instances: current, next, and previous. Loop the following till current is NOT null:
- Save the next Node of the current element in the next pointer.
- Set the next of the current Node to the previous.
- Swap previous to current.
- Swap the current element to the next.
As explained in the video, for the recursive approach, we need to divide the LinkedList into two parts: head and remainder portion.
- Head points to the first element initially.
- Remaining points to the next element from the head.
We traverse the LinkedList recursively until the second last element. Once we’ve reached the last element set that as the head.
From there we do the following until we reach the start of the LinkedList.
- node.next.next = node;
- node.next = null;
To not lose a track of the original head, we’ll save a copy of the head instance.
PSEUDO CODE:-
Using Iterative Method:
public class MyLinkedList {
public Node head;
public static class Node {
Node next;
Object data;
Node(Object data) {
this.data = data;
next = null;
}
}
}
Using Recursive Method:
public Node recursiveReverse(Node head) {
Node first;
if (head==null || head.next == null)
return head;
first = recursiveReverse(head.next);
head.next.next = head;
head.next = null;
return first;
}"
"In this video, we introduce you to a brand new data structure known as STACK that follows the LIFO rule (Last In First Out).
You can think of the stack data structure as a pile of plates on top of one another.
This means the last element inserted inside the stack is removed first. Then, we provide examples and give an overview of its use cases where Stack can be implemented to reverse a string, store browser history, and in parenthesis validators.
In the video, we also cover all sorts of cases and brainstorm how to approach the problem during the theory part of the introduction.
Next, we understand operations that can be performed on the stack such as (algorithms explain in detail):-
1. Push: Add an element to the top of a stack
2. Pop: Remove an element from the top of a stack
3. IsEmpty: Check if the stack is empty
4. IsFull: Check if the stack is full
5. Peek: Get the value of the top element without removing it"
"In this video, will help you to understand how a stack is implemented using an array as the data structure. and write all the operational functions.
Highlighted by properties, examples, and test cases you will be guided about implementing a stack with all the operations on the stack taking place using an array.
The primary advantage of using an array for implementing a stack is that it is more time-efficient than linked list implementation of the stack, which takes extra time in allocating pointers of nodes whenever there is a change in the size of the stack. All the operations in case of an array-based implementation of a stack are performed in O (1) time.
Operations performed in the video are:
1. Push: This operation adds an item to the stack; it will throw an exception if the stack’s capacity is full.
2. Pop: This removes the last inserted element from a stack that is removed in reverse order of their insertion; it will throw an underflow exception if the given stack is empty.
3. Peek: This operation returns the topmost element of the stack.
4. IsEmpty: Checks if a stack is empty or not. Returns true if the given stack is empty; otherwise, it returns false.
5. IsStackFull: Checks if a stack is full or not. Returns true if the given stack is full; otherwise, it returns false.
PSUEDO CODE:-
void push(int val) {
if(top>=n-1)
cout<<""Stack Overflow""<<endl;
else {
top++;
stack[top]=val;
}
}
void pop() {
if(top<=-1)
cout<<""Stack Underflow""<<endl;
else {
cout<<""The popped element is ""<< stack[top] <<endl;
top--;
}
}
void display() {
if(top>=0) {
cout<<""Stack elements are:"";
for(int i=top; i>=0; i--)
cout<<stack[i]<<"" "";
cout<<endl;
}else
cout<<""Stack is empty"";
}"
"In this video, we will be using Linked List instead of Arrays to implement Stack. While the fundamentals will remain the same as in the previous video, we will now be introducing pointers yet again to traverse, remove and insert data at proper places using Linked Lists.
As we know, a stack is represented using nodes of a linked list. Each node consists of two parts:
- data - The data part of each node contains the assigned value
- next (storing address of next node) - next points to the node containing the next item in the stack
- top - refers to the topmost node in the stack.
Both the push() and pop() operations are carried out at the front/top of the linked list and hence take O(1) time.
One noticeable difference from the previous video is that a stack can also be implemented using arrays but here we are going for the Linked List approach since arrays are of limited size and the size of the stack has to be predetermined whereas in a linked list implementation nodes can be added according to the user's requirements.
PSUEDO CODE: -
void push(int value) {
struct Node *newNode;
newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = value; // assign value to the node
if (top == NULL) {
newNode->next = NULL;
} else {
newNode->next = top; // Make the node as top
}
top = newNode; // top always points to the newly created node
printf(""Node is Inserted\n\n"");
}
int pop() {
if (top == NULL) {
printf(""\nStack Underflow\n"");
} else {
struct Node *temp = top;
int temp_data = top->data;
top = top->next;
free(temp);
return temp_data;
}
}
void display() {
// Display the elements of the stack
if (top == NULL) {
printf(""\nStack Underflow\n"");
} else {
printf(""The stack is \n"");
struct Node *temp = top;
while (temp->next != NULL) {
printf(""%d--->"", temp->data);
temp = temp->next;
}
printf(""%d--->NULL\n\n"", temp->data);
}
}"
"In this video, we do a deep dive into use-cases, applications, and programming problems where Stack data structure is very useful and efficient. Video highlights the top 3 uses of the stack such as:
1. Evaluation of Arithmetic Expressions
2. Backtracking
3. Delimiter Checking
4. Reverse a Data
5. Processing Function Calls
In the video, the approach to check for valid parenthesis is to first
- Declare an empty stack and then traverse the string and whenever an opening bracket is encountered, insert it into the stack.
- On the other hand, when a closing bracket is encountered, check if the last bracket in the stack matches the current closing bracket.
- If not, then return false otherwise remove the last bracket from the stack.
- After traversing the string, if the stack is empty return true otherwise return false.
In four such steps, the problem can be solved. Similar pattern problems can be tackled with the same approach.
PSUEDO CODE:
bool isBalancedParentheses(string str) {
int n = str.size();
stack<char> openingBrackets;
for (int i = 0; i < n; i++) {
if (str[i] == '(' || str[i] == '{' || str[i] == '[') {
openingBrackets.push(str[i]);
} else {
if (openingBrackets.empty()) {
return false;
}
if (str[i] == ')') {
char last = openingBrackets.top();
openingBrackets.pop();
if (last != '(') {
return false;
}
}
if (str[i] == '}') {
char last = openingBrackets.top();
openingBrackets.pop();
if (last != '{')
return false;
}
}
if (str[i] == ']') {
char last = openingBrackets.top();
openingBrackets.pop();
if (last != '[') {
return false;
}
}
}
}
return openingBrackets.empty();
}
"
"In this video, you will be introduced to yet another commonly used data structure known as a queue. A few highlights as discussed are:
The queue data structure follows the FIFO (First In First Out) principle where elements that are added first will be removed first.
After this, the video will demonstrate the types of operations associated with the Queue data structure used to manipulate the order of the elements and the elements themselves.
Operations highlighted are:
1. enque(): inserts an element at the back of the queue
2. deque (): removes an element from the front of the queue
3. getFront(): returns the first element of the queue
4. getBack(): returns the last element of the queue
5. size(): returns the number of elements in the queue
6. empty(): returns true if the queue is empty"
"In this video, we will be doing a deep dive and understanding the concepts and operations associated with Queues in a better manner using the C++ STL Library for a more thorough understanding.
Some applications highlighted in the video are:
1. Managing requests on a single shared resource such as CPU scheduling and disk scheduling
2. Handling hardware or real-time systems interrupts
3. Handling website traffic
4. Routers and switches in networking
5. Maintaining the playlist in media players"
"Now that we have covered all the theories related to Queues, in this video, we will be writing the C++ code to implement the discussed operations of the Queue data structure using arrays.
We revise what the front and back values signify and dive right into the implementation. Each operation is successfully written in C++ with the help of functions.
Function Insert():
Inserts an element into the queue. If the rear is equal to n-1, then the queue is full, and overflow is displayed. If the front is -1, it is incremented by 1. The rear is incremented by 1 and the element is inserted in the index of the rear.
Code for Insertion:
void Insert() {
int val;
if (rear == n - 1)
cout<<""Queue Overflow""<<endl;
else {
if (front == - 1)
front = 0;
cout<<""Insert the element in queue : ""<<endl;
cin>>val;
rear++;
queue[rear] = val;
}
}
Function Delete():
If there are no elements in the queue then it is underflow condition. Otherwise, the element at the front is displayed and the front is incremented by one.
Code for Deletion
void Delete() {
if (front == - 1 || front > rear) {
cout<<""Queue Underflow "";
return ;
}
else {
cout<<""Element deleted from queue is : ""<< queue[front] <<endl;
front++;;
}
}
Function display(), if front is -1 then queue is empty. Otherwise, all the queue elements are displayed using a for loop
Code to display the queue:
void Display() {
if (front == - 1)
cout<<""Queue is empty""<<endl;
else {
cout<<""Queue elements are : "";
for (int i = front; i <= rear; i++)
cout<<queue[i]<<"" "";
cout<<endl;
}
}
"
"In this video, we will be writing the C++ code to implement the discussed operations of the Queue data structure using Linked Lists. Each operation is successfully written in C++ with the help of functions.
The primary difference from the array implementation is that Linked list Nodes are created dynamically and when required.
Function Insert():
- Inserts an element into the queue. If the rear is NULL, then the queue is empty and a single element is inserted. Otherwise, a node is inserted after the rear with the required element and then that node is set to the rear.
CODE IMPLEMENTATION:
void Insert() {
int val;
cout<<""Insert the element in queue : ""<<endl;
cin>>val;
if (rear == NULL) {
rear = (struct node *)malloc(sizeof(struct node));
rear->next = NULL;
rear->data = val;
front = rear;
} else {
temp=(struct node *)malloc(sizeof(struct node));
rear->next = temp;
temp->data = val;
temp->next = NULL;
rear = temp;
}
}
Function Delete()
- If there are no elements in the queue then it is underflow condition. If there is only one element in the queue that is deleted and the front and rear are set to NULL. Otherwise, the element at the front is deleted, and the front points to the next element.
CODE IMPLEMENTATION:
void Delete() {
temp = front;
if (front == NULL) {
cout<<""Underflow""<<endl;
return;
} else
if (temp->next != NULL) {
temp = temp->next;
cout<<""Element deleted from queue is : ""<<front->data<<endl;
free(front);
front = temp
} else{
cout<<""Element deleted from queue is : ""<<front->data<<endl;
free(front);
front = NULL;
rear = NULL;
}
}"
"In this video, we start with a new topic – that of the Tree data structure. To clearly understand Trees, the video goes through the proper terminology used such as root node, children node, leaf nodes, height, levels, and depth of a tree, etc., and gives a basic overview of all the properties of all the nodes involved in the tree data structure.
In a nutshell, it can be covered by the following points: -
1. A tree is a nonlinear hierarchical data structure that consists of nodes connected by edges.
2. The first node from where the tree originates is called as a root node.
3. The connecting link between any two nodes is called an edge.
4. The node which has a branch from it to any other node is called a parent node.
5. The node which is a descendant of some node is called a child node.
6. Nodes that belong to the same parent are called siblings.
7. The node which does not have any child is called a leaf node.
8. In a tree, each step from top to bottom is called as the level of a tree.
9. The total edges that lie on the longest path from any leaf node to a particular node is called the height of that node.
10. The total edges from the root node to a particular node are called the depth of that node.
Finally, after understanding the basics, in the video, we demonstrate the use-cases/applications of trees where they make it easy to solve problems with a repeating pattern such as: -
1. Binary Search Trees (BSTs) quickly check whether an element is present in a set or not.
2. Heap is a kind of tree that is used for heap sort.
3. Tries are used in modern routers to store routing information.
4. Most popular databases use B-Trees and T-Trees, which are variants of the tree structure.
5. Compilers use a syntax tree to validate the syntax of every program you write."
"In this video, we will be introducing you to Binary Trees data structure:
It has at most 2 children nodes. Since each element in a binary tree can have only 2 children, we name them the left and right child. It includes the following parts -
1. Data
2. Pointer to the left child
3. Pointer to the right child
Via the video, we also understand 3 traversal methods that are very important from an interview point of view.
1. PreOrder Traversal: root – left child – right child. It means that the root node is traversed first then its left child and finally the right child.
2. InOrder Traversal: left child – root – right child. It means that the left child is traversed first then its root node and finally the right child.
3. PostOrder Traversal: left child – right child – root. It means that the left child is traversed first then the right child and finally its root node.
CODE -
#include <stdlib.h>
#include <iostream>
using namespace std;
struct node {
int data;
struct node *left;
struct node *right;
};
// New node creation
struct node *newNode(int data) {
struct node *node = (struct node *)malloc(sizeof(struct node));
node->data = data;
node->left = NULL;
node->right = NULL;
return (node);
}
// Traverse Preorder
void traversePreOrder(struct node *temp) {
if (temp != NULL) {
cout << "" "" << temp->data;
traversePreOrder(temp->left);
traversePreOrder(temp->right);
}
}
// Traverse Inorder
void traverseInOrder(struct node *temp) {
if (temp != NULL) {
traverseInOrder(temp->left);
cout << "" "" << temp->data;
traverseInOrder(temp->right);
}
}
// Traverse Postorder
void traversePostOrder(struct node *temp) {
if (temp != NULL) {
traversePostOrder(temp->left);
traversePostOrder(temp->right);
cout << "" "" << temp->data;
}
}
int main() {
struct node *root = newNode(1);
root->left = newNode(2);
root->right = newNode(3);
root->left->left = newNode(4);
cout << ""preorder traversal: "";
traversePreOrder(root);
cout << ""\nInorder traversal: "";
traverseInOrder(root);
cout << ""\nPostorder traversal: "";
traversePostOrder(root);
}
Applications, as demonstrated in the video, are:-
- For easy and quick access to data
- In router algorithms
- To construct or build a heap
- Syntax tree
"
"In this video, we see a Recursive way to implement Inorder Traversal in Binary Trees with a simple and lucid example with code demonstration.
We are making use of queues to make this possible. Recursively, until we encounter that node is none, in C++ we make a call to the Inorder() function and pass the nodes down the tree hierarchy.
The Algorithm from the video can be broken down into the following 3 steps:
Until all nodes are traversed:
- Step 1 − Recursively traverse the left subtree.
- Step 2 − Visit the root node.
- Step 3 − Recursively traverse the right subtree.
A detailed coded example is given at the end of the video with a thorough explanation and tips.
CODE:
void inorderTraversal(struct Node* node) {
if (node == NULL)
return;
inorderTraversal(node->left);
cout << node->data << ""->"";
inorderTraversal(node->right);
}
"
"In this video, we see a Recursive way to implement Preorder Traversal in Binary Trees with a simple and lucid example with code demonstration.
We are making use of queues to make this possible. Recursively, until we encounter that node is none, in C++ we make a call to the Preorder() function and pass the nodes down the tree hierarchy.
The Algorithm from the video can be broken down into the following 3 steps: -
Until all nodes are traversed −
- Step 1 − Visit the root node.
- Step 2 − Recursively traverse the left subtree.
- Step 3 − Recursively traverse the right subtree.
A detailed coded example is given at the end of the video with a thorough explanation and tips.
PSUEDO CODE:
void preorderTraversal(struct Node* node) {
if (node == NULL)
return;
cout << node->data << ""->"";
preorderTraversal(node->left);
preorderTraversal(node->right);
}
"
"In this video, we see a Recursive way to implement Postorder Traversal in Binary Trees with a simple and lucid example with code demonstration.
We are making use of queues to make this possible. Recursively, until we encounter that node is none, in C++ we make a call to the Postorder() function and pass the nodes down the tree hierarchy.
The Algorithm from the video can be broken down into the following 3 steps:
Until all nodes are traversed:
- Step 1 − Recursively traverse the left subtree.
- Step 2 − Recursively traverse the right subtree.
- Step 3 − Visit the root node.
A detailed coded example is given at the end of the video with a thorough explanation and tips.
CODE: -
// Postorder traversal
void postorderTraversal(struct Node* node) {
if (node == NULL)
return;
postorderTraversal(node->left);
postorderTraversal(node->right);
cout << node->data << ""->"";
}"
"In this video, we will be diving deeper and studying some advanced problems related to Trees to reinforce concepts we have understood from the previous sessions.
Level Order Traversal of Binary Tree
Given a binary tree, the objective is to print its nodes level by level, i.e., print all nodes of level 1 first, followed by nodes of level 2, and so on… Print nodes for any level from left to right.
This is also known as Breadth First search as the search tree is broadened as much as possible on each depth before going to the next depth.
bool printLevel(Node* root, int level)
{
if (root == nullptr) {
return false;
}
if (level == 1)
{
cout << root->key << "" "";
// return true if at least one node is present at a given level
return true;
}
bool left = printLevel(root->left, level - 1);
bool right = printLevel(root->right, level - 1);
return left || right;
}
void levelOrderTraversal(Node* root)
{
// start from level 1 — till the height of the tree
int level = 1;
// run till printLevel() returns false
while (printLevel(root, level)) {
level++;
}
}
Height of Binary Tree:
The objective is to find the maximum or the largest number of edges from a leaf node to the root node or root node to the leaf node. In the video, we have used a recursive approach as it is efficient and easy to understand.
int tree_height(Node* root) {
if (root == NULL)
return 0;
else {
// Find the height of left, right subtrees
left_height = tree_height(root->left);
right_height = tree_height(root->right);
// Find max(subtree_height) + 1 to get the height of the tree
return max(left_height, right_height) + 1;
}
Maximum Element from Binary Tree
int findMaxNode(Node* root) {
if (root == NULL)
return -100;
int maxVal = root->data;
int leftMaxVal = findMaxNode(root->left);
int rightMaxVal = findMaxNode(root->right);
if (leftMaxVal > maxVal)
maxVal = leftMaxVal;
if (rightMaxVal > maxVal)
maxVal = rightMaxVal;
return maxVal;
}
Print All Nodes at K distance from Binary Tree:
Using a Recursive approach, it can be done as demonstrated in the video.
public void findKDistanceFromNode(
TreeNode node,int dist, List<Integer> result,TreeNode blocker
) {
if (dist < 0 || node == null ||(blocker != null && node == blocker)) {
return;
}
if (dist == 0) {
result.add(node.val);
}
findKDistanceFromNode(node.left, dist - 1, result, blocker);
findKDistanceFromNode(node.right, dist - 1, result, blocker);
}"
"In previous videos, we have studied this problem but we implemented the recursive solution. But instead, in this video, we will be trying out the iterative approach to print any Binary Tree in the Inorder fashion.
Since we are doing it iteratively, the stack is the preferred data structure to aid in the solution as seen in the video.
Important steps to remember are:-
- If the current node is NULL and the stack is not empty:
a. Remove and print the last item from the stack.
b. Set the current node to be the node to the right of the removed node.
c. Repeat the second step of the algorithm.
- If the current node is NULL and the stack is empty, then the algorithm has finished.
PSUEDO CODE:
void inOrderTraversal(Node *root)
{
stack<Node*> stack;
Node *curr = root;
while (!stack.empty() || curr != NULL)
{
if (curr != NULL)
{
stack.push(curr);
curr = curr->left;
}
else
{
curr = stack.top();
stack.pop(); // Pop the node on top of stack.
cout << curr->data << "" ""; // Print it.
curr = curr->right;
}
}
}"
"In previous videos, we have studied this problem but we implemented the recursive solution. But instead, in this video, we will be trying out the iterative approach to print any Binary Tree in the Preorder fashion.
Since we are doing it iteratively, the stack is the preferred data structure to aid in the solution as seen in the video.
Important steps to remember are:
- Pop and print an item from the stack.
- Push the right child of a popped item to stack.
- Push the left child of a popped item to stack.
PSUEDO CODE:-
void preorderIt(Node* root)
{
if (root == nullptr)
return;
stack<Node*> s;
s.push(root);
while (!s.empty())
{
Node* curr = s.top();
s.pop();
cout << curr->data << "" "";
if (curr->right) {
s.push(curr->right);
}
if (curr->left) {
s.push(curr->left);
}
}
}"
"Previously, we learned about trees. From this video onwards we are going to be studying an extension of the Tree Data structure known as the Binary Search Tree (BST).
It is a data structure that quickly allows us to maintain a sorted list of numbers.
- It is called a binary tree because each tree node has a maximum of two children.
- It is called a search tree because it can be used to search for the presence of a number in logN time.
In the video, we also see that the properties of a binary search tree are different from those a standard Tree in the following ways:-
1. All nodes of the left subtree are less than the root node
2. All nodes of the right subtree are more than the root node
3. Both subtrees of each node are also BSTs i.e. they have the above two properties
Since it is optimized for the search operation, from the video, we say that
- If the value is below the root, only search in the left subtree AND
- if the value is above the root, only search in the right subtree.
PSEUDO CODE:
If root == NULL
return NULL;
If number == root->data
return root->data;
If number < root->data
return search(root->left)
If number > root->data
return search(root->right)"
"In this video, we will study 4 problems and work out their C++ implementation.
INSERTION:
Insertion should take place in a BST such that the property of the BST should be maintained. All left children nodes should be lesser and all right children nodes should be greater than the parent node.
PSUEDO CODE:
public static TreeNode insertionRecursive(TreeNode root, int value) {
if (root == null)
return new TreeNode(value);
if (value < (int) root.data) {
root.left = insertionRecursive(root.left, value);
} else if (value > (int) root.data) {
root.right = insertionRecursive(root.right, value);
}
return root;
}
DELETION:
For deletion operations, we must consider a few cases and write the logic for them individually.
- If no children - Just delete it.
- If a single child - Copy that child to the node.
- If two children - Determine the next highest element (in order successor) in the right subtree. Replace the node to be removed with the inorder successor. Delete the inorder successor duplicate.
PSUEDO CODE:
public static TreeNode deleteRecursively(TreeNode root, int value) {
if (root == null)
return root;
if (value < (int) root.data) {
root.left = deleteRecursively(root.left, value);
} else if (value > (int) root.data) {
root.right = deleteRecursively(root.right, value);
} else {
if (root.left == null) {
return root.right;
} else if (root.right == null)
return root.left;
root.data = inOrderSuccessor(root.right);
root.right = deleteRecursively(root.right, (int) root.data);
}
return root;
}
FLOOR OF BST:
It is the node with the greatest data lesser than or equal to the key value.
PSUEDO CODE:
int Floor(node *root, int input)
{
if (root == NULL)
return -1;
if (root->key == input)
return root->key;
if (root->key > input)
return Floor(root->left, input);
else{
int floor = Floor(root->right, input);
return (floor<=input && floor!=-1)? floor : root->key;
}
}
CEILING OF BST:
It is the node with the smallest data larger than or equal to the key value.
PSUEDO CODE:
int Ceil(node* root, int input)
{
if (root == NULL)
return -1;
if (root->key == input)
return root->key;
if (root->key < input)
return Ceil(root->right, input);
int ceil = Ceil(root->left, input);
return (ceil >= input) ? ceil : root->key;
} "
"In this video, we will continue with the topic of BSTs and reinforce our learnings by studying the implementation of Self Balancing BSTs in C++.
Self-balancing binary search trees are an important type of data structure used in the underlying implementation of many containers, libraries, and algorithms. Usually, they achieve self-balancing through a certain sequence of rotations when there is a change in the tree while maintaining all BST properties.
During the introduction, it is explained that they are simply height-balanced binary search trees that automatically keep the height as small as possible when insertion and deletion operations are performed on the tree.
There are three major variants of self-balancing BSTs that we will consider using. They are AVL trees, red-black trees, and splay trees.
As seen from the video this is only possible by performing 4 types of rotations:-
- Left rotation
- Right Rotation
- Left-Right rotation
- Right-Left rotation
PSUEDO CODE FOR LEFT ROATATE OPERATION:
LEFT-ROTATE (T, x)
y = x.right
x.right = y.left
if y.left != nil
y.left.parent = x
y.parent = x.parent
if x.parent == nil
T.root = y
elseif x == x.parent.left
x.parent.left = y
else x.parent.right = y
y.left = x
x.parent = y
PSUEDO CODE FOR RIGHT ROTATE OPERATION:
RIGHT-ROTATE (T, x)
y = x.left
x.left = y.right
if y.right != nil
y.right.parent = x
y.parent = x.parent
if x.parent == nil
T.root = y
elseif x == x.parent.right
x.parent.right = y
else x.parent.left = y
y.right = x
x.parent = y"
"In this video, we continue from the point where we left off and study more issues that crop up with hashing and methods to handle them.
Topics explained in the videos are (with examples):-
Direct Address Table:
It is a data structure used for mapping records to their corresponding keys using arrays. In direct address tables, records are placed using their key values directly as indexes. They facilitate fast searching, insertion, and deletion operations.
Collision Handling:
Since a hash function gets us a small number for a big key, there is the possibility that two keys result in the same value. The situation where a newly inserted key maps to an already occupied slot in the hash table is called collision and must be handled using some collision handling technique.
Two methods discussed are Chaining and Open Addressing."
"In the previous videos, we have seen the side effects of hashing known as a collision. Now we will address this issue in this video by learning about Chaining which is a technique used for avoiding collisions in Hash Tables.
After a theoretical explanation, the C++ solution is implemented for a thorough understanding.
In the chaining approach, the hash table is an array of linked lists. All key-value pairs mapping to the same index will be stored in the linked list of that index.
As we have understood from the previous videos
hashIndex = key % noOfBuckets
PSUEDO CODE:
class Hash{
int BUCKET;
list < int >*table;
public:
Hash (int V);
void insertItem (int x);
void deleteItem (int key);
int hashFunction (int x){
return (x % BUCKET);
}
void displayHash ();
};
Hash::Hash (int b){
this->BUCKET = b;
table = new list < int >[BUCKET];
}
void Hash::insertItem (int key){
int index = hashFunction (key);
table[index].push_back (key);
}
void Hash::deleteItem (int key){
int index = hashFunction (key);
list < int >::iterator i;
for (i = table[index].begin (); i != table[index].end (); i++){
if (*i == key)
break;
}
if (i != table[index].end ())
table[index].erase (i);
}
void Hash::displayHash (){
for (int i = 0; i < BUCKET; i++){
cout << i;
for (auto x:table[i])
cout << "" --> "" << x;
cout << endl;
}
}"
"In this video, we will get a brief overview of 3 topics:-
Open Addressing
Like separate chaining, open addressing is a method for handling collisions. In Open Addressing, all elements are stored in the hash table itself. So at any point, the size of the table must be greater than or equal to the total number of keys.
Operations involved are-
1. Insert(h)
2. Search(h)
3. Delete(h)
Double Hashing:
It is a technique to resolve collisions in Open Addressed Hash Tables. As we see in the video, this can be done by applying a second hash function to the o key when a collision occurs.
Implementation:
Delete Operation-
Declare Function Remove(int k)
Declare hash_val, init of the integer datatype.
initialize hash_val = HashFunc(k)
initialize init = -1
while (hash_val != init and (ht[hash_val]
== DelNode::getNode() or ht[hash_val]
!= NULL and ht[hash_val]->k!= k))
if (init == -1)
init = hash_val
hash_val = HashFunc(hash_val + 1)
if (hash_val != init and ht[hash_val] != NULL)
delete ht[hash_val]
ht[hash_val] = DelNode::getNode()
Search Operation-
Declare Function SearchKey(int k)
Declare hash_val, init of the integer datatype.
initialize hash_val = HashFunc(k)
intialize init = -1
while (hash_val != init and (ht[hash_val]
== DelNode::getNode() or ht[hash_val]
!= NULL and ht[hash_val]->k!= k
if (init == -1)
init = hash_val
hash_val = HashFunc(hash_val + 1)
if (ht[hash_val] == NULL or hash_val == init)
return -1
else
return ht[hash_val]->v
"
"In this video, we will be studying 2 separate subarray based problems with an almost similar solution pattern. Firstly, we consider all the possible cases, brainstorm over the approach to the problem and then attempt to write an efficient C++ solution.
A subarray is a contiguous part of the array. An array that is inside another array
Problem Statement:
Given an array of positive and negative numbers, find if there is a subarray (of size at least one) with 0 sum.
Example:
Input: {4, 2, -3, 1, 6}
Output: true
Explanation: There is a subarray with zero sum from index 1 to 3.
As shown in the video, we are using the following approach:
- Traverse the array from start to end.
- From every index start another loop from i to the end of the array to get all subarrays starting from i, and keep a variable currentSum to calculate the sum of every subarray.
- For every index in inner loop update currentSum = currentSum + arr[j]
- If the currentSum is equal to the given sum then print the subarray.
Code:
bool hasZeroSumSubarray(int nums[], int n)
{
unordered_set<int> set;
set.insert(0);
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += nums[i];
if (set.find(sum) != set.end())
return true;
else
set.insert(sum);
}
return false;
}
Subarray with Given Sum
Problem Statement:
Given an array arr[] of non-negative integers and an integer sum, find a continuous subarray that adds to a given sum.
Note: There may be more than one subarray with sum as the given sum, print the first such subarray.
Examples:
Input: arr[] = {1, 4, 20, 3, 10, 5}, sum = 33
Output: Sum found between indexes 2 and 4
Explanation: Sum of elements between indices 2 and 4 is 20 + 3 + 10 = 33
Code:
bool find_Subarray(int arr[], int N, int required) {
int sum = 0;
for (int i = 0; i < N; i++) {
sum = arr[i];
for (int j = i + 1; j < N + 1; j++) {
if (sum == required) {
// found subarray with given sum
return true;
} else if (sum > required) {
break;
}
sum = sum + arr[j];
}
}
return false;
}"
"In this video, we will be studying 2 separate subarray based problems with an almost similar solution pattern. We shall be making use of the Hashing Techniques that we studied in the previous videos to write an efficient C++ solution.
A subarray is a contiguous part of the array. An array that is inside another array
Longest subarray with given sum
Problem Statement:
Given an array arr[] of size n containing integers. The problem is to find the length of the longest sub-array having sum equal to the given value k.
Example:
Input: arr[] = { 10, 5, 2, 7, 1, 9 }, k = 15
Output: 4
Explanation: The sub-array is {5, 2, 7, 1}.
Our approach as shown in the video will be to consider the sum of all the sub-arrays and return the length of the longest sub-array having the sum ‘k’.
CODE -
int lenOfLongSubarr(int arr[], int n, int k)
{
unordered_map<int, int> um;
int sum = 0, maxLen = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
if (sum == k)
maxLen = i + 1;
if (um.find(sum) == um.end())
um[sum] = i;
if (um.find(sum - k) != um.end()) {
if (maxLen < (i - um[sum - k]))
maxLen = i - um[sum - k];
}
}
return maxLen;
}
Longest Subarray with an equal number of 0s and 1s
Problem Statement: Given an array containing only 0s and 1s, find the largest subarray which contains an equal no of 0s and 1s. The expected time complexity is O(n).
Example:
Input: arr[] = {1, 0, 1, 1, 1, 0, 0}
Output: 1 to 6
(Starting and Ending indexes)
CODE -
Run a loop from i=0 to n-1
if (sumleft[i] == 0)
{
maxsize = i+1;
startindex = 0;
}
if (hash[sumleft[i]-min] == -1)
hash[sumleft[i]-min] = i;
else
{
if ((i - hash[sumleft[i]-min]) > maxsize)
{
maxsize = i - hash[sumleft[i]-min];
startindex = hash[sumleft[i]-min] + 1;
}
}
return maxsize"
"In this video, we will be studying 2 separate problems with an almost similar solution pattern.
We shall be making use of the Hashing Techniques as well as the basic array properties that we studied in the previous videos to write an efficient C++ solution.
Count Distinct Elements
Problem Statement:
Given an unsorted array arr[] of length N, The task is to count all distinct elements in arr[].
Examples:
Input : arr[] = {10, 20, 20, 10, 30, 10}
Output : 3
Explanation: There are three distinct elements 10, 20, and 30.
To tackle the problem, as shown in the video we will be using the following approach:
- Create a count variable and run two loops, one with counter i from 0 to N-1 to traverse arr[] and the second with counter j from 0 to i-1 to check if ith element has appeared before. If yes, increment the count.
Code:
int arr[] = {10, 30, 40, 20, 10, 20, 50, 10};
int n = sizeof(arr)/sizeof(arr[0]);
unordered_map <int, int>mp;
int count_dis=0;
for(int i=0; i<n; i++)
mp[arr[i]]++;
cout<<mp.size();
Count Frequency of Elements in Arrays:
Problem Statement:
Given an array which may contain duplicates, print all elements and their frequencies.
Examples:
Input : arr[] = {10, 20, 20, 10, 10, 20, 5, 20}
Output : 10 3
20 4
5 1
Code:
int frequency(int arr[], int size){
bool check[size];
for(int i=0;i<size;i++){
check[i] = 0;
}
for(int i=0; i<size; i++){
if(check[i]== 1){
continue;
}
int count = 1;
for(int j = i+1; j<size; j++){
if (arr[i] == arr[j]){
check[j] = 1;
count++;
}
}
cout<<""frequency of ""<<arr[i]<<"" is: "" << count << endl;
}
}"
"In this video, we begin with a new data structure known as Binary Heap. Firstly, we understand the terminology involved during problem-solving as well as the properties that are discussed in the video.
- It’s a complete tree
i.e. All levels are completely filled except possibly the last level and the last level has all keys as left as possible. This property of Binary Heap makes them suitable to be stored in an array.
- A Binary Heap is either Min Heap or Max Heap.
In a Min Binary Heap, the key at the root must be minimum among all keys present in Binary Heap. The same property must be recursively true for all nodes in Binary Tree. Max Binary Heap is similar to MinHeap.
Things to Remember:
- Arr[(i-1)/2] - Returns the parent node
- Arr[(2*i)+1] - Returns the left child node
- Arr[(2*i)+2] - Returns the right child node
class BHeap {
private:
vector <int> heap;
int l(int parent);
int r(int parent);
int par(int child);
void heapifyup(int index);
void heapifydown(int index);
public:
BHeap() {}
void Insert(int element);
void DeleteMin();
int ExtractMin();
void showHeap();
int Size();
};"
"In this video, we will continue with Binary Heap and dive deeper by understanding implementation details for 2 Operations in C++(Heapify and ExtractMax/ExtractMin).
Problem Statement:
Given an array of N elements. The task is to build a Binary Heap from the given array. The heap can be Min Heap or Max Heap
For Heapify operations, there are two possible strategies discussed in the video.
1. Build the heap from an array of n items using two methods. In the first method, we successively perform the insert operation on the heap. In n insert operations, we can build the heap from the array. Since each insert operation takes O(log n) time and there are n such operations, the complexity of this method is O(nlog n).
2. The internal node of the tree is max-heapified one at a time. If there are n items in the array, we start from the item at n/2 position and heapify it and we move to next item and do the same all the way down to the first item in the array.
void maxHeapify(int i){
int left = BinaryHeap::leftChild(i);
int right = BinaryHeap::rightChild(i);
int largest = i;
if (left <= size && heap[left] > heap[largest]) {
largest = left;
}
if (right <= size && heap[right] > heap[largest]) {
largest = right;
}
if (largest != i) {
int temp = heap[i];
heap[i] = heap[largest];
heap[largest] = temp;
maxHeapify(largest);
}
}
Extract operation can be done as demonstrated in the video in a much simpler manner.
Consider, extractMax():
int extractMax() {
int maxItem = heap[0];
heap[0] = heap[size - 1];
size = size - 1;
maxHeapify(0);
return maxItem;
}"
"In this video, we will cover some advanced topics like Decreasing a Key and Deleting and Building a Binary Heap.
In the previous video, we learnt about the Heapify operation. Building a binary heap is more or less the same.
Decreasing a Key:
To decrease the value of a certain key, we’ll use the map to locate its index. After we locate its index, we’ll change its value, and start moving it up the tree if needed.
1. Locate the index of the given key using the map.
2. Remove the old value of the key from the map, decrease it, and store the new value inside the map.
3. Perform multiple steps starting from the index of the key. In each step, we compare the value of the key with the value of its parent. If the value of the key is smaller than the value of its parent, we swap their values
4. Complexity is O(log N) where is the number of keys inside the heap.
Deletion:
Since deleting an element at any intermediary position in the heap can be costly, so we can simply replace the element to be deleted by the last element and delete the last element of the Heap.
1. Replace the root or element to be deleted by the last element.
2. Delete the last element from the Heap.
3. Heapify the last node placed at the position of root.
As seen from the code in the video -
void deleteRoot(int arr[], int& n)
{
int lastElement = arr[n - 1];
arr[0] = lastElement;
n = n - 1;
heapify(arr, n, 0);
}"
"This is the introductory lecture of our Algorithms Module, after learning about Data Structures we will learn about Algorithm Analysis.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Characteristics of an Algorithm
Not all procedures can be called an algorithm. An algorithm should have the following characteristics −
- Unambiguous − The algorithm should be unambiguous. Each of its steps (or phases), and their inputs/outputs should be clear and must lead to only one meaning.
- Input − An algorithm should have 0 or more well-defined inputs.
- Output − An algorithm should have 1 or more well-defined outputs and should match the desired output.
- Finiteness − Algorithms must terminate after a finite number of steps.
- Feasibility − This should be feasible with the available resources.
- Independent − An algorithm should have step-by-step directions, which should be independent of any programming code.
Example
Let's try to learn algorithm writing by using an example.
Problem − Design an algorithm to add two numbers and display the result.
Step 1 − START
Step 2 − declare three integers a, b & c
Step 3 − define values of a & b
Step 4 − add values of a & b
Step 5 − store output of step 4 to c
Step 6 − print c
Step 7 − STOP
Algorithms tell the programmers how to code the program. Alternatively, the algorithm can be written as −
Step 1 − START ADD
Step 2 − get values of a & b
Step 3 − c ← a + b
Step 4 − display c
Step 5 − STOP
Algorithm Analysis:
The efficiency of an algorithm can be analyzed at two different stages, before implementation, and after implementation. They are the following −
- A Priori Analysis
- A Posterior Analysis
Algorithm Complexity:
Suppose X is an algorithm and n is the size of input data, the time and space used by the algorithm X are the two main factors, which decide the efficiency of X.
- Time Factor − Time is measured by counting the number of key operations such as comparisons in the sorting algorithm.
- Space Factor − Space is measured by counting the maximum memory space required by the algorithm.
The complexity of an algorithm f(n) gives the running time and/or the storage space required by the algorithm in terms of n as the size of input data. In the lectures ahead we will learn bout the various Algorithms and their implementation.
"
"In this lecture of our Algorithm module, we will learn about Linear & Binary Search.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Linear Search:
A linear search is also known as a sequential search that simply scans each element at a time. Suppose we want to search for an element in an array or list; we simply calculate its length and do not jump at any item.
Below is the code syntax for the linear search.
// Linear Search in C++
#include <iostream>
using namespace std;
int search(int array[], int n, int x)
{
// Going through array sequencially
for (int i = 0; i < n; i++)
if (array[i] == x)
return i;
return -1;
}
Binary Search:
A binary search is a search in which the middle element is calculated to check whether it is smaller or larger than the element which is to be searched. The main advantage of using binary search is that it does not scan each element in the list. Instead of scanning each element, it performs the search to half of the list. So, the binary search takes less time to search for an element as compared to a linear search.
Below is the code syntax for the binary search.
#include <iostream>
using namespace std;
int binarySearch(int array[], int x, int low, int high)
{
// Repeat until the pointers low and high meet each
// other
while (low <= high) {
int mid = low + (high - low) / 2;
if (array[mid] == x)
return mid;
if (array[mid] < x)
low = mid + 1;
else
high = mid - 1;
}
return -1;
}
"
"In this lecture of our Algorithm module, we will learn about Bubble Sort.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in the wrong order. This algorithm is not suitable for large data sets as its average and worst-case time complexity is quite high.
How does Bubble Sort Work?
Input: arr[] = {5, 1, 4, 2, 8}
First Pass:
Bubble sort starts with the very first two elements, comparing them to check which one is greater.
- ( 5 1 4 2 8 ) –> ( 1 5 4 2 8 ), Here, the algorithm compares the first two elements and swaps since 5 > 1.
- ( 1 5 4 2 8 ) –> ( 1 4 5 2 8 ), Swap since 5 > 4
- ( 1 4 5 2 8 ) –> ( 1 4 2 5 8 ), Swap since 5 > 2
- ( 1 4 2 5 8 ) –> ( 1 4 2 5 8 ), Now, since these elements are already in order (8 > 5), the algorithm does not swap them.
Second Pass:
Now, during the second iteration it should look like this:
- ( 1 4 2 5 8 ) –> ( 1 4 2 5 8 )
- ( 1 4 2 5 8 ) –> ( 1 2 4 5 8 ), Swap since 4 > 2
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
Third Pass:
Now, the array is already sorted, but our algorithm does not know if it is completed. The algorithm needs one whole pass without any swap to know it is sorted.
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
- ( 1 2 4 5 8 ) –> ( 1 2 4 5 8 )
Follow the below steps to solve the problem:
- Run a nested for loop to traverse the input array using two variables i and j, such that 0 ≤ i < n-1 and 0 ≤ j < n-i-1
- If arr[j] is greater than arr[j+1] then swap these adjacent elements, else move on
- Print the sorted array
Below is the implementation of the above approach:
#include <stdio.h>
void swap(int* xp, int* yp)
{
int temp = *xp;
*xp = *yp;
*yp = temp;
}
// A function to implement bubble sort
void bubbleSort(int arr[], int n)
{
int i, j;
for (i = 0; i < n - 1; i++)
// Last i elements are already in place
for (j = 0; j < n - i - 1; j++)
if (arr[j] > arr[j + 1])
swap(&arr[j], &arr[j + 1]);
}
/* Function to print an array */
void printArray(int arr[], int size)
{
int i;
for (i = 0; i < size; i++)
printf(""%d "", arr[i]);
printf(""\n"");
}
// Driver program to test above functions
int main()
{
int arr[] = { 64, 34, 25, 12, 22, 11, 90 };
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf(""Sorted array: \n"");
printArray(arr, n);
return 0;
}
Output:
Sorted array:
1 2 4 5 8
//Time Complexity: O(N2)
//Auxiliary Space: O(1)
"
"In this lecture of our Algorithm module, we will learn about Selection Sort.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
The selection sort algorithm sorts an array by repeatedly finding the minimum element (considering ascending order) from the unsorted part and putting it at the beginning.
The algorithm maintains two subarrays in a given array.
- The subarray which already sorted.
- The remaining subarray was unsorted.
Below is the implementation of the above approach:
#include <bits/stdc++.h>
using namespace std;
//Swap function
void swap(int *xp, int *yp)
{
int temp = *xp;
*xp = *yp;
*yp = temp;
}
void selectionSort(int arr[], int n)
{
int i, j, min_idx;
// One by one move boundary of
// unsorted subarray
for (i = 0; i < n-1; i++)
{
// Find the minimum element in
// unsorted array
min_idx = i;
for (j = i+1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;
// Swap the found minimum element
// with the first element
if(min_idx!=i)
swap(&arr[min_idx], &arr[i]);
}
}
//Function to print an array
void printArray(int arr[], int size)
{
int i;
for (i=0; i < size; i++)
cout << arr[i] << "" "";
cout << endl;
}
// Driver program to test above functions
int main()
{
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr)/sizeof(arr[0]);
selectionSort(arr, n);
cout << ""Sorted array: \n"";
printArray(arr, n);
return 0;
}
Output
Sorted array:
11 12 22 25 64
Complexity Analysis of Selection Sort:
Time Complexity: The time complexity of Selection Sort is O(N2) as there are two nested loops:
- One loop to select an element of Array one by one = O(N)
- Another loop to compare that element with every other Array element = O(N)
Therefore overall complexity = O(N) * O(N) = O(N*N) = O(N2)
"
"In this lecture of our Algorithm module, we will learn about Insertion Sort.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Insertion sort is a simple sorting algorithm that works similarly to the way you sort playing cards in your hands. The array is virtually split into a sorted and an unsorted part. Values from the unsorted part are picked and placed in the correct position in the sorted part.
Characteristics of Insertion Sort:
- This algorithm is one of the simplest algorithms with simple implementation
- Basically, Insertion sort is efficient for small data values
- Insertion sort is adaptive in nature, i.e. it is appropriate for data sets that are already partially sorted.
Below is the implementation:
#include <bits/stdc++.h>
using namespace std;
// Function to sort an array using
// insertion sort
void insertionSort(int arr[], int n)
{
int i, key, j;
for (i = 1; i < n; i++)
{
key = arr[i];
j = i - 1;
// Move elements of arr[0..i-1],
// that are greater than key, to one
// position ahead of their
// current position
while (j >= 0 && arr[j] > key)
{
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
// A utility function to print an array
// of size n
void printArray(int arr[], int n)
{
int i;
for (i = 0; i < n; i++)
cout << arr[i] << "" "";
cout << endl;
}
// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6 };
int N = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, N);
printArray(arr, N);
return 0;
}
Output:
5 6 11 12 13
//Time Complexity: O(N^2)
//Auxiliary Space: O(1)"
"In this lecture of our Algorithm module, we will learn about Quick Sort.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
QuickSort is a Divide and Conquer algorithm. It picks an element as a pivot and partitions the given array around the picked pivot. There are many different versions of quickSort that pick pivot in different ways.
- Always pick the first element as a pivot.
- Always pick the last element as a pivot (implemented below)
- Pick a random element as a pivot.
- Pick median as the pivot.
The key process in quickSort is a partition(). The target of partitions is, given an array and an element x of an array as the pivot, put x at its correct position in a sorted array and put all smaller elements (smaller than x) before x, and put all greater elements (greater than x) after x. All this should be done in linear time.
Following are the implementations of QuickSort:
#include <bits/stdc++.h>
using namespace std;
// A utility function to swap two elements
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
int partition (int arr[], int low, int high)
{
int pivot = arr[high]; // pivot
int i = (low - 1); // Index of smaller element and indicates the right position of pivot found so far
for (int j = low; j <= high - 1; j++)
{
// If current element is smaller than the pivot
if (arr[j] < pivot)
{
i++; // increment index of smaller element
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
/* The main function that implements QuickSort
arr[] --> Array to be sorted,
low --> Starting index,
high --> Ending index */
void quickSort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = partition(arr, low, high);
// Separately sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
/* Function to print an array */
void printArray(int arr[], int size)
{
int i;
for (i = 0; i < size; i++)
cout << arr[i] << "" "";
cout << endl;
}
// Driver Code
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
cout << ""Sorted array: \n"";
printArray(arr, n);
return 0;
}
Output:
Sorted array:
1 5 7 8 9 10
//Time taken by QuickSort, in general, can be written as follows.
// T(n) = T(k) + T(n-k-1) + (n)
"
"In this lecture of our Algorithm module, we will learn about Merge Sort.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
The Merge Sort algorithm is a sorting algorithm that is based on the Divide and Conquer paradigm. In this algorithm, the array is initially divided into two equal halves and then they are combined in a sorted manner.
Algorithm:
- step 1: start
- step 2: declare array and left, right, mid variable
- step 3: perform merge function.
if left > right
return
mid= (left+right)/2
mergesort(array, left, mid)
mergesort(array, mid+1, right)
merge(array, left, mid, right)
- step 4: Stop
Follow the steps below the solve the problem:
MergeSort(arr[], l, r)
If r > l
- Find the middle point to divide the array into two halves:
- middle m = l + (r – l)/2
- Call mergeSort for first half:
- Call mergeSort(arr, l, m)
- Call mergeSort for second half:
- Call mergeSort(arr, m + 1, r)
- Merge the two halves sorted in steps 2 and 3:
- Call merge(arr, l, m, r)
Time Complexity: O(N log(N)), Sorting arrays on different machines. Merge Sort is a recursive algorithm and time complexity can be expressed as following recurrence relation.
T(n) = 2T(n/2) + θ(n)
"
"In this lecture of our Algorithm module, we will learn about Greedy Algorithms.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Greedy is an algorithmic paradigm that builds up a solution piece by piece, always choosing the next piece that offers the most obvious and immediate benefit. So the problems where choosing locally optimal also leads to the global solution is the best fit for Greedy.
- It is designed to achieve the optimum solution for a given problem. In the greedy algorithm approach, decisions are made from the given solution domain. As being greedy, the closest solution that seems to provide an optimum solution is chosen.
- It tries to find a localized optimum solution, which may eventually lead to globally optimized solutions. However, generally greedy algorithms do not provide globally optimized solutions.
Components of Greedy Algorithm
The components that can be used in the greedy algorithm are:
- Candidate set: A solution that is created from the set is known as a candidate set.
- Selection function: This function is used to choose the candidate or subset which can be added to the solution.
- Feasibility function: A function that is used to determine whether the candidate or subset can be used to contribute to the solution or not.
- Objective function: A function is used to assign the value to the solution or the partial solution.
- Solution function: This function is used to intimate whether the complete function has been reached or not.
Pseudo code of Greedy Algorithm:
Algorithm Greedy (a, n)
{
Solution : = 0;
for i = 0 to n do
{
x: = select(a);
if feasible(solution, x)
{
Solution: = union(solution , x)
}
return solution;
} }
"
"In this lecture of our Algorithm module, we will learn about Fractional Knapsack.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
The fractional knapsack problem is also one of the techniques which are used to solve the knapsack problem. In a fractional knapsack, the items are broken to maximize profit. The problem in which we break the item is known as a Fractional knapsack problem.
This problem can be solved with the help of using two techniques:
- Brute-force approach: The brute-force approach tries all the possible solutions with all the different fractions but it is a time-consuming approach.
- Greedy approach: In the Greedy approach, we calculate the ratio of profit/weight, and accordingly, we will select the item. The item with the highest ratio would be selected first.
There are three approaches to solving the problem:
- The first approach is to select the item based on the maximum profit.
- The second approach is to select the item based on the minimum weight.
- The third approach is to calculate the ratio of profit/weight.
Example:
#include <bits/stdc++.h>
using namespace std;
// Structure for an item which stores weight and
// corresponding value of Item
struct Item {
int value, weight;
// Constructor
Item(int value, int weight)
{
this->value = value;
this->weight = weight;
}
};
// Comparison function to sort Item according to val/weight
// ratio
bool cmp(struct Item a, struct Item b)
{
double r1 = (double)a.value / (double)a.weight;
double r2 = (double)b.value / (double)b.weight;
return r1 > r2;
}
// Main greedy function to solve problem
double fractionalKnapsack(int W, struct Item arr[], int N)
{
// sorting Item on basis of ratio
sort(arr, arr + N, cmp);
double finalvalue = 0.0; // Result (value in Knapsack)
// Looping through all Items
for (int i = 0; i < N; i++) {
// If adding Item won't overflow, add it completely
if (arr[i].weight <= W) {
W -= arr[i].weight;
finalvalue += arr[i].value;
}
// If we can't add current Item, add fractional part
// of it
else {
finalvalue
+= arr[i].value
* ((double)W / (double)arr[i].weight);
break;
}
}
// Returning final value
return finalvalue;
}
// Driver's code
int main()
{
int W = 50; // Weight of knapsack
Item arr[] = { { 60, 10 }, { 100, 20 }, { 120, 30 } };
int N = sizeof(arr) / sizeof(arr[0]);
// Function call
cout << ""Maximum value we can obtain = ""
<< fractionalKnapsack(W, arr, N);
return 0;
}
Output:
Maximum value we can obtain = 240
//Time Complexity: O(N log N)
//Auxiliary Space: O(N)
"
"In this lecture of our Algorithm module, we will learn about Job Sequencing Problem.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Problem Statement:
In job sequencing problem, the objective is to find a sequence of jobs, which is completed within their deadlines and gives maximum profit.
Solution:
Let us consider, a set of n given jobs that are associated with deadlines, and profit is earned, if a job is completed by its deadline. These jobs need to be ordered in such a way that there is maximum profit.
- It may happen that all of the given jobs may not be completed within their deadlines.
- Assume, the deadline of ith job Ji is di and the profit received from this job is pi. Hence, the optimal solution of this algorithm is a feasible solution with maximum profit.
- Thus, D(i)>0 for 1⩽i⩽n
- Initially, these jobs are ordered according to profit, i.e. p1⩾p2⩾p3⩾...⩾pn.
Algorithm: Job-Sequencing-With-Deadline (D, J, n, k)
D(0) := J(0) := 0
k := 1
J(1) := 1 // means first job is selected
for i = 2 … n do
r := k
while D(J(r)) > D(i) and D(J(r)) ≠ r do
r := r – 1
if D(J(r)) ≤ D(i) and D(i) > r then
for l = k … r + 1 by -1 do
J(l + 1) := J(l)
J(r + 1) := i
k := k + 1
Analysis:
In this algorithm, we are using two loops, one is within another. Hence, the complexity of this algorithm is O(n2).
Example:
Let us consider a set of given jobs as shown in the following table. We have to find a sequence of jobs, which will be completed within their deadlines and will give maximum profit. Each job is associated with a deadline and profit.
Job J1 J2 J3 J4 J5
Deadline 2 1 3 2 1
Profit 60 100 20 40 20
Solution:
To solve this problem, the given jobs are sorted according to their profit in a descending order. Hence, after sorting, the jobs are ordered as shown in the following table.
Job J2 J1 J4 J3 J5
Deadline 1 2 2 3 1
Profit 100 60 40 20 20
From this set of jobs, first we select J2, as it can be completed within its deadline and contributes maximum profit.
- Next, J1 is selected as it gives more profit compared to J4.
- In the next clock, J4 cannot be selected as its deadline is over, hence J3 is selected as it executes within its deadline.
- The job J5 is discarded as it cannot be executed within its deadline.
Thus, the solution is the sequence of jobs (J2, J1, J3), which are being executed within their deadline and gives maximum profit.
Total profit of this sequence is 100 + 60 + 20 = 180.
"
"In this lecture of our Algorithm module, we will learn about Huffman Coding & Huffman Algorithms.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Huffman coding is a lossless data compression algorithm. In this algorithm, a variable-length code is assigned to input different characters. The code length is related to how frequently characters are used. Most frequent characters have the smallest codes and longer codes for least frequent characters.
- There are mainly two parts. The first one is to create a Huffman tree, and another one is to traverse the tree to find codes.
- For example, consider some strings “YYYZXXYYX”, the frequency of character Y is larger than X and character Z has the least frequency. So the length of the code for Y is smaller than X, and the code for X will be smaller than Z.
- The complexity for assigning the code for each character according to their frequency is O(n log n)
Input and Output:
Input:
A string with different characters, say “ACCEBFFFFAAXXBLKE”
Output:
Code for different characters:
Data: K, Frequency: 1, Code: 0000
Data: L, Frequency: 1, Code: 0001
Data: E, Frequency: 2, Code: 001
Data: F, Frequency: 4, Code: 01
Data: B, Frequency: 2, Code: 100
Data: C, Frequency: 2, Code: 101
Data: X, Frequency: 2, Code: 110
Data: A, Frequency: 3, Code: 111
Algorithm: huffmanCoding(string)
- Input: A string with different characters.
- Output: The codes for each individual character.
Begin
define a node with character, frequency, left and right child of the node for Huffman tree.
create a list ‘freq’ to store frequency of each character, initially, all are 0
for each character c in the string do
increase the frequency for character ch in freq list.
done
for all type of character ch do
if the frequency of ch is non zero then
add ch and its frequency as a node of priority queue Q.
done
while Q is not empty do
remove item from Q and assign it to left child of node
remove item from Q and assign to the right child of node
traverse the node to find the assigned code
done
End
traverseNode(n: node, code)
- Input: The node n of the Huffman tree, and the code assigned from the previous call
- Output: Code assigned with each character
if a left child of node n ≠φ then
traverseNode(leftChild(n), code+’0’) //traverse through the left child
traverseNode(rightChild(n), code+’1’) //traverse through the right child
else
display the character and data of current node.
"
"In this lecture of our Algorithm module, we will learn about Recursion Introduction.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
A recursive algorithm calls itself with smaller input values and returns the result for the current input by carrying out basic operations on the returned value for the smaller input. Generally, if a problem can be solved by applying solutions to smaller versions of the same problem, and the smaller versions shrink to readily solvable instances, then the problem can be solved using a recursive algorithm.
In general, recursive computer programs require more memory and computation compared with iterative algorithms, but they are simpler and for many cases a natural way of thinking about the problem.
Example − a function calling itself.
int function(int value) {
if(value < 1)
return;
function(value - 1);
printf(""%d "",value);
}
Example − a function that calls another function which in turn calls it again.
int function1(int value1) {
if(value1 < 1)
return;
function2(value1 - 1);
printf(""%d "",value1);
}
int function2(int value2) {
function1(value2);
}
Properties:
A recursive function can go infinite like a loop. To avoid infinite running of recursive function, there are two properties that a recursive function must have −
- Base criteria − There must be at least one base criteria or condition, such that, when this condition is met the function stops calling itself recursively.
- Progressive approach − The recursive calls should progress in such a way that each time a recursive call is made it comes closer to the base criteria.
- Time Complexity: In case of iterations, we take number of iterations to count the time complexity. Likewise, in case of recursion, assuming everything is constant, we try to figure out the number of times a recursive call is being made. A call made to a function is Ο(1), hence the (n) number of times a recursive call is made makes the recursive function Ο(n).
- Space Complexity: Space complexity is counted as what amount of extra space is required for a module to execute. In case of iterations, the compiler hardly requires any extra space. The compiler keeps updating the values of variables used in the iterations. But in case of recursion, the system needs to store an activation record each time a recursive call is made. Hence, it is considered that the space complexity of the recursive function may go higher than that of a function with iteration.
"
"In this lecture of our Algorithm module, we will learn about printing N to 1 & printing 1 to N using Recursion.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that A recursive algorithm is an algorithm which calls itself with ""smaller (or simpler)"" input values, and which obtains the result for the current input by applying simple operations to the returned value for the smaller (or simpler) input. Now we will learn about its implementation and will use it in an example to demonstrate the same.
Program to print numbers from N to 1 in reverse order:
Given a number N, the task is to print the numbers from N to 1.
Examples:
Input: N = 10
Output: 10 9 8 7 6 5 4 3 2 1
Input: N = 7
Output: 7 6 5 4 3 2 1
Approach 1: We will use recursion to solve this problem.
1. Check for the base case. Here it is N<=0.
2. If base condition satisfied, return to the main function.
3. If base condition not satisfied, print N and call the function recursively with value (N – 1) until base condition satisfies.
Below is the implementation of the above approach.
#include <bits/stdc++.h>
using namespace std;
// Recursive function to print from N to 1
void PrintReverseOrder(int N)
{
// if N is less than 1
// then return void function
if (N <= 0) {
return;
}
else {
cout << N << "" "";
// recursive call of the function
PrintReverseOrder(N - 1);
}
}
// Driven Code
int main()
{
int N = 5;
PrintReverseOrder(N);
return 0;
}
Output:
5 4 3 2 1
//Time Complexity: O(N)
//Auxiliary Space: O(N)
"
"In this lecture of our Algorithm module, we will learn about Natural Number Sum using Recursion.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that A recursive algorithm is an algorithm which calls itself with ""smaller (or simpler)"" input values, and which obtains the result for the current input by applying simple operations to the returned value for the smaller (or simpler) input. Now we will learn about its implementation and will use it in an example to demonstrate the same.
Given a number n, find sum of first n natural numbers. To calculate the sum, we will use a recursive function recur_sum().
Examples :
Input : 3
Output : 6
Explanation : 1 + 2 + 3 = 6
Input : 5
Output : 15
Explanation : 1 + 2 + 3 + 4 + 5 = 15
Below is code to find the sum of natural numbers up to n using recursion :
#include <iostream>
using namespace std;
// Returns sum of first
// n natural numbers
int recurSum(int n)
{
if (n <= 1)
return n;
return n + recurSum(n - 1);
}
// Driver code
int main()
{
int n = 5;
cout << recurSum(n);
return 0;
}
Output :
15
// Time complexity : O(n)
// Auxiliary space : O(n)
"
"In this lecture of our Algorithm module, we will learn about Sum of Digits Using Recursion.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Given a number, we need to find sum of its digits using recursion.
Examples:
Input : 12345
Output : 15
Input : 45632
Output :20
"
"In this lecture of our Algorithm module, we will learn about Rope Cutting Problem.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Given a rope of length n meters, cut the rope in different parts of integer lengths in a way that maximizes product of lengths of all parts. You must make at least one cut. Assume that the length of rope is more than 2 meters.
Examples:
Input: n = 2
Output: 1 (Maximum obtainable product is 1*1)
Input: n = 3
Output: 2 (Maximum obtainable product is 1*2)
Input: n = 4
Output: 4 (Maximum obtainable product is 2*2)
Input: n = 5
Output: 6 (Maximum obtainable product is 2*3)
Input: n = 10
Output: 36 (Maximum obtainable product is 3*3*4)
int maxProd(int n)
{
int val[n+1];
val[0] = val[1] = 0;
// Build the table val[] in bottom up manner and return
// the last entry from the table
for (int i = 1; i <= n; i++)
{
int max_val = 0;
for (int j = 1; j <= i; j++)
max_val = max(max_val, (i-j)*j, j*val[i-j]);
val[i] = max_val;
}
return val[n];
}
Time Complexity of the Dynamic Programming solution is O(n^2) and it requires O(n) extra space. This is a hands on tutorial where we will discuss the algorithm using an example for clear understanding of the topic.
"
"In this lecture of our Algorithm module, we will learn about Generating Subsets.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that A recursive algorithm is an algorithm which calls itself with ""smaller (or simpler)"" input values, and which obtains the result for the current input by applying simple operations to the returned value for the smaller (or simpler) input. Now we will learn about its implementation and will use it in an example to demonstrate the same.
Given a set represented as a string, write a recursive code to print all subsets of it. The subsets can be printed in any order.
Examples:
Input : set = ""abc""
Output : """", ""a"", ""b"", ""c"", ""ab"", ""ac"", ""bc"", ""abc""
Input : set = ""abcd""
Output : """" ""a"" ""ab"" ""abc"" ""abcd"" ""abd"" ""ac"" ""acd""
""ad"" ""b"" ""bc"" ""bcd"" ""bd"" ""c"" ""cd"" ""d""
Method: The idea is to pick each element one by one from the input set, then generate a subset for the same, and we follow this process recursively.
We’ll use ArrayList for this purpose
For ex,
- f(0) = {a}, {} // {} when we don’t include any element from the set, it is null i.e {}.
- f(1) = {a}, {}, {b}, {a, b} // We have to copy all the elements from f(0) and then include the very next element from the set i.e b. So f(1) = f(0) + 1;
- f(2) = {a}, {}, {b}, {a, b}, {a, c}, {c}, {b, c}, {a, b, c}. So f(2) = f(1) +2;
The general form becomes f(n) = f(n-1) + n;
"
"In this lecture of our Algorithm module, we will learn about Tower of Hanoi.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Tower of Hanoi is a mathematical puzzle where we have three rods (A, B, and C) and N disks. Initially, all the disks are stacked in decreasing value of diameter i.e., the smallest disk is placed on the top and they are on rod A. The objective of the puzzle is to move the entire stack to another rod (here considered C), obeying the following simple rules:
- Only one disk can be moved at a time.
- Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack i.e. a disk can only be moved if it is the uppermost disk on a stack.
- No disk may be placed on top of a smaller disk.
Tower of Hanoi using Recursion:
The idea is to use the helper node to reach the destination using recursion. Below is the pattern for this problem:
- Shift ‘N-1’ disks from ‘A’ to ‘B’, using C.
- Shift last disk from ‘A’ to ‘C’.
- Shift ‘N-1’ disks from ‘B’ to ‘C’, using A.
Below is the implementation of the above approach.
#include <bits/stdc++.h>
using namespace std;
void towerOfHanoi(int n, char from_rod, char to_rod,
char aux_rod)
{
if (n == 0) {
return;
}
towerOfHanoi(n - 1, from_rod, aux_rod, to_rod);
cout << ""Move disk "" << n << "" from rod "" << from_rod
<< "" to rod "" << to_rod << endl;
towerOfHanoi(n - 1, aux_rod, to_rod, from_rod);
}
// Driver code
int main()
{
int N = 3;
// A, B and C are names of rods
towerOfHanoi(N, 'A', 'C', 'B');
return 0;
}
Output:
Move disk 1 from rod A to rod C
Move disk 2 from rod A to rod B
Move disk 1 from rod C to rod B
Move disk 3 from rod A to rod C
Move disk 1 from rod B to rod A
Move disk 2 from rod B to rod C
Move disk 1 from rod A to rod C
Time complexity: O(2N), There are two possibilities for every disk. Therefore, 2 * 2 * 2 * . . . * 2(N times) is 2N.
Auxiliary Space: O(N), Function call stack space.
"
"In this lecture of our Algorithm module, we will learn about Concepts of Backtracking.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Backtracking is an algorithmic technique for solving problems recursively by trying to build a solution incrementally, one piece at a time, removing those solutions that fail to satisfy the constraints of the problem at any point in time (by time, here, is referred to the time elapsed till reaching any level of the search tree).
There are three types of problems in backtracking –
1. Decision Problem – In this, we search for a feasible solution.
2. Optimization Problem – In this, we search for the best solution.
3. Enumeration Problem – In this, we find all feasible solutions.
Pseudo Code for Backtracking:
- Recursive backtracking solution.
void findSolutions(n, other params) :
if (found a solution) :
solutionsFound = solutionsFound + 1;
displaySolution();
if (solutionsFound >= solutionTarget) :
System.exit(0);
return
for (val = first to last) :
if (isValid(val, n)) :
applyValue(val, n);
findSolutions(n+1, other params);
removeValue(val, n);
- Finding whether a solution exists or not
boolean findSolutions(n, other params) :
if (found a solution) :
displaySolution();
return true;
for (val = first to last) :
if (isValid(val, n)) :
applyValue(val, n);
if (findSolutions(n+1, other params))
return true;
removeValue(val, n);
return false;
"
"In this lecture of our Algorithm module, we will learn about N Queen Problem.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that A backtracking algorithm is a problem-solving algorithm that uses a brute force approach for finding the desired output. The Brute force approach tries out all the possible solutions and chooses the desired/best solutions. Now we will learn about its implementation with help of an example.
#include <bits/stdc++.h>
#define N 4
using namespace std;
/* A utility function to print solution */
void printSolution(int board[N][N])
{
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
cout << "" "" << board[i][j] << "" "";
printf(""\n"");
}
}
/* A utility function to check if a queen can
be placed on board[row][col]. Note that this
function is called when ""col"" queens are
already placed in columns from 0 to col -1.
So we need to check only left side for
attacking queens */
bool isSafe(int board[N][N], int row, int col)
{
int i, j;
/* Check this row on left side */
for (i = 0; i < col; i++)
if (board[row][i])
return false;
/* Check upper diagonal on left side */
for (i = row, j = col; i >= 0 && j >= 0; i--, j--)
if (board[i][j])
return false;
/* Check lower diagonal on left side */
for (i = row, j = col; j >= 0 && i < N; i++, j--)
if (board[i][j])
return false;
return true;
}
/* A recursive utility function to solve N
Queen problem */
bool solveNQUtil(int board[N][N], int col)
{
/* base case: If all queens are placed
then return true */
if (col >= N)
return true;
/* Consider this column and try placing
this queen in all rows one by one */
for (int i = 0; i < N; i++) {
/* Check if the queen can be placed on
board[i][col] */
if (isSafe(board, i, col)) {
/* Place this queen in board[i][col] */
board[i][col] = 1;
/* recur to place rest of the queens */
if (solveNQUtil(board, col + 1))
return true;
/* If placing queen in board[i][col]
doesn't lead to a solution, then
remove queen from board[i][col] */
board[i][col] = 0; // BACKTRACK
}
}
/* If the queen cannot be placed in any row in
this column col then return false */
return false;
}
/* This function solves the N Queen problem using
Backtracking. It mainly uses solveNQUtil() to
solve the problem. It returns false if queens
cannot be placed, otherwise, return true and
prints placement of queens in the form of 1s.
Please note that there may be more than one
solutions, this function prints one of the
feasible solutions.*/
bool solveNQ()
{
int board[N][N] = { { 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 } };
if (solveNQUtil(board, 0) == false) {
cout << ""Solution does not exist"";
return false;
}
printSolution(board);
return true;
}
// driver program to test above function
int main()
{
solveNQ();
return 0;
}
Output:
0 0 1 0
1 0 0 0
0 0 0 1
0 1 0 0
Time Complexity: O(N!)
Auxiliary Space: O(N2)
Optimization in is_safe() function
The idea is not to check every element in right and left diagonal, instead use the property of diagonals:
1. The sum of i and j is constant and unique for each right diagonal, where i is the row of elements and j is the column of elements.
2. The difference between i and j is constant and unique for each left diagonal, where i and j are row and column of element respectively."
"In this lecture of our Algorithm module, we will learn about Sudoku Problem.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that A backtracking algorithm is a problem-solving algorithm that uses a brute force approach for finding the desired output. The Brute force approach tries out all the possible solutions and chooses the desired/best solutions. Now we will learn about its implementation with help of an example.
Problem Statement: Given a partially filled 9×9 2D array ‘grid[9][9]’, the goal is to assign digits (from 1 to 9) to the empty cells so that every row, column, and subgrid of size 3×3 contains exactly one instance of the digits from 1 to 9.
Example:
Input:
grid = { {3, 0, 6, 5, 0, 8, 4, 0, 0},
{5, 2, 0, 0, 0, 0, 0, 0, 0},
{0, 8, 7, 0, 0, 0, 0, 3, 1},
{0, 0, 3, 0, 1, 0, 0, 8, 0},
{9, 0, 0, 8, 6, 3, 0, 0, 5},
{0, 5, 0, 0, 9, 0, 6, 0, 0},
{1, 3, 0, 0, 0, 0, 2, 5, 0},
{0, 0, 0, 0, 0, 0, 0, 7, 4},
{0, 0, 5, 2, 0, 6, 3, 0, 0} }
Output:
3 1 6 5 7 8 4 9 2
5 2 9 1 3 4 7 6 8
4 8 7 6 2 9 5 3 1
2 6 3 4 1 5 9 8 7
9 7 4 8 6 3 1 2 5
8 5 1 7 9 2 6 4 3
1 3 8 9 4 7 2 5 6
6 9 2 3 5 1 8 7 4
7 4 5 2 8 6 3 1 9
Explanation: Each row, column and 3*3 box of
the output matrix contains unique numbers.
Input:
grid = { { 3, 1, 6, 5, 7, 8, 4, 9, 2 },
{ 5, 2, 9, 1, 3, 4, 7, 6, 8 },
{ 4, 8, 7, 6, 2, 9, 5, 3, 1 },
{ 2, 6, 3, 0, 1, 5, 9, 8, 7 },
{ 9, 7, 4, 8, 6, 0, 1, 2, 5 },
{ 8, 5, 1, 7, 9, 2, 6, 4, 3 },
{ 1, 3, 8, 0, 4, 7, 2, 0, 6 },
{ 6, 9, 2, 3, 5, 1, 8, 7, 4 },
{ 7, 4, 5, 0, 8, 6, 3, 1, 0 } };
Output:
3 1 6 5 7 8 4 9 2
5 2 9 1 3 4 7 6 8
4 8 7 6 2 9 5 3 1
2 6 3 4 1 5 9 8 7
9 7 4 8 6 3 1 2 5
8 5 1 7 9 2 6 4 3
1 3 8 9 4 7 2 5 6
6 9 2 3 5 1 8 7 4
7 4 5 2 8 6 3 1 9
Explanation: Each row, column and 3*3 box of
the output matrix contains unique numbers.
Code Implementation:
#include <bits/stdc++.h>
using namespace std;
// UNASSIGNED is used for empty
// cells in sudoku grid
#define UNASSIGNED 0
// N is used for the size of Sudoku grid.
// Size will be NxN
#define N 9
// This function finds an entry in grid
// that is still unassigned
bool FindUnassignedLocation(int grid[N][N],
int& row, int& col);
// Checks whether it will be legal
// to assign num to the given row, col
bool isSafe(int grid[N][N], int row,
int col, int num);
/* Takes a partially filled-in grid and attempts
to assign values to all unassigned locations in
such a way to meet the requirements for
Sudoku solution (non-duplication across rows,
columns, and boxes) */
bool SolveSudoku(int grid[N][N])
{
int row, col;
// If there is no unassigned location,
// we are done
if (!FindUnassignedLocation(grid, row, col))
// success!
return true;
// Consider digits 1 to 9
for (int num = 1; num <= 9; num++)
{
// Check if looks promising
if (isSafe(grid, row, col, num))
{
// Make tentative assignment
grid[row][col] = num;
// Return, if success
if (SolveSudoku(grid))
return true;
// Failure, unmake & try again
grid[row][col] = UNASSIGNED;
}
}
// This triggers backtracking
return false;
}
/* Searches the grid to find an entry that is
still unassigned. If found, the reference
parameters row, col will be set the location
that is unassigned, and true is returned.
If no unassigned entries remain, false is returned. */
bool FindUnassignedLocation(int grid[N][N],
int& row, int& col)
{
for (row = 0; row < N; row++)
for (col = 0; col < N; col++)
if (grid[row][col] == UNASSIGNED)
return true;
return false;
}
/* Returns a boolean which indicates whether
an assigned entry in the specified row matches
the given number. */
bool UsedInRow(int grid[N][N], int row, int num)
{
for (int col = 0; col < N; col++)
if (grid[row][col] == num)
return true;
return false;
}
/* Returns a boolean which indicates whether
an assigned entry in the specified column
matches the given number. */
bool UsedInCol(int grid[N][N], int col, int num)
{
for (int row = 0; row < N; row++)
if (grid[row][col] == num)
return true;
return false;
}
/* Returns a boolean which indicates whether
an assigned entry within the specified 3x3 box
matches the given number. */
bool UsedInBox(int grid[N][N], int boxStartRow,
int boxStartCol, int num)
{
for (int row = 0; row < 3; row++)
for (int col = 0; col < 3; col++)
if (grid[row + boxStartRow]
[col + boxStartCol] ==
num)
return true;
return false;
}
/* Returns a boolean which indicates whether
it will be legal to assign num to the given
row, col location. */
bool isSafe(int grid[N][N], int row,
int col, int num)
{
/* Check if 'num' is not already placed in
current row, current column
and current 3x3 box */
return !UsedInRow(grid, row, num)
&& !UsedInCol(grid, col, num)
&& !UsedInBox(grid, row - row % 3,
col - col % 3, num)
&& grid[row][col] == UNASSIGNED;
}
/* A utility function to print grid */
void printGrid(int grid[N][N])
{
for (int row = 0; row < N; row++)
{
for (int col = 0; col < N; col++)
cout << grid[row][col] << "" "";
cout << endl;
}
}
// Driver Code
int main()
{
// 0 means unassigned cells
int grid[N][N] = { { 3, 0, 6, 5, 0, 8, 4, 0, 0 },
{ 5, 2, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 8, 7, 0, 0, 0, 0, 3, 1 },
{ 0, 0, 3, 0, 1, 0, 0, 8, 0 },
{ 9, 0, 0, 8, 6, 3, 0, 0, 5 },
{ 0, 5, 0, 0, 9, 0, 6, 0, 0 },
{ 1, 3, 0, 0, 0, 0, 2, 5, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 7, 4 },
{ 0, 0, 5, 2, 0, 6, 3, 0, 0 } };
if (SolveSudoku(grid) == true)
printGrid(grid);
else
cout << ""No solution exists"";
return 0;
}
"
"In this lecture of our Algorithm module, we will learn about Breadth First Search.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
A Graph is a non-linear data structure consisting of vertices and edges. The vertices are sometimes also referred to as nodes and the edges are lines or arcs that connect any two nodes in the graph. More formally a Graph is composed of a set of vertices( V ) and a set of edges( E ). The graph is denoted by G(E, V).
Tree v/s Graph
Trees are the restricted types of graphs, just with some more rules. Every tree will always be a graph but not all graphs will be trees. Linked List, Trees, and Heaps all are special cases of graphs.
In the coming videos, we will be learning various types of Graphs like:
1. Undirected Graph: A graph in which edges do not have any direction. That is the nodes are unordered pairs in the definition of every edge.
2. Directed Graph: A graph in which edge has direction. That is the nodes are ordered pairs in the definition of every edge.
3. Undirected Graph: A graph in which edges do not have any direction. That is the nodes are unordered pairs in the definition of every edge.
4. Directed Graph: A graph in which edge has direction. That is the nodes are ordered pairs in the definition of every edge.
5. Cyclic Graph: A graph containing at least one cycle is known as a Cyclic graph.
6. Directed Acyclic Graph: A Directed Graph that does not contain any cycle.
7. Weighted Graph
- A graph in which the edges are already specified with suitable weight is known as a weighted graph.
- Weighted graphs can be further classified as directed weighted graphs and undirected weighted graphs.
"
"In this lecture of our Algorithm module, we will learn about Breadth First Search.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Breadth-first search is a graph traversal algorithm that starts traversing the graph from the root node and explores all the neighboring nodes. Then, it selects the nearest node and explores all the unexplored nodes. While using BFS for traversal, any node in the graph can be considered as the root node.
Given below is the algorithm for BFS technique.
Consider G as a graph which we are going to traverse using the BFS algorithm.
Let S be the root/starting node of the graph.
- Step 1: Start with node S and enqueue it to the queue.
- Step 2: Repeat the following steps for all the nodes in the graph.
- Step 3: Dequeue S and process it.
- Step 4: Enqueue all the adjacent nodes of S and process them.
- [END OF LOOP]
- Step 6: EXIT
The pseudo-code for the BFS technique is given below.
Procedure BFS (G, s)
G is the graph and s is the source node
begin
let q be queue to store nodes
q.enqueue(s) //insert source node in the queue
mark s as visited.
while (q is not empty)
//remove the element from the queue whose adjacent nodes are to be processed
n = q.dequeue( )
//processing all the adjacent nodes of n
for all neighbors m of n in Graph G if w is not visited
q.enqueue (m) //Stores m in Q to in turn visit its adjacent nodes
mark m as visited.
end
"
"In this lecture of our Algorithm module, we will learn about Depth First Search Algorithm.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Depth First Traversal (or Search) for a graph is similar to Depth First Traversal of a tree. The only catch here is, that, unlike trees, graphs may contain cycles (a node may be visited twice). To avoid processing a node more than once, use a boolean visited array. A graph can have more than one DFS traversal.
Follow the below steps to solve the problem:
- Create a recursive function that takes the index of the node and a visited array.
- Mark the current node as visited and print the node.
- Traverse all the adjacent and unmarked nodes and call the recursive function with the index of the adjacent node.
Below is the implementation of the above approach:
#include <bits/stdc++.h>
using namespace std;
// Graph class represents a directed graph
// using adjacency list representation
class Graph {
public:
map<int, bool> visited;
map<int, list<int> > adj;
// function to add an edge to graph
void addEdge(int v, int w);
// DFS traversal of the vertices
// reachable from v
void DFS(int v);
};
void Graph::addEdge(int v, int w)
{
adj[v].push_back(w); // Add w to v’s list.
}
void Graph::DFS(int v)
{
// Mark the current node as visited and
// print it
visited[v] = true;
cout << v << "" "";
// Recur for all the vertices adjacent
// to this vertex
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i)
if (!visited[*i])
DFS(*i);
}
// Driver's code
int main()
{
// Create a graph given in the above diagram
Graph g;
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
cout << ""Following is Depth First Traversal""
"" (starting from vertex 2) \n"";
// Function call
g.DFS(2);
return 0;
}
Output:
Following is Depth First Traversal (starting from vertex 2)
2 0 1 3
Time complexity: O(V + E), where V is the number of vertices and E is the number of edges in the graph.
Auxiliary Space: O(V), since an extra visited array of size V is required."
"In this lecture of our Algorithm module, we will learn about Applications of DFS.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Previously we learned that Depth-first search (DFS) is an algorithm (or technique) for traversing a graph. DFS uses a stack data structure for the traversal. A graph can have more than one DFS traversal.
Following is the problem that use DFS as a building block.
Detecting cycle in a graph: A graph has cycle if and only if we see a back edge during DFS. So we can run DFS for the graph and check for back edges.
Follow the below steps to Implement the idea:
- Create the graph using the given number of edges and vertices.
- Create a recursive function that initializes the current vertex, visited array, and recursion stack.
- Mark the current node as visited and also mark the index in the recursion stack.
- Find all the vertices which are not visited and are adjacent to the current node and recursively call the function for those vertices
- If the recursive function returns true, return true.
- If the adjacent vertices are already marked in the recursion stack then return true.
- Create a wrapper function, that calls the recursive function for all the vertices, and
- If any function returns true return true.
- Else if for all vertices the function returns false return false.
// A C++ Program to detect cycle in a graph
#include<bits/stdc++.h>
using namespace std;
class Graph
{
int V; // No. of vertices
list<int> *adj; // Pointer to an array containing adjacency lists
bool isCyclicUtil(int v, bool visited[], bool *rs); // used by isCyclic()
public:
Graph(int V); // Constructor
void addEdge(int v, int w); // to add an edge to graph
bool isCyclic(); // returns true if there is a cycle in this graph
};
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w)
{
adj[v].push_back(w); // Add w to v’s list.
}
// This function is a variation of DFSUtil() in
// https://www.geeksforgeeks.org/archives/18212
bool Graph::isCyclicUtil(int v, bool visited[], bool *recStack)
{
if(visited[v] == false)
{
// Mark the current node as visited and part of recursion stack
visited[v] = true;
recStack[v] = true;
// Recur for all the vertices adjacent to this vertex
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i)
{
if ( !visited[*i] && isCyclicUtil(*i, visited, recStack) )
return true;
else if (recStack[*i])
return true;
}
}
recStack[v] = false; // remove the vertex from recursion stack
return false;
}
// Returns true if the graph contains a cycle, else false.
// This function is a variation of DFS() in
// https://www.geeksforgeeks.org/archives/18212
bool Graph::isCyclic()
{
// Mark all the vertices as not visited and not part of recursion
// stack
bool *visited = new bool[V];
bool *recStack = new bool[V];
for(int i = 0; i < V; i++)
{
visited[i] = false;
recStack[i] = false;
}
// Call the recursive helper function to detect cycle in different
// DFS trees
for(int i = 0; i < V; i++)
if ( !visited[i] && isCyclicUtil(i, visited, recStack))
return true;
return false;
}
int main()
{
// Create a graph given in the above diagram
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
if(g.isCyclic())
cout << ""Graph contains cycle"";
else
cout << ""Graph doesn't contain cycle"";
return 0;
}
"
"In this lecture of our Algorithm module, we will learn about Detect Cycle in Undirected Graph.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Problem Statement: Given an undirected graph, how to check if there is a cycle in the graph?
Approach: Run a DFS from every unvisited node. Depth First Traversal can be used to detect a cycle in a Graph. DFS for a connected graph produces a tree. There is a cycle in a graph only if there is a back edge present in the graph. A back edge is an edge that is joining a node to itself (self-loop) or one of its ancestor in the tree produced by DFS.
#include <iostream>
#include <limits.h>
#include <list>
using namespace std;
// Class for an undirected graph
class Graph {
// No. of vertices
int V;
// Pointer to an array
// containing adjacency lists
list<int>* adj;
bool isCyclicUtil(int v, bool visited[], int parent);
public:
// Constructor
Graph(int V);
// To add an edge to graph
void addEdge(int v, int w);
// Returns true if there is a cycle
bool isCyclic();
};
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w)
{
// Add w to v’s list.
adj[v].push_back(w);
// Add v to w’s list.
adj[w].push_back(v);
}
// A recursive function that
// uses visited[] and parent to detect
// cycle in subgraph reachable
// from vertex v.
bool Graph::isCyclicUtil(int v, bool visited[], int parent)
{
// Mark the current node as visited
visited[v] = true;
// Recur for all the vertices
// adjacent to this vertex
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i) {
// If an adjacent vertex is not visited,
// then recur for that adjacent
if (!visited[*i]) {
if (isCyclicUtil(*i, visited, v))
return true;
}
// If an adjacent vertex is visited and
// is not parent of current vertex,
// then there exists a cycle in the graph.
else if (*i != parent)
return true;
}
return false;
}
// Returns true if the graph contains
// a cycle, else false.
bool Graph::isCyclic()
{
// Mark all the vertices as not
// visited and not part of recursion
// stack
bool* visited = new bool[V];
for (int i = 0; i < V; i++)
visited[i] = false;
// Call the recursive helper
// function to detect cycle in different
// DFS trees
for (int u = 0; u < V; u++) {
// Don't recur for u if
// it is already visited
if (!visited[u])
if (isCyclicUtil(u, visited, -1))
return true;
}
return false;
}
// Driver program to test above functions
int main()
{
Graph g1(5);
g1.addEdge(1, 0);
g1.addEdge(0, 2);
g1.addEdge(2, 1);
g1.addEdge(0, 3);
g1.addEdge(3, 4);
g1.isCyclic() ? cout << ""Graph contains cycle\n""
: cout << ""Graph doesn't contain cycle\n"";
Graph g2(3);
g2.addEdge(0, 1);
g2.addEdge(1, 2);
g2.isCyclic() ? cout << ""Graph contains cycle\n""
: cout << ""Graph doesn't contain cycle\n"";
return 0;
}
Output:
Graph contains cycle
Graph doesn't contain cycle
Complexity Analysis:
- Time Complexity: O(V+E).
- The program does a simple DFS Traversal of the graph which is represented using adjacency list. So the time complexity is O(V+E).
- Space Complexity: O(V).
- To store the visited array O(V) space is required.
"
"In this lecture of our Algorithm module, we will learn about Topological Sorting (DFS Based Algorithm).
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of vertices such that for every directed edge uv, vertex u comes before v in the ordering. Topological Sorting for a graph is not possible if the graph is not a DAG.
Below is the implementation of DFS to print topological sorting of a DAG:
#include <iostream>
#include <list>
#include <stack>
using namespace std;
// Class to represent a graph
class Graph {
// No. of vertices'
int V;
// Pointer to an array containing adjacency listsList
list<int>* adj;
// A function used by topologicalSort
void topologicalSortUtil(int v, bool visited[],
stack<int>& Stack);
public:
// Constructor
Graph(int V);
// function to add an edge to graph
void addEdge(int v, int w);
// prints a Topological Sort of
// the complete graph
void topologicalSort();
};
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
void Graph::addEdge(int v, int w)
{
// Add w to v’s list.
adj[v].push_back(w);
}
// A recursive function used by topologicalSort
void Graph::topologicalSortUtil(int v, bool visited[],
stack<int>& Stack)
{
// Mark the current node as visited.
visited[v] = true;
// Recur for all the vertices
// adjacent to this vertex
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i)
if (!visited[*i])
topologicalSortUtil(*i, visited, Stack);
// Push current vertex to stack
// which stores result
Stack.push(v);
}
// The function to do Topological Sort.
// It uses recursive topologicalSortUtil()
void Graph::topologicalSort()
{
stack<int> Stack;
// Mark all the vertices as not visited
bool* visited = new bool[V];
for (int i = 0; i < V; i++)
visited[i] = false;
// Call the recursive helper function
// to store Topological
// Sort starting from all
// vertices one by one
for (int i = 0; i < V; i++)
if (visited[i] == false)
topologicalSortUtil(i, visited, Stack);
// Print contents of stack
while (Stack.empty() == false) {
cout << Stack.top() << "" "";
Stack.pop();
}
}
// Driver Code
int main()
{
// Create a graph given in the above diagram
Graph g(6);
g.addEdge(5, 2);
g.addEdge(5, 0);
g.addEdge(4, 0);
g.addEdge(4, 1);
g.addEdge(2, 3);
g.addEdge(3, 1);
cout << ""Following is a Topological Sort of the given ""
""graph \n"";
// Function Call
g.topologicalSort();
return 0;
}
Output:
Following is a Topological Sort of the given graph
5 4 2 3 1 0
Complexity Analysis:
- Time Complexity: O(V+E).
- The above algorithm is simply DFS with an extra stack. So time complexity is the same as DFS which is.
- Auxiliary space: O(V).
- The extra space is needed for the stack.
"
"In this lecture of our Algorithm module, we will learn about Topological Sorting (Kahn's BFS Based Algorithm).
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of vertices such that for every directed edge uv, vertex u comes before v in the ordering. Topological Sorting for a graph is not possible if the graph is not a DAG.
The C++ code using a BFS traversal is given below:
vector<int> topological_sorting(vector<vector<int>> graph) {
vector <int> indegree(graph.size(), 0);
queue<int> q;
vector<int> solution;
for(int i = 0; i < graph.size(); i++) {
for(int j = 0; j < graph[i].size(); j++)
{
//iterate over all edges
indegree[ graph[i][j] ]++;
}
}
//enqueue all nodes with indegree 0
for(int i = 0; i < graph.size(); i++)
{
if(indegree[i] == 0) {
q.push(i);
}
}
//remove one node after the other
while(q.size() > 0) {
int currentNode = q.front();
q.pop();
solution.push_back(currentNode);
for(int j = 0; j < graph[currentNode].size(); j++)
{
//remove all edges
int newNode = graph[currentNode][j];
indegree[newNode]--;
if(indegree[newNode] == 0)
{
//target node has now no more incoming edges
q.push(newNode);
}
}
}
return solution;
}
int main()
{
int n,v1,v2;
cin>>n;
vector<vector<int>> graph;
for(int i=1;i<=n;i++)
{
cin>>v1>>v2;
g.addEdge(v1, v2);
}
cout << "" Topological Sort of the given graph \n"";
g.topologicalSort();
return 0;
}
"
"In this lecture of our Algorithm module, we will learn about Shortest Path in DAG.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
What do we mean by the Shortest Path in an unweighted graph?
Let G = (V, E) be an undirected graph with E edges and V vertices. Let T be the shortest path between any 2 vertices in the graph such that there is no other path in the graph between any 2 vertices whose sum of edge weights is less than T’s sum of edge weights.
In the case of unweighted graphs, there will be no edge weights. In that case, the shortest path T will become the path between the given 2 vertices with the minimum number of edges.
Pseudo Code:
Let's assume that the function computeAllPaths(Src, Dest) returns all paths from Src to Dest.
procedure ShortestPath(Graph G, Src, Dest)
1. List_of_paths ← computeAllPaths(G, Src, Dest)
2. Shortest_path ← empty list
3. for path in list_of_paths do
4. if Shortest_path is empty do
5. Shortest_path ← path
6. end if
7. else if Shortest_path.size() > path.size() do
8. Shortest_path ← path
9. end else if
10. return Shortest_path
11. end procedure"
"In this lecture of our Algorithm module, we will learn about Bellman Ford Shortest Path Algorithm.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
The Bellman-Ford algorithm is a single-source shortest path algorithm. This means that, given a weighted graph, this algorithm will output the shortest distance from a selected node to all other nodes. It is very similar to the Dijkstra Algorithm.
Below is the implementation of the approach:
#include <bits/stdc++.h>
using namespace std;
// a structure to represent a weighted edge in graph
struct Edge {
int src, dest, weight;
};
// a structure to represent a connected, directed and
// weighted graph
struct Graph {
// V-> Number of vertices, E-> Number of edges
int V, E;
// graph is represented as an array of edges.
struct Edge* edge;
};
// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E)
{
struct Graph* graph = new Graph;
graph->V = V;
graph->E = E;
graph->edge = new Edge[E];
return graph;
}
// A utility function used to print the solution
void printArr(int dist[], int n)
{
printf(""Vertex Distance from Source\n"");
for (int i = 0; i < n; ++i)
printf(""%d \t\t %d\n"", i, dist[i]);
}
// The main function that finds shortest distances from src
// to all other vertices using Bellman-Ford algorithm. The
// function also detects negative weight cycle
void BellmanFord(struct Graph* graph, int src)
{
int V = graph->V;
int E = graph->E;
int dist[V];
// Step 1: Initialize distances from src to all other
// vertices as INFINITE
for (int i = 0; i < V; i++)
dist[i] = INT_MAX;
dist[src] = 0;
// Step 2: Relax all edges |V| - 1 times. A simple
// shortest path from src to any other vertex can have
// at-most |V| - 1 edges
for (int i = 1; i <= V - 1; i++) {
for (int j = 0; j < E; j++) {
int u = graph->edge[j].src;
int v = graph->edge[j].dest;
int weight = graph->edge[j].weight;
if (dist[u] != INT_MAX
&& dist[u] + weight < dist[v])
dist[v] = dist[u] + weight;
}
}
// Step 3: check for negative-weight cycles. The above
// step guarantees shortest distances if graph doesn't
// contain negative weight cycle. If we get a shorter
// path, then there is a cycle.
for (int i = 0; i < E; i++) {
int u = graph->edge[i].src;
int v = graph->edge[i].dest;
int weight = graph->edge[i].weight;
if (dist[u] != INT_MAX
&& dist[u] + weight < dist[v]) {
printf(""Graph contains negative weight cycle"");
return; // If negative cycle is detected, simply
// return
}
}
printArr(dist, V);
return;
}
// Driver's code
int main()
{
/* Let us create the graph given in above example */
int V = 5; // Number of vertices in graph
int E = 8; // Number of edges in graph
struct Graph* graph = createGraph(V, E);
// add edge 0-1 (or A-B in above figure)
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[0].weight = -1;
// add edge 0-2 (or A-C in above figure)
graph->edge[1].src = 0;
graph->edge[1].dest = 2;
graph->edge[1].weight = 4;
// add edge 1-2 (or B-C in above figure)
graph->edge[2].src = 1;
graph->edge[2].dest = 2;
graph->edge[2].weight = 3;
// add edge 1-3 (or B-D in above figure)
graph->edge[3].src = 1;
graph->edge[3].dest = 3;
graph->edge[3].weight = 2;
// add edge 1-4 (or B-E in above figure)
graph->edge[4].src = 1;
graph->edge[4].dest = 4;
graph->edge[4].weight = 2;
// add edge 3-2 (or D-C in above figure)
graph->edge[5].src = 3;
graph->edge[5].dest = 2;
graph->edge[5].weight = 5;
// add edge 3-1 (or D-B in above figure)
graph->edge[6].src = 3;
graph->edge[6].dest = 1;
graph->edge[6].weight = 1;
// add edge 4-3 (or E-D in above figure)
graph->edge[7].src = 4;
graph->edge[7].dest = 3;
graph->edge[7].weight = -3;
// Function call
BellmanFord(graph, 0);
return 0;
}
Output:
Vertex Distance from Source
0 0
1 -1
2 2
3 -2
4 1
"
"In this lecture of our Algorithm module, we will learn about Kosaraju's Algorithm.
An Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
A directed graph is strongly connected if there is a path between all pairs of vertices. A strongly connected component (SCC) of a directed graph is a maximal strongly connected subgraph. For example, there are l 3 SCCs in the following graph.
We can find all strongly connected components in O(V+E) time using Kosaraju’s algorithm. Following is detailed Kosaraju’s algorithm.
1. Create an empty stack ‘S’ and do DFS traversal of a graph. In DFS traversal, after calling recursive DFS for adjacent vertices of a vertex, push the vertex to stack. In the above graph, if we start DFS from vertex 0, we get vertices in stack as 1, 2, 4, 3, 0.
2. Reverse directions of all arcs to obtain the transpose graph.
3. One by one pop a vertex from S while S is not empty. Let the popped vertex be ‘v’. Take v as source and do DFS (call DFSUtil(v)). The DFS starting from v prints strongly connected component of v. In the above example, we process vertices in order 0, 3, 4, 2, 1 (One by one popped from stack).
Following is C++ implementation of Kosaraju’s algorithm.
#include <iostream>
#include <list>
#include <stack>
using namespace std;
class Graph
{
int V; // No. of vertices
list<int> *adj; // An array of adjacency lists
// Fills Stack with vertices (in increasing order of finishing
// times). The top element of stack has the maximum finishing
// time
void fillOrder(int v, bool visited[], stack<int> &Stack);
// A recursive function to print DFS starting from v
void DFSUtil(int v, bool visited[]);
public:
Graph(int V);
void addEdge(int v, int w);
// The main function that finds and prints strongly connected
// components
void printSCCs();
// Function that returns reverse (or transpose) of this graph
Graph getTranspose();
};
Graph::Graph(int V)
{
this->V = V;
adj = new list<int>[V];
}
// A recursive function to print DFS starting from v
void Graph::DFSUtil(int v, bool visited[])
{
// Mark the current node as visited and print it
visited[v] = true;
cout << v << "" "";
// Recur for all the vertices adjacent to this vertex
list<int>::iterator i;
for (i = adj[v].begin(); i != adj[v].end(); ++i)
if (!visited[*i])
DFSUtil(*i, visited);
}
Graph Graph::getTranspose()
{
Graph g(V);
for (int v = 0; v < V; v++)
{
// Recur for all the vertices adjacent to this vertex
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i)
{
g.adj[*i].push_back(v);
}
}
return g;
}
void Graph::addEdge(int v, int w)
{
adj[v].push_back(w); // Add w to v’s list.
}
void Graph::fillOrder(int v, bool visited[], stack<int> &Stack)
{
// Mark the current node as visited and print it
visited[v] = true;
// Recur for all the vertices adjacent to this vertex
list<int>::iterator i;
for(i = adj[v].begin(); i != adj[v].end(); ++i)
if(!visited[*i])
fillOrder(*i, visited, Stack);
// All vertices reachable from v are processed by now, push v
Stack.push(v);
}
// The main function that finds and prints all strongly connected
// components
void Graph::printSCCs()
{
stack<int> Stack;
// Mark all the vertices as not visited (For first DFS)
bool *visited = new bool[V];
for(int i = 0; i < V; i++)
visited[i] = false;
// Fill vertices in stack according to their finishing times
for(int i = 0; i < V; i++)
if(visited[i] == false)
fillOrder(i, visited, Stack);
// Create a reversed graph
Graph gr = getTranspose();
// Mark all the vertices as not visited (For second DFS)
for(int i = 0; i < V; i++)
visited[i] = false;
// Now process all vertices in order defined by Stack
while (Stack.empty() == false)
{
// Pop a vertex from stack
int v = Stack.top();
Stack.pop();
// Print Strongly connected component of the popped vertex
if (visited[v] == false)
{
gr.DFSUtil(v, visited);
cout << endl;
}
}
}
// Driver program to test above functions
int main()
{
// Create a graph given in the above diagram
Graph g(5);
g.addEdge(1, 0);
g.addEdge(0, 2);
g.addEdge(2, 1);
g.addEdge(0, 3);
g.addEdge(3, 4);
cout << ""Following are strongly connected components in ""
""given graph \n"";
g.printSCCs();
return 0;
}
Output
Following are strongly connected components in given graph
0 1 2
3
4
"
"In this lecture of our Algorithm module, we will learn about Articulation Point.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
A vertex in an undirected connected graph is an articulation point (or cut vertex) if removing it (and edges through it) disconnects the graph. Articulation points represent vulnerabilities in a connected network – single points whose failure would split the network into 2 or more components. They are useful for designing reliable networks.
For a disconnected undirected graph, an articulation point is a vertex removing which increases number of connected components.
Following is the implementation of Tarjan’s algorithm for finding articulation points.
#include <bits/stdc++.h>
using namespace std;
// A recursive function that find articulation
// points using DFS traversal
// adj[] --> Adjacency List representation of the graph
// u --> The vertex to be visited next
// visited[] --> keeps track of visited vertices
// disc[] --> Stores discovery times of visited vertices
// low[] -- >> earliest visited vertex (the vertex with minimum
// discovery time) that can be reached from subtree
// rooted with current vertex
// parent --> Stores the parent vertex in DFS tree
// isAP[] --> Stores articulation points
void APUtil(vector<int> adj[], int u, bool visited[],
int disc[], int low[], int& time, int parent,
bool isAP[])
{
// Count of children in DFS Tree
int children = 0;
// Mark the current node as visited
visited[u] = true;
// Initialize discovery time and low value
disc[u] = low[u] = ++time;
// Go through all vertices adjacent to this
for (auto v : adj[u]) {
// If v is not visited yet, then make it a child of u
// in DFS tree and recur for it
if (!visited[v]) {
children++;
APUtil(adj, v, visited, disc, low, time, u, isAP);
// Check if the subtree rooted with v has
// a connection to one of the ancestors of u
low[u] = min(low[u], low[v]);
// If u is not root and low value of one of
// its child is more than discovery value of u.
if (parent != -1 && low[v] >= disc[u])
isAP[u] = true;
}
// Update low value of u for parent function calls.
else if (v != parent)
low[u] = min(low[u], disc[v]);
}
// If u is root of DFS tree and has two or more children.
if (parent == -1 && children > 1)
isAP[u] = true;
}
void AP(vector<int> adj[], int V)
{
int disc[V] = { 0 };
int low[V];
bool visited[V] = { false };
bool isAP[V] = { false };
int time = 0, par = -1;
// Adding this loop so that the
// code works even if we are given
// disconnected graph
for (int u = 0; u < V; u++)
if (!visited[u])
APUtil(adj, u, visited, disc, low,
time, par, isAP);
// Printing the APs
for (int u = 0; u < V; u++)
if (isAP[u] == true)
cout << u << "" "";
}
// Utility function to add an edge
void addEdge(vector<int> adj[], int u, int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
int main()
{
// Create graphs given in above diagrams
cout << ""Articulation points in first graph \n"";
int V = 5;
vector<int> adj1[V];
addEdge(adj1, 1, 0);
addEdge(adj1, 0, 2);
addEdge(adj1, 2, 1);
addEdge(adj1, 0, 3);
addEdge(adj1, 3, 4);
AP(adj1, V);
cout << ""\nArticulation points in second graph \n"";
V = 4;
vector<int> adj2[V];
addEdge(adj2, 0, 1);
addEdge(adj2, 1, 2);
addEdge(adj2, 2, 3);
AP(adj2, V);
cout << ""\nArticulation points in third graph \n"";
V = 7;
vector<int> adj3[V];
addEdge(adj3, 0, 1);
addEdge(adj3, 1, 2);
addEdge(adj3, 2, 0);
addEdge(adj3, 1, 3);
addEdge(adj3, 1, 4);
addEdge(adj3, 1, 6);
addEdge(adj3, 3, 5);
addEdge(adj3, 4, 5);
AP(adj3, V);
return 0;
}
Output
Articulation points in first graph
0 3
Articulation points in second graph
1 2
Articulation points in third graph
1
Time Complexity: The above function is simple DFS with additional arrays. So time complexity is same as DFS which is O(V+E) for adjacency list representation of graph."
"In this lecture of our Algorithm module, we will learn about Maximal Flow Algorithm.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
In optimization theory, maximum flow problems involve finding a feasible flow through a flow network that obtains the maximum possible flow rate.
The maximum flow problem can be seen as a special case of more complex network flow problems, such as the circulation problem. The maximum value of an s-t flow (i.e., flow from source s to sink t) is equal to the minimum capacity of an s-t cut (i.e., cut severing s from t) in the network, as stated in the max-flow min-cut theorem.
The Ford–Fulkerson method or Ford–Fulkerson algorithm (FFA) is a greedy algorithm that computes the maximum flow in a flow network. It is sometimes called a ""method"" instead of an ""algorithm"" as the approach to finding augmenting paths in a residual graph is not fully specified[1] or it is specified in several implementations with different running times"
"In this lecture of our Algorithm module, we will get an overview of Dynamic Programming, Memoization, and Tabulation.
Algorithm is a step-by-step procedure, which defines a set of instructions to be executed in a certain order to get the desired output. Algorithms are generally created independent of underlying languages, i.e. an algorithm can be implemented in more than one programming language.
The Dynamic Programming is one of the different algorithm paradigm. In this approach, the problems can be divided into some sub-problems and it stores the output of some previous subproblems to use them in future. It helps to reduce the computational time for the task.
There are two types of the Dynamic Programming Technique −
- Overlapping Subproblem
- Optimal Substructure
It is mainly an optimization over plain recursion. Wherever we see a recursive solution that has repeated calls for same inputs, we can optimize it using Dynamic Programming. The idea is to simply store the results of subproblems, so that we do not have to re-compute them when needed later. This simple optimization reduces time complexities from exponential to polynomial.
The following are the steps that the dynamic programming follows:
- It breaks down the complex problem into simpler subproblems.
- It finds the optimal solution to these sub-problems.
- It stores the results of subproblems (memoization). The process of storing the results of subproblems is known as memorization.
- It reuses them so that same sub-problem is calculated more than once.
- Finally, calculate the result of the complex problem.
The above five steps are the basic steps for dynamic programming. The dynamic programming is applicable that are having properties such as:
Let's understand dynamic programming through an example.
int fib(int n)
{
if(n<0)
error;
if(n==0)
return 0;
if(n==1)
return 1;
sum = fib(n-1) + fib(n-2);
}
In the above code, we have used the recursive approach to find out the Fibonacci series. When the value of 'n' increases, the function calls will also increase, and computations will also increase. In this case, the time complexity increases exponentially, and it becomes 2n.
Memoization:
Memoization is a common strategy for dynamic programming problems, which are problems where the solution is composed of solutions to the same problem with smaller inputs. It is the top-down approach to solving a problem with dynamic programming.
- It’s called memoization because we will create a memo, or a “note to self”, for the values returned from solving each problem.
- Then, when we encounter the same problem again, we simply check the memo, and, rather than solving the problem a second (or third or fourth) time, we retrieve the solution from our memo.
Top-Down Fibonacci
Here’s our Fibonacci sequence, memoized:
const fibDown = (n, memo=[]) => {
if (n == 0 || n == 1) {
return n;
}
if (memo[n] == undefined) {
memo[n] = fibDown(n - 1, memo) + fibDown(n - 2, memo);
}
return memo[n];
}
Tabulation:
Tabulation is one of the methods used when solving dynamic programming problems. You start by filling up a table and then figure out the solution to the problem based on the result on the table. It is a Bottom-up method. We start solving the problems from the base cases (bottom) and gathering answers to the top.
With bottom-up, or tabulation, we start with the smallest problems and use the returned values to calculate larger values.
We can think of it as entering values in a table, or spreadsheet, and then applying a formula to those values.
Bottom-Up Fibonacci
Here’s our Fibonacci sequence, tabulated:
const fibottomUp = n => {
if (n === 0) {
return 0;
}
let x = 0;
let y = 1;
for (let i = 2; i < n; i++) {
let tmp = x + y;
x = y;
y = tmp;
}
return x + y;
}"
"In this lecture of our Algorithm module, we will learn about Longest Common Subsequence.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
LCS Problem Statement: Given two sequences, find the length of longest subsequence present in both of them. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous. For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”.
The following steps are followed for finding the longest common subsequence.
1. Create a table of dimension n+1*m+1 where n and m are the lengths of X and Y respectively. The first row and the first column are filled with zeros.
2. Fill each cell of the table using the following logic.
3. If the character corresponding to the current row and current column are matching, then fill the current cell by adding one to the diagonal element. Point an arrow to the diagonal cell.
4. Else take the maximum value from the previous column and previous row element for filling the current cell. Point an arrow to the cell with maximum value. If they are equal, point to any of them.
5. Step 2 is repeated until the table is filled.
6. The value in the last row and the last column is the length of the longest common subsequence.
7. In order to find the longest common subsequence, start from the last element and follow the direction of the arrow. The elements corresponding to () symbol form the longest common subsequence.
Longest Common Subsequence Algorithm
X and Y be two given sequences
Initialize a table LCS of dimension X.length * Y.length
X.label = X
Y.label = Y
LCS[0][] = 0
LCS[][0] = 0
Start from LCS[1][1]
Compare X[i] and Y[j]
If X[i] = Y[j]
LCS[i][j] = 1 + LCS[i-1, j-1]
Point an arrow to LCS[i][j]
Else
LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1])
Point an arrow to max(LCS[i-1][j], LCS[i][j-1])
"
"In this lecture of our Algorithm module, we will learn about Longest Palindromic Subsequence.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
The longest palindromic substring or longest symmetric factor problem is the problem of finding a maximum-length contiguous substring of a given string that is also a palindrome.
For example, the longest palindromic substring of ""bananas"" is ""anana"". The longest palindromic substring is not guaranteed to be unique; for example, in the string ""abracadabra"", there is no palindromic substring with length greater than three, but there are two palindromic substrings with length three, namely, ""aca"" and ""ada"".
In some applications it may be necessary to return all maximal palindromic substrings (that is, all substrings that are themselves palindromes and cannot be extended to larger palindromic substrings) rather than returning only one substring or returning the maximum length of a palindromic substring."
"In this lecture of our Algorithm module, we will learn about Longest Increasing Subsequence Problem.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: The Longest Increasing Subsequence (LIS) problem is to find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order. For example, the length of LIS for {10, 22, 9, 33, 21, 50, 41, 60, 80} is 6 and LIS is {10, 22, 33, 50, 60, 80}.
Examples:
Input: arr[] = {3, 10, 2, 1, 20}
Output: Length of LIS = 3
The longest increasing subsequence is 3, 10, 20
Input: arr[] = {3, 2}
Output: Length of LIS = 1
The longest increasing subsequences are {3} and {2}
Input: arr[] = {50, 3, 10, 7, 40, 80}
Output: Length of LIS = 4
The longest increasing subsequence is {3, 7, 40, 80}
We can avoid recomputation of subproblems by using tabulation in Dynamic Programming. Below is the implementation of the above approach:
#include <bits/stdc++.h>
using namespace std;
/* lis() returns the length of the longest
increasing subsequence in arr[] of size n */
int lis(int arr[], int n)
{
int lis[n];
lis[0] = 1;
/* Compute optimized LIS values in
bottom up manner */
for (int i = 1; i < n; i++) {
lis[i] = 1;
for (int j = 0; j < i; j++)
if (arr[i] > arr[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1;
}
// Return maximum value in lis[]
return *max_element(lis, lis + n);
}
/* Driver program to test above function */
int main()
{
int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
int n = sizeof(arr) / sizeof(arr[0]);
printf(""Length of lis is %d\n"", lis(arr, n));
return 0;
}
Output
Length of lis is 5
//Time Complexity: O(n2)
//Auxiliary Space: O(n)
"
"In this lecture of our Algorithm module, we will learn about Longest Increasing Subsequence with nlogn solution.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
The longest increasing subsequence problem is solvable in time O(n\log n) where n denotes the length of the input sequence.
You have to observe the approach used to solve the Longest Increasing Subsequence problem with O(nlogn)."
"In this lecture of our Algorithm module, we will learn about Subset Sum Problem.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: Given a set of non-negative integers, and a value sum, determine if there is a subset of the given set with sum equal to given sum.
Example:
Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 9
Output: True
There is a subset (4, 5) with sum 9.
Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 30
Output: False
There is no subset that add up to 30.
Method 1: Recursion.
Approach: For the recursive approach we will consider two cases.
1. Consider the last element and now the required sum = target sum – value of ‘last’ element and number of elements = total elements – 1
2. Leave the ‘last’ element and now the required sum = target sum and number of elements = total elements – 1
Following is the recursive formula for isSubsetSum() problem.
isSubsetSum(set, n, sum)
= isSubsetSum(set, n-1, sum) ||
isSubsetSum(set, n-1, sum-set[n-1])
Base Cases:
isSubsetSum(set, n, sum) = false, if sum > 0 and n == 0
isSubsetSum(set, n, sum) = true, if sum == 0
Let’s take a look at the simulation of above approach-:
set[]={3, 4, 5, 2}
sum=9
(x, y)= 'x' is the left number of elements,
'y' is the required sum
(4, 9)
{True}
/ \
(3, 6) (3, 9)
/ \ / \
(2, 2) (2, 6) (2, 5) (2, 9)
{True}
/ \
(1, -3) (1, 2)
{False} {True}
/ \
(0, 0) (0, 2)
{True} {False}
Method 2: To solve the problem in Pseudo-polynomial time use the Dynamic programming.
So we will create a 2D array of size (arr.size() + 1) * (target + 1) of type boolean. The state DP[i][j] will be true if there exists a subset of elements from A[0….i] with sum value = ‘j’. The approach for the problem is:
if (A[i-1] > j)
DP[i][j] = DP[i-1][j]
else
DP[i][j] = DP[i-1][j] OR DP[i-1][j-A[i-1]]
1. This means that if current element has value greater than ‘current sum value’ we will copy the answer for previous cases
2. And if the current sum value is greater than the ‘ith’ element we will see if any of previous states have already experienced the sum=’j’ OR any previous states experienced a value ‘j – A[i]’ which will solve our purpose.
The below simulation will clarify the above approach:
set[]={3, 4, 5, 2}
target=6
0 1 2 3 4 5 6
0 T F F F F F F
3 T F F T F F F
4 T F F T T F F
5 T F F T T T F
2 T F T T T T T
"
"In this lecture of our Algorithm module, we will learn about Number Of Ways To Make Coin Change.
A classic dynamic programming strategy works upward by finding the combinations of all smaller values that would sum to the current threshold.[3] Thus, at each threshold, all previous thresholds are potentially considered to work upward to the goal amount W. For this reason, this dynamic programming approach requires a number of steps that is O(nW), where n is the number of types of coins."
"In this lecture of our Algorithm module, we will learn about Matrix Chain Multiplication in DP.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: Given the dimension of a sequence of matrices in an array arr[], where the dimension of the ith matrix is (arr[i-1] * arr[i]), the task is to find the most efficient way to multiply these matrices together such that the total number of element multiplications is minimum.
Follow the below steps to solve the problem:
- Build a matrix dp[][] of size N*N for memoization purposes.
- Use the same recursive call as done in the above approach:
- When we find a range (i, j) for which the value is already calculated, return the minimum value for that range (i.e., dp[i][j]).
- Otherwise, perform the recursive calls as mentioned earlier.
- The value stored at dp[0][N-1] is the required answer.
Below is the implementation of the above approach
#include <bits/stdc++.h>
using namespace std;
int dp[100][100];
// Function for matrix chain multiplication
int matrixChainMemoised(int* p, int i, int j)
{
if (i == j)
{
return 0;
}
if (dp[i][j] != -1)
{
return dp[i][j];
}
dp[i][j] = INT_MAX;
for (int k = i; k < j; k++)
{
dp[i][j] = min(
dp[i][j], matrixChainMemoised(p, i, k)
+ matrixChainMemoised(p, k + 1, j)
+ p[i - 1] * p[k] * p[j]);
}
return dp[i][j];
}
int MatrixChainOrder(int* p, int n)
{
int i = 1, j = n - 1;
return matrixChainMemoised(p, i, j);
}
// Driver Code
int main()
{
int arr[] = { 1, 2, 3, 4 };
int n = sizeof(arr) / sizeof(arr[0]);
memset(dp, -1, sizeof dp);
cout << ""Minimum number of multiplications is ""
<< MatrixChainOrder(arr, n);
}
Output
Minimum number of multiplications is 18
Time Complexity: O(N3 )
Auxiliary Space: O(N2) ignoring recursion stack space
"
"In this lecture of our Algorithm module, we will learn about Optimal Strategy for a Game in DP.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: Consider a row of n coins of values v1 . . . vn, where n is even. We play a game against an opponent by alternating turns. In each turn, a player selects either the first or last coin from the row, removes it from the row permanently, and receives the value of the coin. Determine the maximum possible amount of money we can definitely win if we move first.
Let us understand the problem with few examples:
1. 5, 3, 7, 10 : The user collects maximum value as 15(10 + 5)
2. 8, 15, 3, 7 : The user collects maximum value as 22(7 + 15)
Does choosing the best at each move gives an optimal solution? No.
In the second example, this is how the game can be finished:
1. …….User chooses 8.
2. …….Opponent chooses 15.
3. …….User chooses 7.
4. …….Opponent chooses 3.
5. Total value collected by user is 15(8 + 7)
6. …….User chooses 7.
7. …….Opponent chooses 8.
8. …….User chooses 15.
9. …….Opponent chooses 3.
10. Total value collected by user is 22(7 + 15)
So if the user follows the second game state, the maximum value can be collected although the first move is not the best.
Approach: As both the players are equally strong, both will try to reduce the possibility of winning of each other. Now let’s see how the opponent can achieve this.
The bottom up approach is shown below.
#include <bits/stdc++.h>
using namespace std;
// Returns optimal value possible
// that a player can collect from
// an array of coins of size n.
// Note than n must be even
int optimalStrategyOfGame(int* arr, int n)
{
// Create a table to store
// solutions of subproblems
int table[n][n];
// Fill table using above
// recursive formula. Note
// that the table is filled
// in diagonal fashion (similar
// to http:// goo.gl/PQqoS),
// from diagonal elements to
// table[0][n-1] which is the result.
for (int gap = 0; gap < n; ++gap) {
for (int i = 0, j = gap; j < n; ++i, ++j) {
// Here x is value of F(i+2, j),
// y is F(i+1, j-1) and
// z is F(i, j-2) in above recursive
// formula
int x = ((i + 2) <= j) ? table[i + 2][j] : 0;
int y = ((i + 1) <= (j - 1))
? table[i + 1][j - 1]
: 0;
int z = (i <= (j - 2)) ? table[i][j - 2] : 0;
table[i][j] = max(arr[i] + min(x, y),
arr[j] + min(y, z));
}
}
return table[0][n - 1];
}
// Driver program to test above function
int main()
{
int arr1[] = { 8, 15, 3, 7 };
int n = sizeof(arr1) / sizeof(arr1[0]);
printf(""%d\n"", optimalStrategyOfGame(arr1, n));
int arr2[] = { 2, 2, 2, 2 };
n = sizeof(arr2) / sizeof(arr2[0]);
printf(""%d\n"", optimalStrategyOfGame(arr2, n));
int arr3[] = { 20, 30, 2, 2, 2, 10 };
n = sizeof(arr3) / sizeof(arr3[0]);
printf(""%d\n"", optimalStrategyOfGame(arr3, n));
return 0;
}
Output
22
4
42
Complexity Analysis:
- Time Complexity: O(n2).
- Use of a nested for loop brings the time complexity to n2.
- Auxiliary Space: O(n2).
- As a 2-D table is used for storing states.
"
"In this lecture of our Algorithm module, we will learn about Maximum Cuts Problem in Dynamic Programming.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: Given a rope of length n meters, cut the rope in different parts of integer lengths in a way that maximizes product of lengths of all parts. You must make at least one cut. Assume that the length of rope is more than 2 meters.
Examples:
Input: n = 2
Output: 1 (Maximum obtainable product is 1*1)
Input: n = 3
Output: 2 (Maximum obtainable product is 1*2)
Input: n = 4
Output: 4 (Maximum obtainable product is 2*2)
Input: n = 5
Output: 6 (Maximum obtainable product is 2*3)
Input: n = 10
Output: 36 (Maximum obtainable product is 3*3*4)
Below is the implementation of a Dynamic Programming solution for Max Product Problem
int maxProd(int n)
{
int val[n+1];
val[0] = val[1] = 0;
// Build the table val[] in bottom up manner and return
// the last entry from the table
for (int i = 1; i <= n; i++)
{
int max_val = 0;
for (int j = 1; j <= i; j++)
max_val = max(max_val, (i-j)*j, j*val[i-j]);
val[i] = max_val;
}
return val[n];
}
Time Complexity of the Dynamic Programming solution is O(n^2) and it requires O(n) extra space.
"
"In this lecture of our Algorithm module, we will learn about Minimum Jumps to reach at end Problem in Dynamic programming.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: Given an array of integers where each element represents the max number of steps that can be made forward from that element. Write a function to return the minimum number of jumps to reach the end of the array (starting from the first element). If an element is 0, they cannot move through that element. If the end isn’t reachable, return -1.
Examples:
Input: arr[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}
Output: 3 (1-> 3 -> 9 -> 9)
Explanation: Jump from 1st element
to 2nd element as there is only 1 step,
now there are three options 5, 8 or 9.
If 8 or 9 is chosen then the end node 9
can be reached. So 3 jumps are made.
Input: arr[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Output: 10
Explanation: In every step a jump
is needed so the count of jumps is 10.
Using Dynamic Programming method, we build jumps[] array from right to left such that jumps[i] indicates the minimum number of jumps needed to reach arr[n-1] from arr[i]. Finally, we return jumps[0].
#include <bits/stdc++.h>
using namespace std;
// Returns Minimum number of jumps to reach end
int minJumps(int arr[], int n)
{
// jumps[0] will hold the result
int* jumps = new int[n];
int min;
// Minimum number of jumps needed to reach last element
// from last elements itself is always 0
jumps[n - 1] = 0;
// Start from the second element, move from right to
// left and construct the jumps[] array where jumps[i]
// represents minimum number of jumps needed to reach
// arr[m-1] from arr[i]
for (int i = n - 2; i >= 0; i--) {
// If arr[i] is 0 then arr[n-1] can't be reached
// from here
if (arr[i] == 0)
jumps[i] = INT_MAX;
// If we can directly reach to the end point from
// here then jumps[i] is 1
else if (arr[i] >= n - i - 1)
jumps[i] = 1;
// Otherwise, to find out the minimum number of
// jumps needed to reach arr[n-1], check all the
// points reachable from here and jumps[] value for
// those points
else {
// initialize min value
min = INT_MAX;
// following loop checks with all reachable
// points and takes the minimum
for (int j = i + 1; j < n && j <= arr[i] + i;
j++) {
if (min > jumps[j])
min = jumps[j];
}
// Handle overflow
if (min != INT_MAX)
jumps[i] = min + 1;
else
jumps[i] = min; // or INT_MAX
}
}
return jumps[0];
}
// Driver program to test above function
int main()
{
int arr[] = { 1, 3, 6, 1, 0, 9 };
int size = sizeof(arr) / sizeof(int);
cout << ""Minimum number of jumps to reach""
<< "" end is "" << minJumps(arr, size);
return 0;
}
Output:
Minimum number of jumps to reach end is 3
// Time complexity:O(n^2). Nested traversal of the array is needed.
//Auxiliary Space:O(n). To store the DP array linear space is needed.
"
"In this lecture of our Algorithm module, we will learn about Egg Dropping Puzzle in DP.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Problem Statement: This is a famous puzzle problem. Suppose there is a building with n floors, if we have m eggs, then how can we find the minimum number of drops needed to find a floor from which it is safe to drop an egg without breaking it.
There some important points to remember −
- When an egg does not break from a given floor, then it will not break for any lower floor also.
- If an egg breaks from a given floor, then it will break for all upper floors.
- When an egg breaks, it must be discarded, otherwise we can use it again.
In this approach, we work on the same idea as described above neglecting the case of calculating the answers to sub-problems again and again.. The approach will be to make a table which will store the results of sub-problems so that to solve a sub-problem, it would only require a look-up from the table which will take constant time, which earlier took exponential time.
Formally for filling DP[i][j] state where ‘i’ is the number of eggs and ‘j’ is the number of floors:
- We have to traverse for each floor ‘x’ from ‘1’ to ‘j’ and find minimum of:
(1 + max( DP[i-1][j-1], DP[i][j-x] )).
A Dynamic Programming based for the Egg Dropping Puzzle implementation.
#include <bits/stdc++.h>
using namespace std;
// A utility function to get
// maximum of two integers
int max(int a, int b)
{
return (a > b) ? a : b;
}
/* Function to get minimum
number of trials needed in worst
case with n eggs and k floors */
int eggDrop(int n, int k)
{
/* A 2D table where entry
eggFloor[i][j] will represent
minimum number of trials needed for
i eggs and j floors. */
int eggFloor[n + 1][k + 1];
int res;
int i, j, x;
// We need one trial for one floor and 0
// trials for 0 floors
for (i = 1; i <= n; i++) {
eggFloor[i][1] = 1;
eggFloor[i][0] = 0;
}
// We always need j trials for one egg
// and j floors.
for (j = 1; j <= k; j++)
eggFloor[1][j] = j;
// Fill rest of the entries in table using
// optimal substructure property
for (i = 2; i <= n; i++) {
for (j = 2; j <= k; j++) {
eggFloor[i][j] = INT_MAX;
for (x = 1; x <= j; x++) {
res = 1 + max(
eggFloor[i - 1][x - 1],
eggFloor[i][j - x]);
if (res < eggFloor[i][j])
eggFloor[i][j] = res;
}
}
}
// eggFloor[n][k] holds the result
return eggFloor[n][k];
}
/* Driver program to test to printDups*/
int main()
{
int n = 2, k = 36;
cout << ""\nMinimum number of trials ""
""in worst case with "" << n<< "" eggs and ""<< k<<
"" floors is ""<< eggDrop(n, k);
return 0;
}
Output
Minimum number of trials in worst case with 2 eggs and 36 floors is 8
Complexity Analysis:
- Time Complexity: O(n*k^2).
- Where ‘n’ is the number of eggs and ‘k’ is the number of floors, as we use a nested for loop ‘k^2’ times for each egg
- Auxiliary Space: O(n*k).
- As a 2-D array of size ‘n*k’ is used for storing elements.
"
"In this lecture of our Algorithm module, we will learn about Count Binary Search Trees with n Keys.
Previously we learned that Dynamic Programming (DP) is an algorithmic technique used when solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems.
Total number of possible Binary Search Trees with n different keys (countBST(n)) = Catalan number Cn = (2n)! / ((n + 1)! * n!)
- For n = 0, 1, 2, 3, … values of Catalan numbers are 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, …. So are numbers of Binary Search Trees.
- Total number of possible Binary Trees with n different keys (countBT(n)) = countBST(n) * n!
Below is code for finding count of BSTs and Binary Trees with n numbers.
#include <bits/stdc++.h>
using namespace std;
// A function to find factorial of a given number
unsigned long int factorial(unsigned int n)
{
unsigned long int res = 1;
// Calculate value of [1*(2)*---*(n-k+1)] / [k*(k-1)*---*1]
for (int i = 1; i <= n; ++i)
{
res *= i;
}
return res;
}
unsigned long int binomialCoeff(unsigned int n, unsigned int k)
{
unsigned long int res = 1;
// Since C(n, k) = C(n, n-k)
if (k > n - k)
k = n - k;
// Calculate value of [n*(n-1)*---*(n-k+1)] / [k*(k-1)*---*1]
for (int i = 0; i < k; ++i)
{
res *= (n - i);
res /= (i + 1);
}
return res;
}
// A Binomial coefficient based function to find nth catalan
// number in O(n) time
unsigned long int catalan(unsigned int n)
{
// Calculate value of 2nCn
unsigned long int c = binomialCoeff(2*n, n);
// return 2nCn/(n+1)
return c/(n+1);
}
// A function to count number of BST with n nodes
// using catalan
unsigned long int countBST(unsigned int n)
{
// find nth catalan number
unsigned long int count = catalan(n);
// return nth catalan number
return count;
}
// A function to count number of binary trees with n nodes
unsigned long int countBT(unsigned int n)
{
// find count of BST with n numbers
unsigned long int count = catalan(n);
// return count * n!
return count * factorial(n);
}
// Driver Program to test above functions
int main()
{
int count1,count2, n = 5;
// find count of BST and binary trees with n nodes
count1 = countBST(n);
count2 = countBT(n);
// print count of BST and binary trees with n nodes
cout<<""Count of BST with ""<<n<<"" nodes is ""<<count1<<endl;
cout<<""Count of binary trees with ""<<n<<"" nodes is ""<<count2;
return 0;
}
Output:
Count of BST with 5 nodes is 42
Count of binary trees with 5 nodes is 5040
"
"In this guided Database Management System video lecture, we will get acquainted with fundamental Database concepts and the roadmap for topics we will be learning in the upcoming videos.
Database- A collection of related data which represents some aspect of the real world. A database system is designed to be built and populated with data for a certain task.
DBMS allows users to create their own databases as per their requirement. It includes the user of the database and other application programs since it provides an interface between the data and the software application.
Example -
- The GRADE file stores the grades which students receive in the various sections
- The TUTOR file contains information about each professor.
Popular DBMS Software are:-
- MySQL
- Oracle
- MongoDB
- PostgreSQL
- SQLite
As explained in the video,
- DBMS offers a variety of techniques to store & retrieve data
- DBMS serves as an efficient handler to balance the needs of multiple applications using the same data
- Uniform administration procedures for data
- Application programmers never exposed to details of data representation and storage.
- A DBMS uses various powerful functions to store and retrieve data efficiently.
- Offers Data Integrity and Security
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to keys and their in-depth implementation.
Concepts such as Primary Key, Candidate Key, Super Key and Foreign key along with their use-cases is highlighted for beginners and intermediate level learners.
Keys are an attribute or set of attributes which allows you to find the relation between two tables. Keys help you uniquely identify a row in a table by a combination of one or more columns in that table.
Key types as explained in the video:-
- Super Key – group of single or multiple keys which identifies rows in a table.
- Primary Key – group of columns in a table that uniquely identify every row in that table.
- Candidate Key – set of attributes that uniquely identify tuples in a table. Candidate Key is a super key with no repeated attributes.
- Alternate Key – column in a table that uniquely identify every row in that table.
- Foreign Key – column that creates a relationship between two tables. The purpose of Foreign keys is to maintain data integrity and allow navigation between two different instances of an entity.
This video also covers differences between the key types and provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Entity Relation Diagrams and how they are used to design database image and schema.
ER Diagram stands for Entity Relationship Diagram, which displays the relationship of entity sets stored in a database and explains the logical structure of databases. ER diagrams are created based on three basic concepts: entities, attributes and relationships.
ERD Relationship Model
- It is an easy to use graphical tool for modeling data
- Widely used in Database Design
- It is a GUI representation of the logical structure of a Database
As explained in the video, the notation is:
- Rectangles: represents entity types
- Ellipses: represent attributes
- Diamonds: represents relationship types
- Lines: links attributes to entity types and entity types with other relationship types
- Primary key: attributes are underlined
- Double Ellipses: Represent multi-valued attributes
Every model consists of 3 basic concepts:-
1. Entities - can be place, person, object, event or a concept, which stores data in the database. The characteristics of entities are must have an attribute, and a unique key.
2. Attributes - It is a single-valued property of either an entity-type or a relationship-type. For example, a lecture might have attributes: time, date, duration, place, etc.
3. Relationships - An association among two or more entities. Entities take part in relationships.
As seen in the video, 4 types of cardinal relationships are:
- One-to-One Relationships
- One-to-Many Relationships
- May to One Relationships
- Many-to-Many Relationships
In the final topic for the video, we will understand two entity types:
1. Strong Entity
- Strong entity set always has a primary key.
- It is represented by a rectangle symbol.
- The connecting line of the strong entity set with the relationship is single.
2. Weak Entity
- It does not have enough attributes to build a primary key.
- It is represented by a double rectangle symbol.
- The line connecting the weak entity set for identifying relationship is double.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Normalization, Dependency and Decomposition.
NORMALIZATION
- Process of organizing the data in the database.
- Used to minimize the redundancy from a relation or set of relations. It is also used to eliminate undesirable characteristics like Insertion, Update, and Deletion Anomalies.
- Normalization divides the larger table into smaller and links them using relationships.
- The normal form is used to reduce redundancy from the database table.
The 3 Normalization Forms (detailed videos later):-
- 1NF - A relation is in 1NF if it contains an atomic value.
- 2NF - A relation will be in 2NF if it is in 1NF and all non-key attributes are fully functional dependent on the primary key.
- 3NF - A relation will be in 3NF if it is in 2NF and no transition dependency exists.
Functional Dependency:
It is a relationship that exists between two attributes. It typically exists between the primary key and non-key attribute within a table.
X → Y
Here, X is a determinant, and Y is dependent.
There are two types:
Trivial functional dependency
- A → B has trivial functional dependency if B is a subset of A.
-
Non-trivial functional dependency
- A → B has a non-trivial functional dependency if B is not a subset of A.
Decomposition
- In a database, it breaks the table into multiple tables.
- If the relation has no proper decomposition, then it may lead to problems like loss of information.
- When a relation in the relational model is not in appropriate normal form then the decomposition of a relation is required.
- Used to eliminate some of the problems of bad design like anomalies, inconsistencies, and redundancy.
It is mainly of two types:
Lossless Decomposition:
- If the information is not lost from the relation that is decomposed, then the decomposition will be lossless.
- Guarantees that the join of relations will result in the same relation as it was decomposed.
Dependency Preserving:
- At least one decomposed table must satisfy every dependency.
- If a relation R is decomposed into relation R1 and R2, then the dependencies of R either must be a part of R1 or R2 or must be derivable from the combination of functional dependencies of R1 and R2.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to 1NF and 2NF.
Normalization is the process of minimizing redundancy from a relation or set of relations. Redundancy in relation may cause insertion, deletion, and update anomalies. So, it helps to minimize the redundancy in relations. Normal forms are used to eliminate or reduce redundancy in database tables.
1NF (First Normal Form):
If a relation contain composite or multi-valued attribute, it violates first normal form or a relation is in first normal form if it does not contain any composite or multi-valued attribute. A relation is in first normal form if every attribute in that relation is singled valued attribute.
Example-
ID Name Courses
------------------
1 A c1, c2
2 E c3
3 M C2, c3
In the above table Course is a multi-valued attribute so it is not in 1NF.
Below Table is in 1NF as there is no multi-valued attribute
ID Name Course
------------------
1 A c1
1 A c2
2 E c3
3 M c2
3 M c3
2NF (Second Normal Form):
To be in second normal form, a relation must be in first normal form and relation must not contain any partial dependency. A relation is in 2NF if it has No Partial Dependency, i.e. no non-prime attribute (attributes which are not part of any candidate key) is dependent on any proper subset of any candidate key of the table.
Partial Dependency – If the proper subset of candidate key determines non-prime attribute, it is called partial dependency.
NOT IN 2NF FORM:
STUD_NO COURSE_NO COURSE_FEE
1 C1 1000
2 C2 1500
1 C4 2000
4 C3 1000
4 C1 1000
2 C5 2000
AFTER CONVERTING TO 2NF FORM:
Table 1
STUD_NO COURSE_NO
1 C1
2 C2
1 C4
4 C3
4 C1
Table 2
COURSE_NO COURSE_FEE
C1 1000
C2 1500
C3 1000
C4 2000
C5 2000
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Indexes and their types.
Indexing is a technique that uses data structures to optimize the searching time of a database query. It helps in faster query results and quick data retrieval from the database. Indexing makes database performance better. It also consumes lesser space in the main memory.
Index usually consists of two columns which are a key-value pair. The two columns of the index table(i.e., the key-value pair) contain copies of selected columns of the tabular data of the database.
Why Use Indexes?
- Indexing helps in faster query results or quick data retrieval.
- Indexing helps in faster sorting and grouping of records
- Some Indexing uses sorted and unique keys which helps to retrieve sorted queries even faster.
- Index tables are smaller in size so require lesser memory and hence stored in main memory.
- Indexing helps in better CPU utilization and better performance.
Three types of Indexes as studied in the video are:-
1) Clustered Index
Clustered Indexing is used when there are multiple related records found at one place. It is defined on ordered data. The important thing to note here is that the index table of clustered indexing is created using non-key values which may or may not be unique. To achieve faster retrieval, we group columns having similar characteristics. The indexes are created using these groups and this process is known as Clustering Index.
Some properties:-
- Search Keys are non-key values, sorted and non nullable
- Search Keys may or may not be unique.
- Requires extra work to create indexing
2) Non-Clustered Index
It is a two-level indexing technique used to reduce the mapping size of the primary index. The secondary index points to a certain location where the data is to be found but the actual data is not sorted like in the primary indexing.
- Search Keys are Candidate Keys. They are sorted but actual data may or may not be sorted.
- Requires more time than primary indexing.
- Search Keys cannot be null.
- Faster than clustered indexing but slower than primary indexing.
3) Multi-level Indexing
Since the index table is stored in the main memory, single-level indexing for a huge amount of data requires a lot of memory space. Hence, multilevel indexing was introduced in which we divide the main data block into smaller blocks. This makes the outer block of the index table small enough to be stored in the main memory.
We use the B+ Tree data structure for multilevel indexing. The leaf nodes of the B+ tree contain the actual data pointers.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to B & B+ Trees.
B tree is an m-way tree that self-balances. Because of their balanced nature, such trees are widely used to organize and manage massive datasets and to facilitate searches.
Binary trees have a maximum of 2 children, whereas a Multiway Search Tree, also known as an M-way tree, has a maximum of m children where m>2. M-way trees are primarily used in external searching, i.e. retrieving data from secondary storage such as disc files, due to their structure.
Structure of a B Tree
A B-tree of order m is an m-way search tree that is either empty or satisfies the following characteristics:
- All leaf nodes are at the same level.
- All non-leaf nodes (except the root node) have at least [m/2] children.
- All nodes (except the root node) should have at least [m/2]-1 keys.
- If the root node is a leaf node (only node in the tree), then it will have no children and will have at least one key. If the root is a non leaf node, then it will have at least 3 children and at least one key.
- A non leaf node with n-1 key values should have n non NULL children.
B+ Tree
It is simply an improved version of the B tree that allows for efficient and smooth insertion, deletion, and sequential access. It helps to mitigate the disadvantages of the B-tree by saving data pointers at the leaf node level and simply storing key values in internal nodes.
The B+ tree is also known as an advanced self-balanced tree since every path from the tree's root to its leaf is the same length. The fact that all of the leaf nodes are the same length indicates that they all occur at the same level. It is not possible for some leaf nodes to appear at the third level while others appear at the second level.
A B+ tree is the same as a B tree; the only difference is that the B+ tree has an additional level with linked leaves at the bottom. In addition, unlike the B tree, each node in a B+ tree contains only keys rather than key-value pairs.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to ACID properties and transactions.
A transaction is a logical unit of work that accesses and updates the contents of a database. Read and write operations are used by transactions to access data.
- Atomicity: It refers to the fact that the data is kept atomic. It means that if any operation on the data is conducted, it should either be executed completely, or it not at all. It also implies that the operation should not be interrupted or just half completed. When performing operations on a transaction, the operation should be completed totally rather than partially. If any of the operations aren’t completed fully, the transaction gets aborted. Sometimes, a current operation will be running and then, an operation with a higher priority enters. This discontinues the current operation and the current operation will be aborted. Atomicity is often referred to as the ‘all or nothing’ rule.
- Consistency: This means that integrity constraints must be maintained so that the database is consistent before and after the transaction. It refers to the correctness of a database. Referring to the example above, The total amount before and after the transaction must be maintained. Example -
Total before T occurs = 500 + 200 = 700.
Total after T occurs = 400 + 300 = 700.
Therefore, the database is consistent. Inconsistency occurs in case T1 completes but T2 fails. As a result, T is incomplete.
- Isolation: Isolation is defined as a state of separation i.e no data from one database should impact the other and where many transactions can take place at the same time. When the operation on the first state of the database is finished, the process on the second state of the database should begin. It indicates that if two actions are conducted on two different databases, the value of one database may not be affected by the value of the other. When two or more transactions occur at the same time in the case of transactions, consistency should be maintained. Any modifications made in one transaction will not be visible to other transactions until the change is committed to the memory.
- Durability: It refers to the fact that if an operation is completed successfully, the database remains permanent in the disk. The database’s durability should be such that even if the system fails or crashes, the database will survive. However, if the database is lost, the recovery manager is responsible for guaranteeing the database’s long-term viability. Every time we make a change, we must use the COMMIT command to commit the values.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Serializability and its two types.
Serializability is related to schedules and transactions. Schedules are a series of operations performing one transaction to the other.
Serializability of schedules ensures that a non-serial schedule is equivalent to a serial schedule. It helps in maintaining the transactions to execute simultaneously without interleaving one another. It is a way to check if the execution of two or more transactions are maintaining the database consistency or not.
R(X) means Reading the value: X; and W(X) means Writing the value: X.
Serializability of any non-serial schedule can be verified using two types mainly: Conflict Serializability and View Serializability.
1. View Serializability:
If a non-serial schedule is view equivalent to some other serial schedule then the schedule is called View Serializable Schedule. It is needed to ensure the consistency of a schedule.
The two conditions needed by schedules(S1 and S2) to be view equivalent are:
Initial read must be on the same piece of data.
Example: If transaction t1 is reading ""A"" from database in schedule S1, then in schedule S2, t1 must read A.
Final write must be on the same piece of data.
Example: If a transaction t1 updated A at last in S1, then in S2, t1 should perform final write as well.
The mid sequence should also be in the same order.
Example: If t1 is reading A which is updated by t2 in S1, then in
S2, t1 should read A which should be updated by t2.
This process of checking view equivalency of a schedule is called View Serializability.
2. Conflict Serializability:
A non-serial schedule is a conflict serializable if, after performing some swapping on the non-conflicting operation results in a serial schedule. It is checked using the non-serial schedule and an equivalent serial schedule. This process of checking is called Conflict Serializability.
It is tedious to use if we have many operations and transactions as it requires a lot of swapping.
For checking, we will use the same Precedence Graph technique discussed in the video. First, we will check conflicting pairs operations(read-write, write-read, and write-write) and then form directed edges between those conflicting pair transactions. If we can find a loop in the graph, then the schedule is non-conflicting serializable otherwise it is surely a conflicting serializable schedule.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Recoverability Techniques.
In real world applications there are multiple transactions that are happening parallelly. Some of these transactions are dependent on each other while some are independent. If one transaction fails in the dependent transactions it should not affect other undergoing transactions. To counter this, we discuss Recoverability in the video.
RECOVERABLE SCHEDULE
Schedules in which transactions commit only after all transactions whose changes they read commit are called recoverable schedules.
If some transaction Tj is reading value updated or written by some other transaction Ti, then the commit of Tj must occur after the commit of Ti.
Example 1:S1: R1(x), W1(x), R2(x), R1(y), R2(y),
W2(x), W1(y), C1, C2;
Given schedule follows order of Ti->Tj => C1->C2.
Transaction T1 is executed before T2 hence there is no chances of conflict occur. R1(x) appears before W1(x) and transaction T1 is committed before T2 i.e. completion of first transaction performed first update on data item x, hence given schedule is recoverable.
CASCADELESS SCHEDULE
When no read or write-write occurs before execution of transaction then corresponding schedule is called cascadeless schedule.
Example –
S3: R1(x), R2(z), R3(x), R1(z), R2(y), R3(y), W1(x), C1,
W2(z), W3(y), W2(y), C3, C2;
In this schedule W3(y) and W2(y) overwrite conflicts and there is no read, therefore given schedule is cascadeless schedule.
STRICT SCHEDULE
if schedule contains no read or write before commit then it is known as strict schedule. Strict schedule is strict in nature.
Example –
S4: R1(x), R2(x), R1(z), R3(x), R3(y),
W1(x), C1, W3(y), C3, R2(y), W2(z), W2(y), C2;
In this schedule no read-write or write-write conflict arises before commit hence its strict schedule.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Timestamp Ordering Protocol.
1. The Timestamp Ordering Protocol is used to order the transactions based on their timestamps. The order of transactions is nothing but the ascending order of the transaction creation.
2. The priority of the older transaction is higher; that's why it executes first. This protocol uses system time or a logical counter to determine the timestamp of the transaction.
3. The lock-based protocol is used to manage the order between conflicting pairs of transactions at the execution time. But timestamp-based protocols start working as soon as a transaction is created.
4. In the video we see an example similar to the following - two transactions T1 and T2. Suppose transaction T1 has entered the system 007 times and transaction T2 has entered the system 009 times. T1 has the higher priority, so it executes first as it entered the system first.
5. The timestamp ordering protocol also maintains the timestamp of the last 'read' and 'write' operation on a piece of data.
1. Check the following condition whenever a transaction Ti issues a Read (X) operation:
- If W_TS(X) >TS(Ti) then the operation is rejected.
- If W_TS(X) <= TS(Ti) then the operation is executed.
- Timestamps of all the data items are updated.
2. Check the following condition whenever a transaction Ti issues a Write(X) operation:
- If TS(Ti) < R_TS(X) then the operation is rejected.
- If TS(Ti) < W_TS(X) then the operation is rejected and Ti is rolled back otherwise the operation is executed.
Here,
TS(TI) denotes the timestamp of the transaction Ti.
R_TS(X) denotes the Read time-stamp of data-item X.
W_TS(X) denotes the Write time-stamp of data-item X.
Finally, we learn about the advantages in the video that timestamp including serializability like:
- TS protocol ensures freedom from deadlock; that means no transaction ever waits.
- But the schedule may not be recoverable and may not even be cascade- free.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this guided Database Management System video lecture, we will understand some vital DBMS concepts related to Timestamp Ordering Protocol.
The database should be locked and unlocked in a way that prevents starvation, deadlock, and inconsistency.
2PL locking protocol
Every transaction will lock and unlock the data item in two different phases.
- Growing Phase − All the locks are issued in this phase. No locks are released, after all changes to data-items are committed and then the second phase (shrinking phase) starts.
- Shrinking phase − No locks are issued in this phase, all the changes to data-items are noted (stored) and then locks are released.
In the growing phase transaction reaches a point where all the locks it may need has been acquired. This point is called LOCK POINT.
After the lock point has been reached, the transaction enters a shrinking phase.
Two phase locking is of two types:
1. Strict two phase locking protocol: A transaction can release a shared lock after the lock point, but it cannot release any exclusive lock until the transaction commits. This protocol creates a cascade less schedule. Cascading schedule: In this schedule, one transaction is dependent on another transaction. So if one has to rollback, then the other has to rollback.
2. Rigorous two phase locking protocolA transaction cannot release any lock either shared or exclusive until it commits. The 2PL protocol guarantees serializability, but cannot guarantee that deadlock will not happen.
Example:
Let T1 and T2 are two transactions.
T1=A+B and T2=B+A
T1 T2
Lock-X(A) Lock-X(B)
Read A; Read B;
Lock-X(B) Lock-X(A)
Here,
Lock-X(B) : Cannot execute Lock-X(B) since B is locked by T2.
Lock-X(A) : Cannot execute Lock-X(A) since A is locked by T1.
In the above situation T1 waits for B and T2 waits for A. The waiting time never ends. At least one of the transactions cannot proceed further unless the lock is released voluntarily. This situation is called a ""deadlock.""
Wait for Graph:
It is used in the deadlock detection method, creating a node for each transaction, creating an edge Ti to Tj, if Ti is waiting to lock an item locked by Tj. A cycle in WFG indicates a deadlock has occurred. WFG is created at regular intervals.
This video provides practical tips and hints with a complete explanation and plenty of examples that will lead to a solid understanding of designing databases and tables."
"In this Operating System lecture, you will learn about elementary concepts related to OS and get an overview of the topics that will be studied in the following lectures.
An Operating System (OS) is an interface between a computer user and computer hardware. An operating system is a software which performs all the basic tasks like file management, memory management, process management, handling input and output, and controlling peripheral devices such as disk drives and printers.
Examples of OS include Android, Windows, Linux, Unix and Wear OS.
Major Functionalities of Operating System:
- Resource Management: Its responsibility is to provide hardware and manage system resources. It decreases the load in the system.
- Process Management: It includes various tasks like scheduling, termination of the process. OS manages various tasks at a time.
- Storage Management: The file system mechanism used for the management of the storage. All the data stores in various tracks of Hard disks that all managed by the storage manager.
- Memory Management: Refers to the management of primary memory. The operating system has to keep track, how much memory has been used and by whom. OS also has to allocate and deallocate the memory space.
- Security/Privacy Management: Privacy is also provided by the Operating system by means of passwords so that unauthorized applications can’t access programs or data.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding.
"
"In this Operating System lecture, you will learn about types of Operating System.
As explained in the video, OS falls into various categories.
- Batch Operating System- Sequence of jobs in a program on a computer without manual interventions.
- Multi-tasking OS - allows many users to share the computer resources. (Max utilization of the resources).
- Multi-programming OS - Manages a group of different computers and makes appear to be a single computer.
- Network operating system- computers running in different operating systems can participate in a common network (It is used for security purposes).
- Real-time operating system – meant applications to fix the deadlines.
- Spooling based Systems
Examples of Operating System are –
- Windows (GUI based, PC)
- GNU/Linux (Personal, Workstations, ISP, File and print server,)
- macOS (Macintosh), used for Apple’s personal computers and workstations (MacBook, iMac).
- Android (Google’s Operating System for smartphones/tablets/smartwatches)
- iOS (Apple’s OS for iPhone, iPad, and iPod Touch)
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about services provided by the Operating System to the user.
1. Program Execution & Creation - The OS is responsible for the execution of all types of programs whether it be user programs or system programs. It utilizes various resources available for the efficient running of all types of functionalities.
2. I/O Control - i.e, from the keyboard, mouse, desktop, etc. The Operating System does all interfacing in the most appropriate manner regarding all kinds of Inputs and Outputs. For example, there is a difference in the nature of all types of peripheral devices such as mice or keyboards, the Operating System is responsible for handling data between them.
3. Accounting - It tracks an account of all the functionalities taking place in the computer system at a time. All the details such as the types of errors that occurred are recorded.
4. Security - Using password protection to protect user data and similar other techniques. it also prevents unauthorized access to programs and user data.
5. Error Detection and Response - It is responsible for the detection of any type of error or bugs that can occur while any task. The well-secured OS sometimes also acts as a countermeasure for preventing any sort of breach to the Computer System from any external source and probably handling them.
6. File Management - Making decisions regarding the storage of all types of data or files, i.e, floppy disk/hard disk/pen drive, etc. The Operating System decides how the data should be manipulated and stored.
7. Communication - Coordinate and assign interpreters, compilers, assemblers, and other software to the various users of the computer systems.
8. Resource Allocation - Ensures the proper use of all the resources available by deciding which resource to be used by whom for how much time. All the decisions are taken by the Operating System.
9. Provisioning GUI - acts as a communication bridge (interface) between the user and the computer hardware by providing interactive interface like icons, cursors and editors.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about properties of Operating System.
We discuss the following properties in the video:-
Batch Processing - It is a technique in which an Operating System collects the programs and data together in a batch before processing starts. An operating system does the following activities related to batch processing −
- The OS defines a job which has predefined sequence of commands, programs and data as a single unit.
- The OS keeps a number a jobs in memory and executes them without any manual information.
Multitasking - It’s a logical extension of the multiprogramming system that allows numerous applications to run simultaneously. Multitasking in an OS enables a user to execute multiple computer tasks at the same time. Processes that hold common processing resources, such as a CPU, are known as many tasks. The operating system remembers where you are in these jobs and lets you switch between them without data being lost.
Multiprogramming - If a program has to wait for an I/O operation, other programs utilize the CPU in the meantime. These operating systems form an important and popular class of operating systems. Some examples are Linux distributions, Windows, IOS, etc. This article is an attempt to explore multiprogramming operating systems.
Real Time - These operating systems guarantee that critical tasks be completed within a range of time. For example, a robot is hired to weld a car body. If the robot welds too early or too late, the car cannot be sold, so it is a hard real-time system that requires complete car welding by robot hardly on the time.
Distributed - It is a system software over a collection of independent software, networked, communicating, and physically separate computational nodes. They handle jobs which are serviced by multiple CPUs. Each individual node holds a specific software subset of the global aggregate operating system.
Spooling - Spooling is a process in which data is temporarily held to be used and executed by a device, program, or system. Data is sent to and stored in memory or other volatile storage until the program or computer requests it for execution. SPOOL is an acronym for simultaneous peripheral operations online.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about processes, PCB and process management techniques Operating System.
A process is defined as an entity which represents the basic unit of work to be implemented in the system.
As explained in the video, we write our computer programs in a text file and when we execute this program, it becomes a process which performs all the tasks mentioned in the program.
When a program is loaded into the memory and it becomes a process, it can be divided into four sections ─ stack, heap, text and data.
- Start - This is the initial state when a process is first started/created.
- Ready - The process is waiting to be assigned to a processor. Ready processes are waiting to have the processor allocated to them by the operating system so that they can run. Process may come into this state after Start state or while running it by but interrupted by the scheduler to assign CPU to some other process.
- Running - Once the process has been assigned to a processor by the OS scheduler, the process state is set to running and the processor executes its instructions.
- Waiting - Process moves into the waiting state if it needs to wait for a resource, such as waiting for user input, or waiting for a file to become available.
- Terminated or Exit - Once the process finishes its execution, or it is terminated by the operating system, it is moved to the terminated state where it waits to be removed from main memory.
For the final topic discussed in the video, we will shift our focus to PCB. Process Control Block is a data structure that contains information of the process related to it. The process control block is also known as a task control block, entry of the process table, etc.
It is very important for process management as the data structuring for processes is done in terms of the PCB. It also defines the current state of the operating system. It includes the following:-
- Process State
- Process Number
- Program Counter
- Registers
- List of Open Files
- CPU Scheduling Information
- Memory Management Information
- I/O Status Information
- Accounting information
- Location of the Process Control Block
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about several process scheduling types used to schedule processes in an Operating System.
Definition:- The process scheduling is the activity of the process manager that handles the removal of the running process from the CPU and the selection of another process on the basis of a particular strategy.
Long Term Scheduler
- It is also called a job scheduler. A long-term scheduler determines which programs are admitted to the system for processing. It selects processes from the queue and loads them into memory for execution. Process loads into the memory for CPU scheduling.
- It provides a balanced mix of jobs, such as I/O bound and processor bound. It also controls the degree of multiprogramming.
- If the degree of multiprogramming is stable, then the average rate of process creation must be equal to the average departure rate of processes leaving the system.
Short Term Scheduler
- It is also called as CPU scheduler. Its main objective is to increase system performance in accordance with the chosen set of criteria.
- It is the change of ready state to running state of the process. CPU scheduler selects a process among the processes that are ready to execute and allocates CPU to one of them.
- Short-term schedulers, also known as dispatchers, make the decision of which process to execute next. Short-term schedulers are faster than long-term schedulers.
Medium Term Scheduler
- Medium-term scheduling is a part of swapping. It removes the processes from the memory. It reduces the degree of multiprogramming.
- The medium-term scheduler is in-charge of handling the swapped out-processes.
- A running process may become suspended if it makes an I/O request. A suspended processes cannot make any progress towards completion. In this condition, to remove the process from memory and make space for other processes, the suspended process is moved to the secondary storage.
- This process is called swapping, and the process is said to be swapped out or rolled out. Swapping may be necessary to improve the process mix.
Next, we understand two types of scheduling:-
1. Preemptive scheduling is used when a process switches from running state to ready state or from the waiting state to ready state. The resources (mainly CPU cycles) are allocated to the process for a limited amount of time and then taken away, and the process is again placed back in the ready queue if that process still has CPU burst time remaining.
2. Non-Preemptive Scheduling is used when a process terminates, or a process switches from running to the waiting state. In this scheduling, once the resources (CPU cycles) are allocated to a process, the process holds the CPU till it gets terminated or reaches a waiting state.
In the final topic we study the types of process queues:-
- Job Queue
In starting, all the processes get stored in the job queue. It is maintained in the secondary memory. The long term scheduler (Job scheduler) picks some of the jobs and put them in the primary memory.
- Ready Queue
Ready queue is maintained in primary memory. The short term scheduler picks the job from the ready queue and dispatch to the CPU for the execution.
- Waiting Queue
When the process needs some IO operation in order to complete its execution, OS changes the state of the process from running to waiting. The context (PCB) associated with the process gets stored on the waiting queue which will be used by the Processor when the process finishes the IO.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about several process scheduling algorithms used to schedule processes in an Operating System.
The most common scheduling algorithms, including First Come First Serve (FCFS), Shortest Job First (SJF), Shortest Remaining Time First (SRTF), and (RR) Round Robin, are discussed in this video .We'll discuss the operation of these algorithms as well as their benefits and drawbacks. The detailed explanation of the scheduling algorithms is the main focus.
A CPU scheduling algorithm is used to determine which process will use CPU for execution and which processes to hold or remove from execution. The main goal or objective of CPU scheduling algorithms is to make sure that the CPU is never in an idle state, meaning that the OS has at least one of the processes ready for execution among the available processes in the ready queue.
Preemptive Scheduling Algorithms:
In these algorithms, processes are assigned with a priority. Whenever a high-priority process comes in, the lower-priority process which has occupied the CPU is preempted. That is, it releases the CPU, and the high-priority process takes the CPU for its execution.
Non-Preemptive Scheduling Algorithms:
In these algorithms, we cannot preempt the process. That is, once a process is running in CPU, it will release it either by context switching or terminating. Often, these are the types of algorithms that can be used because of the limitation of the hardware.
The Types:
1. First Come First Serve is the easiest and simplest CPU scheduling algorithm to implement. In this type of scheduling algorithm, the CPU is first allocated to the process which requests the CPU first. That means the process with minimal arrival time will be executed first by the CPU. It is a non-preemptive scheduling algorithm as the priority of processes does not matter, and they are executed in the manner they arrive in front of the CPU. This scheduling algorithm is implemented with a FIFO(First In First Out) queue.
2. Shortest Job First is a non-preemptive scheduling algorithm in which the process with the shortest burst or completion time is executed first by the CPU. That means the lesser the execution time, the sooner the process will get the CPU. In this scheduling algorithm, the arrival time of the processes must be the same, and the processor must be aware of the burst time of all the processes in advance. If two processes have the same burst time, then First Come First Serve (FCFS) scheduling is used to break the tie.
3. The Round Robin algorithm is related to the First Come First Serve (FCFS) technique but implemented using a preemptive policy. In this scheduling algorithm, processes are executed cyclically, and each process is allocated a small amount of time called time slice or time quantum. The ready queue of the processes is implemented using the circular queue technique in which the CPU is allocated to each process for the given time quantum and then added back to the ready queue to wait for its next turn. If the process completes its execution within the given quantum of time, then it will be preempted, and other processes will execute for the given period of time. But if the process is not completely executed within the given time quantum, then it will again be added to the ready queue and will wait for its turn to complete its execution.
4. Shortest Remaining Time First (SRTF) scheduling algorithm is basically a preemptive mode of the Shortest Job First (SJF) algorithm in which jobs are scheduled according to the shortest remaining time. In this scheduling technique, the process with the shortest burst time is executed first by the CPU, but the arrival time of all processes need not be the same. If another process with the shortest burst time arrives, then the current process will be preempted, and a newer ready job will be executed first.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about the concept of Multithreading in Operating System.
As seen in the video, a thread is a flow of execution through a process's code. It has its own programme counter, which keeps track of which instruction to execute next, system registers, which hold its current working variables, and a stack, which holds the history of previous executions. Few pieces of information, such as code segments, data segments, and open files, are shared by a thread with its peer threads. All other threads are informed when one thread modifies a memory item within a code segment. Another name for a thread is a lightweight process. Through parallelism, threads offer a way to enhance application performance.
No thread can exist outside of a process, and each thread is a part of only one specific process. A different control flow is represented by each thread. Network servers and web servers have both been implemented successfully using threads. They also offer a suitable framework for shared memory multiprocessors to run applications in parallel.
- Threads minimize the context switching time.
- Use of threads provides concurrency within a process.
- Efficient communication.
- It is more economical to create and context switch threads.
- Threads allow utilization of multiprocessor architectures to a greater scale and efficiency.
Types of Thread:
Threads are implemented in following two ways −
- User Level Threads − User managed threads.
- Kernel Level Threads − Operating System managed threads acting on kernel, an operating system core.
There are three common ways of establishing this relationship.
1. Many-to-One Model
2. One-to-One Model
3. Many-to-Many Model
Many-to-One Model
As seen in the video there is many to one relationship between threads. Here, multiple user threads are associated or mapped with one kernel thread. The thread management is done on the user level so it is more efficient.
One-to-One Model
As seen in the video, we can understand the one user thread to mapped to one kernel thread.
Many-to-Many Model
As seen in the video, we can understand the many user threads to mapped to a smaller or equal number of kernel threads. The number of kernel threads is specific to a particular application or machine.
Benefits:
1. Resource sharing: As the threads can share the memory and resources of any process it allows any application to perform multiple activities inside the same address space.
2. Utilization of Multiple Processor Architecture: The different threads can run parallel on the multiple processors hence, this enables the utilization of the processor to a large extent and efficiency.
3. Reduced Context Switching Time: The threads minimize the context switching time as in Thread Context Switching, the virtual memory space remains the same.
4. Economical: The allocation of memory and resources during process creation comes with a cost. As the threads can distribute resources of the process it is more economical to create context-switch threads.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about Memory Management techniques and terminologies in an Operating System.
Main Memory is a repository of rapidly available information shared by the CPU and I/O devices. Main memory is associated with the processor, so moving instructions and information into and out of the processor is extremely fast. A power interruption occurs and the RAM loses its data.
In the video, we understand the concept of logical address space and Physical address space:
Logical and Physical Address Space:
An address seen by the memory unit (i.e the one loaded into the memory address register of the memory) is commonly known as a ""Physical Address"".
Logical address space can be defined as the size of the process. The set of all physical addresses corresponding to these logical addresses is known as Physical address space.
Static and Dynamic Loading:
To load a process into the main memory is done by a loader. There are two different types of loading :
- Static loading:- loading the entire program into a fixed address. It requires more memory space.
- Dynamic loading:- The size of a process is limited to the size of physical memory. To gain proper memory utilization, dynamic loading is used. In dynamic loading, a routine is not loaded until it is called. This loading is useful when a large amount of code is needed to handle efficiently.
Static and Dynamic linking:
To perform a linking task a linker is used. A linker is a program that takes one or more object files generated by a compiler and combines them into a single executable file.
- Static linking: In static linking, the linker combines all necessary program modules into a single executable program. So there is no runtime dependency. Some operating systems support only static linking, in which system language libraries are treated like any other object module.
- Dynamic linking: The basic concept of dynamic linking is similar to dynamic loading. In dynamic linking, ""Stub"" is included for each appropriate library routine reference. A stub is a small piece of code. When the stub is executed, it checks whether the needed routine is already in memory or not. If not available then the program loads the routine into memory.
Swapping:
1. A process must have been in memory before it was executed. Swapping is the process of temporarily moving a process from the main memory into secondary memory, which is quicker than secondary memory.
2. More processes can run and fit into memory at once thanks to swapping. Transferring time makes up the majority of swapping, and the amount of memory swapped directly relates to the total time.
3. Swapping is also referred to as ""roll-out, roll-in"" because the memory manager can swap out the lower priority process and then load and run the higher priority process if one with a higher priority arrives and requests assistance.
4. The lower priority process switched back into memory after completing the higher priority work and continued.
Contiguous Memory Allocation:
The main memory is used by both the operating system and the different client processes. We normally need several user processes to reside in memory simultaneously. In adjacent memory allotment, each process is contained in a single contiguous segment of memory. This means that we need to consider how to allocate available memory.
Paging:
It is a memory management scheme that eliminates the need for contiguous allocation of physical memory. This scheme permits the physical address space of a process to be non-contiguous.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about Virtual Memory in an Operating System.
- Virtual Memory is a storage mechanism which offers user an illusion of having a very big main memory.
- Virtual memory is needed whenever your computer doesn’t have space in the physical memory
- A demand paging mechanism is very much similar to a paging system with swapping where processes stored in the secondary memory and pages are loaded only on demand, not in advance.
- Important Page replacement methods are 1) FIFO 2) Optimal Algorithm 3) LRU Page Replacement.
- In FIFO (First-in-first-out) method, memory selects the page for a replacement that has been in the virtual address of the memory for the longest time.
- The optimal page replacement method selects that page for a replacement for which the time to the next reference is the longest.
- LRU method helps OS to find page usage over a short period of time.
- Virtual memory helps to gain speed when only a particular segment of the program is required for the execution of the program.
- Applications may run slower if the system is using virtual memory.
Types of Page Replacement Methods:
As discussed in the video. some important Page replacement methods are:
FIFO Page Replacement:
FIFO (First-in-first-out) is a simple implementation method. In this method, memory selects the page for a replacement that has been in the virtual address of the memory for the longest time.
- Whenever a new page loaded, the page recently comes in the memory is removed. So, it is easy to decide which page requires to be removed as its identification number is always at the FIFO stack.
- The oldest page in the main memory is one that should be selected for replacement first.
Optimal Algorithm:
The optimal page replacement method selects that page for a replacement for which the time to the next reference is the longest.
- Optimal algorithm results in the fewest number of page faults. This algorithm is difficult to implement.
- An optimal page-replacement algorithm method has the lowest page-fault rate of all algorithms. This algorithm exists and which should be called MIN or OPT.
- Replace the page which unlike to use for a longer period of time. It only uses the time when a page needs to be used.
LRU Page Replacement:
The full form of LRU is the Least Recently Used page. This method helps OS to find page usage over a short period of time. This algorithm should be implemented by associating a counter with an even- page.
- Page, which has not been used for the longest time in the main memory, is the one that will be selected for replacement.
- Easy to implement, keep a list, replace pages by looking back into time.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about I/O Hardware in an Operating System.
Handling various I/O devices, such as a mouse, keyboard, touchpad, disc drives, monitor adapters, USB devices, bit-mapped screens, LEDs, analog-to-digital converters, on/off switches, connections, audio I/O, printers, etc., is a crucial function of an operating system. An I/O system is required to send an application's I/O request to the physical device, receive any response from the device, and then send that response back to the application.
I/O devices can be split into two groups:
1. Block devices − One block device the driver communicates with by sending whole blocks of data. Hard disk, USB cameras, disk-on-key, etc., for example.
2. Character devices − A character system is one that communicates with the driver by sending and receiving single characters (bytes, octets). Such as serial ports, parallel ports, sound cards, etc.
Device Controllers:
Device drivers are software modules that can be plugged into an operating system to manage a specified device. Operating System is supported by device drivers in managing all I/O devices.
The Application Controller functions like a system-driver interface. Usually, I/O devices (keyboard, mouse, printer, etc.) consist of a mechanical component and an electronic component where the system controller is considered an electronic component.
Synchronous vs asynchronous I/O:
1. Synchronous I/O − CPU execution waits in this scheme as I/O goes
2. Asynchronous I/O − I/O runs at the same time as CPU execution
Direct Memory Access (DMA):
Direct Memory Access (DMA) means that CPU grants authority to read from or write to memory without involvement in the I / O module. DMA module manages data sharing between main memory and I / O device itself. Only at the beginning and end of the transfer is CPU involved and interrupted after the whole block has been moved.
Polling I/O:
Polling is the easiest way to communicate with the processor via an I/O device. The process of checking the device's status periodically to see if it is time for the next I / O task, is called polling. The I / O system simply puts the information in a status log, and the processor has to come in and get the details.
Interrupts I/O:
The interrupt-driven approach is an alternative scheme for handling I/O. An interrupt is a signal coming from a device requiring attention to the microprocessor. A system controller places an interrupt signal on the bus when it needs the attention of the CPU when it receives an interrupt, saves its current state and invokes the necessary interrupt handler using the interrupt vector
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about I/O Software in an Operating System.
As explained in the video, I/O software is often organized in the following layers −
- User Level Libraries − This gives the user programme a straightforward interface for performing input and output. For instance, the C and C++ programming languages provide the library stdio.
- Kernel Level Modules − This enables interaction between the device driver and the device controller as well as the device independent I/O modules.
- Hardware − This layer contains the actual hardware as well as the hardware controllers that communicate with the device drivers to bring the hardware to life.
For example, a program that reads a file as input should be able to read a file on a floppy disk, on a hard disk, or on a CD-ROM, without having to modify the program for each different device.
User Space
- These are the libraries which provide richer and simplified interface to access the functionality of the kernel or ultimately interactive with the device drivers. Most of the user-level I/O software consists of library procedures with some exception like spooling system which is a way of dealing with dedicated I/O devices in a multiprogramming system.
- I/O Libraries (e.g., stdio) are in user-space to provide an interface to the OS resident device-independent I/O SW. For example putchar(), getchar(), printf() and scanf() are example of user level I/O library stdio available in C programming.
Kernel:
Kernel I/O Subsystem is responsible to provide many services related to I/O. Following are some of the services provided.
- Scheduling − Kernel schedules a set of I/O requests to determine a good order in which to execute them. When an application issues a blocking I/O system call, the request is placed on the queue for that device. The Kernel I/O scheduler rearranges the order of the queue to improve the overall system efficiency and the average response time experienced by the applications.
- Buffering − Kernel I/O Subsystem maintains a memory area known as buffer that stores data while they are transferred between two devices or between a device with an application operation. Buffering is done to cope with a speed mismatch between the producer and consumer of a data stream or to adapt between devices that have different data transfer sizes.
- Spooling and Device Reservation − A spool is a buffer that holds output for a device, such as a printer, that cannot accept interleaved data streams. The spooling system copies the queued spool files to the printer one at a time. In some operating systems, spooling is managed by a system daemon process. In other operating systems, it is handled by an in kernel thread.
- Caching − Kernel maintains cache memory which is region of fast memory that holds copies of data. Access to the cached copy is more efficient than access to the original.
- Error Handling − An operating system that uses protected memory can guard against many kinds of hardware and application errors.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about File System in an Operating System.
A file is a group of related data that has a name and is stored on secondary storage like magnetic discs, magnetic tapes, and optical discs. Generally speaking, a file is a collection of bits, bytes, lines, or records that have meanings determined by the user and creator of the file.
A File Structure should be according to a required format that the operating system can understand. The following points are discussed in the video.
- A file has a certain defined structure according to its type.
- A text file is a sequence of characters organized into lines.
- A source file is a sequence of procedures and functions.
- An object file is a sequence of bytes organized into blocks that are understandable by the machine.
- When operating system defines different file structures, it also contains the code to support these file structure. Unix, MS-DOS support minimum number of file structure.
File Type:
The ability of the operating system to distinguish between various file types, such as text files, source files, binary files, etc., is referred to as file type. Numerous operating systems can handle a wide variety of files. UNIX and MS-DOS operating systems have the following types of files:
Ordinary files
- These are the files that contain user information.
- These may have text, databases or executable program.
- The user can apply various operations on such files like add, modify, delete or even remove the entire file.
Directory files - These files contain list of file names and other information related to these files.
Special files
- These files are also known as device files.
- These files represent physical device like disks, terminals, printers, networks, tape drive etc.
These files are of two types −
- Character special files − data is handled character by character as in case of terminals or printers.
- Block special files − data is handled in blocks as in the case of disks and tapes.
File Access Mechanisms:
File access mechanism refers to the manner in which the records of a file may be accessed. There are several ways to access files −
- Sequential access
- Direct/Random access
- Indexed sequential access
Sequential access:
A sequential access is one in which the records are accessed in some order, meaning that each record's contents is processed one at a time. The most basic access method is this one. Example: This is how compilers typically access files.
Direct/Random access
- Random access file organization provides, accessing the records directly.
- Each record has its own address on the file with by the help of which it can be directly accessed for reading or writing.
- The records need not be in any sequence within the file and they need not be in adjacent locations on the storage medium.
Indexed sequential access
- This mechanism is built up on base of sequential access.
- An index is created for each file which contains pointers to various blocks.
- Index is searched sequentially and its pointer is used to access the file directly.
Space Allocation
Files are allocated disk spaces by operating system. Operating systems deploy following three main ways to allocate disk space to files.
- Contiguous Allocation
- Linked Allocation
- Indexed Allocation
Contiguous Allocation
- Each file occupies a contiguous address space on disk.
- Assigned disk address is in linear order.
- Easy to implement.
- External fragmentation is a major issue with this type of allocation technique.
Linked Allocation
- Each file carries a list of links to disk blocks.
- Directory contains link / pointer to first block of a file.
- No external fragmentation
- Effectively used in sequential access file.
- Inefficient in case of direct access file.
Indexed Allocation
- Provides solutions to problems of contiguous and linked allocation.
- A index block is created having all pointers to files.
- Each file has its own index block which stores the addresses of disk space occupied by the file.
- Directory contains the addresses of index blocks of files.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about Security in an Operating System.
Security is the most important factor to think about when discussing an operating system. OS security is the process of guaranteeing the integrity, confidentiality, and availability of the OS. Security refers to the specific actions or measures taken to defend the OS against dangers, viruses, worms, malware, and remote hacker intrusions. All preventive-control techniques that guard any computer device's assets from being taken, altered, or deleted if OS protection is arbitrated are included in OS security.
OS security could also be approached in some ways, including adherence to the following points as discussed in the video:
- Performing regular OS patch updates
- Installing updated antivirus engines and software.
- Watching all incoming and outgoing network transit via a firewall.
- Generating secure accounts with demanded perquisites only which is user management.
- To maintain a good Security all the operating has evolved drastically, they have added counter measures in order to keep their OS safe for the users.
Every OS has started implementing these things:-
- Authentication - Every OS need A user credential to use their services otherwise they don’t let you use.
- One Time passwords - offer extra protection along with usual authentication.
- Program Threats - Operating system's procedures and kernel do the assigned task as instructed.
- System Threats - implies to misuse of system facilities and network relationships to place user in trouble.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about Linux Operating System.
One of the most widely used UNIX operating systems is Linux. It is open source because the source code is available for free. The use of it is free. UNIX compatibility was taken into account when creating Linux. Its functionality list resembles UNIX quite a bit.
Components of Linux System
Linux Operating System has primarily three components
- Kernel − Linux's kernel is its foundation. It oversees all of this operating system's important functions. It is made up of different modules and communicates with the underlying hardware directly. The kernel offers the necessary abstraction to shield system or application programmes from low level hardware specifics.
- System Library −System libraries are unique programmes or functions that allow application software or system utilities to access Kernel features. The majority of operating system features are implemented by these libraries, which don't need access to the code of kernel modules.
- System Utility − System utility programmes are in charge of carrying out specific, high-level tasks.
2 Modes: Kernel and User
- Code that is a part of the kernel runs in the highly privileged kernel mode, which has complete access to all of the computer's resources. This code is very effective and quick because it represents a single process, runs in a single address space, and doesn't need to switch contexts. Each process is run by the kernel, which also gives it access to protected hardware and system services. System Library contains support code that is not necessary for kernel mode operation.
- In User Mode, which has no access to the kernel code or system hardware, user programmes and other system programmes operate. User programs and utilities access kernel functions through system libraries to obtain low level system tasks.
Features:
Following are some of the important features of Linux Operating System.
- Portable − Software that is portable can function identically on various types of hardware. Any type of hardware platform can support the installation of the Linux kernel and application programmes.
- Open Source − The Linux development project has a community-based development model and is open source. To increase the functionality of the Linux operating system, various teams collaborate and it is constantly evolving.
- Multi-User −Because Linux is a multiuser operating system, multiple users can simultaneously access system resources like memory, ram, and application programmes.
- Multiprogramming − Because Linux is a multiprogramming operating system, several programmes can run simultaneously.
- Hierarchical File System −System files and user files are organised according to a standard file structure provided by Linux.
- Shell − Linux provides a special interpreter program which can be used to execute commands of the operating system. It can be used to perform a variety of tasks, including calling application programmes.
- Security − Linux offers user security through authentication tools like password protection, restricted access to particular files, and data encryption.
Architecture:
- Hardware layer − Hardware consists of all peripheral devices (RAM/ HDD/ CPU etc).
- Kernel − It is the core component of Operating System, interacts directly with hardware, provides low level services to upper layer components.
- Shell − An interface to kernel, hiding complexity of kernel's functions from users. The shell takes commands from the user and executes kernel's functions.
- Utilities − Utility programs that provide the user most of the functionalities of an operating systems.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about the concept of Hashing in an Operating System.
When constructing the page table, hash tables are frequently used. Virtual page numbers are hashed into hash tables in hashed page tables, which use virtual addresses as their source of data. To manage address spaces larger than 32 bits, they are employed.
The linked list of items for each entry in the hash table is hashed to the same place (to avoid collisions – as we can get the same value of a hash function for different page numbers). The virtual page number is the hash value. All the components outside of the page offset make up the virtual page number.
Hash Table has the following elements:-
- Virtual Page Number (VPN): p, q
- Page Frame Number (PFN): r
- Offset: d
- Hash Function: h(x)
- Hashed Page Table with schema (key, VPN, PFN, Pointer to next entry with key) for each entry in the table
It so happens that h(p) = same_key and h(q) = same_key. There is hash collision. Both p and q are hashed to the same_key.
This is resolved by chaining the entry with VPN = q to the entry with VPN = p. Chaining means to use the Pointer field in the entry with VPN = q to point to the entry with VPN = p.
Working of Hash Page Table
- Operating system (OS) grabs p from the CPU, and performs h(p) to get same_key.
- OS looks up the first entry in the Hashed Page Table with key = same_key and checks p against the first entry's VPN field. It checks p against q. This is incorrect.
- OS uses the Pointer in the first entry to find the second entry. It knows that the second entry has the same key = same_key, because the Page Table is constructed this way. OS checks p against the second entry's VPN field. It checks p against p. This is correct. Bam. Kill confirmed.
- OS knows that this is the correct entry it is looking for. It grabs PFN from the second entry. It grabs r. r is the correct physical frame number that corresponds to virtual page number p.
- OS uses r to look for the physical frame it wants in physical memory, and looks for the exact word wanted which is offset by d within frame r in physical memory. OS grabs the contents of the
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about the concept of Paging in an Operating System.
We have different types of memory allocation and management schemes in Operating Systems. In some memory management schemes, we store data in a contiguous manner just like an array of data. On the other hand, some schemes store data in the form of blocks or chunks, or segments.
Some techniques divide the memory into fixed-sized blocks on the other hand some techniques divide the memory unevenly. So, Paging is nothing but a type of memory management technique that uses non-contiguous memory allocation to store data.
Paging is a fixed-sized memory allocation, storage, and management scheme.
Paging does two types of memory division:
1. The main memory is divided into small fixed-sized blocks of physical memory which are known as Frames. The main memory can be referred to as the Collection of Frames.
2. The logical or Secondary Memory is divided into small fixed-sized blocks which are known as Pages.
3. Paging keeps track of all the free frames of main memory and loads the pages of secondary memory or processes for the CPU to work with.
Translation of Logical Address into Physical Address
The address generated by the CPU is not the actual address in the main memory. The address generated by the CPU is known as a Logical Address and the address of the main memory is termed a Physical Address.
The logical address generated by the CPU has two parts-
1. Page Number: Page number contains the base address of each page present in the physical memory. Page number is denoted with p.
2. Page Offset: Page Offset is the number of bits required to represent a word of data into the page. Page offset is denoted with d.
The Logical Address is translated into the Physical Address by MMU (Memory Management Unit). This translation of logical memory to physical memory is known as address translation. Address translation results in the actual location of the instruction present in the RAM.
The physical address has two parts-
1. Frame Number: The page table provides the corresponding base address of the frame which is known as frame number. Frame number indicates the specific frame where the page is to be stored. The frame number is denoted with f.
2. Page Offset: Offset is the number of words that have to be read from that page. Page offset is denoted with d.
Both offset and page numbers are used in address translation. Operating Systems also uses a data structure (page table) to track the pages.
Advantages of Paging in OS
- Paging allows the data to be stored in a non-contiguous manner.
- Paging helps to solve the external fragmentation issue because the frame size is the same as the page size; hence, a page can be easily loaded into a frame without any loss of data.
- Swapping of pages is easy due to the same size.
- Paging is also a very simple algorithm for memory management.
- With the help of TLB cache, Paging is even faster.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about the concept of Indexing in an Operating System.
The indexed file allocation is somewhat similar to linked file allocation as indexed file allocation also uses pointers but the difference is here all the pointers are put together into one location which is called index block. That means we will get all the locations of blocks in one index file. The blocks and pointers were spread over the memory in the Linked Allocation method, where retrieval was accomplished by visiting each block sequentially. But here in indexed allocation, it becomes easier with the index block to retrieve.
Advantages:
- It reduces the possibilities of external fragmentation.
- Rather than accessing sequentially it has direct access to the block.
Disadvantages:
- Here more pointer overhead is there.
- If we lose the index block we cannot access the complete file.
- It becomes heavy for the small files.
- It is possible that a single index block cannot keep all the pointers for some large files
To resolve this issue, we can use:
- Linked Scheme
If the file is big then more blocks are required so one index block is insufficient to store all the pointers, therefore to store the pointers two or more index blocks are used where these index boxes are connected using linked file allocation that is each index block stores the pointer to the next index block.
- Multilevel Index
In this method, the multiple indexes blocks along with the levels of these blocks. Here, the level 1 block is used for pointing to the level 2 block which points to the blocks occupied by the file. These index blocks can be extended to three or more levels according to the size of the file.
- Combined Scheme
In Combined Scheme, a special block is used to store all the information related to the file like name, authority, size, etc. The special block is called inode(information-node). Some space of this special block is used to store the information related to the field as mentioned above and the remaining space is used to store the addresses of blocks that contain the actual file.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
"In this Operating System lecture, you will learn about the concept of Segmentation in an Operating System.
Segmentation is a memory management technique whereby data items are stored in segments on the storage media. It divides the process or user-accessible space into fixed-sized blocks, called segments.
As demonstrated in the video, Memory management is the process of allocating and controlling the virtual address spaces of computers. It also allows different parts of the kernel to run in separate address spaces.
Segmentation is a memory management technique which divides the program from the user’s view of memory. That means the program is divided into modules/segments, unlike paging in which the program was divided into different pages, and those pages may or may not be loaded into the memory simultaneously. Segmentation prevents internal fragmentation.
Working:
A physical address is an address for finding data in the main memory. So now the CPU will take the help of the segment table. A segment table is a data structure for storing the information of all process segments. CPU use a segment table to map the logical address to a physical address. In the segment table, there are two types of information
1. Limit: Actual size of a segment.
2. Base Address: the address of the segment in the main memory.
Then if the value of offset(d)<=Limit. Then only the CPU can read that segment; else, the error will be there. Offset(d) depicts the size of that segment CPU wants to read.
Advantages of using segmentation technique:
- It can improve the system’s efficiency by allowing different kernel parts to run on separate processor cores.
- It can also improve the system’s responsiveness by allowing different threads to run in parallel.
- Provide a solution to internal fragmentation.
- The segment table is there for storing records of segments. This segment table also consumes some memory to get stored.
- Segmentation is near to the user’s view of physical memory. Segmentation allows users to partition the user programs into modules. These modules are nothing but the independent codes of the current process.
This OS topic was covered in the video with plenty of in-depth explanation and lucid examples for your ease of understanding."
This course will teach you essential skills to become a software development engineer. You will cover fundamentals of computer science, data structures, algorithms, database management systems, and operating systems.
The course is divided into the following modules:
Fundamentals of Computer Science:
This module will introduce you to the basic concepts of computer science, such as data types, algorithms, and programming languages. You will also learn about the hardware and software components of a computer system.
Programming:
This module will teach you the basics of the C++ programming language, including variables, loops, functions, and classes. You will also learn about object-oriented programming and how to use C++ to develop software applications.
Data Structures:
This module will introduce you to the different types of data structures, such as arrays, lists, and trees. You will also learn how to implement these data structures in C++.
Algorithms:
This module will teach you about the different types of algorithms, such as sorting and searching algorithms. You will also learn how to analyze the efficiency of algorithms.
Database Management Systems:
This module will introduce you to the different types of database systems, such as relational databases and NoSQL databases. You will also learn about the different operations that can be performed on databases, such as querying and updating data.
Operating Systems:
This module will introduce you to the different components of an operating system, such as the kernel, memory management, and file system. You will also learn about the different types of operating systems, such as Windows, macOS, and Linux.
If you are interested in becoming a software development engineer, this course is a great place to start. It will give you the skills and knowledge you need to succeed in this challenging and rewarding field.
Here are some of the benefits of taking this course:
Learn from an experienced software development engineer with over 10 years of experience.
Get hands-on experience with C++, data structures, algorithms, and other essential skills.
Complete practical exercises and projects to test your knowledge.
Learn at your own pace and from anywhere in the world.