0% found this document useful (0 votes)
15 views

Data Types and Operator

Here are the steps to solve this problem: 1. Declare an integer variable a and initialize it to 5: int a = 5; 2. Use an if statement to check if a is equal to 5: if (a == 5) 3. If a is equal to 5, print "Equal": { printf("Equal"); } 4. Else, print "Not Equal": else { printf("Not Equal"); } 5. Return 0 at the end of the main function: return 0; So the full code is: #include <stdio.h> int main() {

Uploaded by

hi.gatecse
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views

Data Types and Operator

Here are the steps to solve this problem: 1. Declare an integer variable a and initialize it to 5: int a = 5; 2. Use an if statement to check if a is equal to 5: if (a == 5) 3. If a is equal to 5, print "Equal": { printf("Equal"); } 4. Else, print "Not Equal": else { printf("Not Equal"); } 5. Return 0 at the end of the main function: return 0; So the full code is: #include <stdio.h> int main() {

Uploaded by

hi.gatecse
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 128

Data types and operator

Data types in C are mainly divided into three types:

1. Primitive Data Types: These data types are built-in or predefined data types and can be used directly
by the user to declare variables. example: int, char, float, bool, etc. Primitive data types available in C are:

• Integer
• Character
• Boolean
• Floating Point
• Double Floating Point
• Valueless or Void
• Wide Character
2. Derived Data Types:The data types that are derived from the primitive or built-in datatypes are referred
to as Derived Data Types. These can be of four types namely:

• Function
• Array
• Pointer
• Reference
3.Abstract or User defined Data types: These data types are defined by the user itself. Like, defining a
class in C or a structure. C provides the following user-defined datatypes:

• Class
• Structure
• Union
• Enumeration
• Typedef defined Datatype
Data Type

C++#include<stdio.h> // Header file

int main() // main function


{
int a=10; //Integer variable
printf(“%d”,&a);
return 0;
}

Header file

• Set of predefined standard library functions.


• Contains declaration of built-in functions.
• Stdio contains i/p,o/p functions.
• Header files have .h extension.
Main Function

• It is a special function that always starts executing code from the 'main' having 'int' or 'void' as
return data type.
• An operating system always calls the main() function when programmers or users execute their
programming code.
• It is responsible for starting and ending the program.
• It is a universally accepted keyword in programming language and cannot change its meaning and
name.
C has many built-in operators and can be classified into 6 types:

1. Arithmetic Operators
2. Relational Operators
3. Logical Operators
4. Bitwise Operators
5. Assignment Operators
6. Other Operators
The above operators have been discussed in detail:

1. Arithmetic Operators:

These operators are used to perform arithmetic/mathematical operations on operands. Examples: (+, -,
*, /, %,++,–). Arithmetic operators are of two types:

a) Unary Operators: Operators that operate or work with a single operand are unary operators. For
example: Increment(++) and Decrement(–) Operators

int val = 5;

++val; // 6

b) Binary Operators: Operators that operate or work with two operands are binary operators. For
example: Addition(+), Subtraction(-), multiplication(*), Division(/) operators

int a = 7;

int b = 2;

cout<<a+b; // 9

2. Relational Operators:

These are used for the comparison of the values of two operands. For example, checking if one operand
is equal to the other operand or not, whether an operand is greater than the other operand or not, etc.
Some of the relational operators are (==, >= , <= ).

int a = 3;

int b = 5;
a < b;

// operator to check if a is smaller than b

3. Logical Operators:

Logical Operators are used to combine two or more conditions/constraints or to complement the
evaluation of the original condition in consideration. The result of the operation of a logical operator is a
Boolean value either true or false.

For example, the logical AND represented as ‘&&’ operator in C or C++ returns true when both the
conditions under consideration are satisfied. Otherwise, it returns false. Therefore, a && b returns true
when both a and b are true (i.e. non-zero).

(4 != 5) && (4 < 5); // true

4. Bitwise Operators:

The Bitwise operators are used to perform bit-level operations on the operands. The operators are first
converted to bit-level and then the calculation is performed on the operands. Mathematical operations
such as addition, subtraction, multiplication, etc. can be performed at the bit-level for faster processing.
For example, the bitwise AND represented as & operator in C or C++ takes two numbers as operands and
does AND on every bit of two numbers. The result of AND is 1 only if both bits are 1.

int a = 5, b = 9; // a = 5(00000101), b = 9(00001001)

cout << (a ^ b); // 00001100

cout <<(~a); // 11111010

5. Assignment Operators:

Assignment operators are used to assign value to a variable. The left side operand of the assignment
operator is a variable and the right side operand of the assignment operator is a value. The value on the
right side must be of the same data type as the variable on the left side otherwise the compiler will raise
an error.

Different types of assignment operators are shown below:

a. “=”: This is the simplest assignment operator. This operator is used to assign the value on the right to
the variable on the left.

For example:

a = 10;

b = 20;

ch = 'y';

b. “+=”: This operator is combination of ‘+’ and ‘=’ operators. This operator first adds the current value of
the variable on left to the value on the right and then assigns the result to the variable on the left.
For example:

(a += b) can be written as (a = a + b)

If initially value stored in a is 5. Then (a += 6) = 11.

c. “-=”: This operator is a combination of ‘-‘ and ‘=’ operators. This operator first subtracts the value on
the right from the current value of the variable on left and then assigns the result to the variable on the
left.

For example:

(a -= b) can be written as (a = a - b)

If initially value stored in a is 8. Then (a -= 6) = 2.

d. “*=”: This operator is a combination of ‘*’ and ‘=’ operators. This operator first multiplies the current
value of the variable on left to the value on the right and then assigns the result to the variable on the
left.

For example:

(a *= b) can be written as (a = a * b)

If initially, the value stored in a is 5. Then (a *= 6) = 30.

e. “/=”: This operator is a combination of ‘/’ and ‘=’ operators. This operator first divides the current value
of the variable on left by the value on the right and then assigns the result to the variable on the left.

For example:

(a /= b) can be written as (a = a / b)

If initially, the value stored in a is 6. Then (a /= 2) = 3.

6. Other Operators:

Apart from the above operators, there are some other operators available in C or C++ used to perform
some specific tasks. Some of them are discussed here:

1.sizeof operator:

• sizeof is much used in the C/C++ programming language.


• It is a compile-time unary operator which can be used to compute the size of its operand.
• The result of sizeof is of the unsigned integral type which is usually denoted by size_t.
• Basically, the sizeof the operator is used to compute the size of the variable.
2.comma operator

• The comma operator (represented by the token) is a binary operator that evaluates its first
operand and discards the result, it then evaluates the second operand and returns this value (and
type).
• The comma operator has the lowest precedence of any C operator.
• Comma acts as both operator and separator.

3. Conditional Operator

• The conditional operator is of the form Expression1? Expression2: Expression3.


• Here, Expression1 is the condition to be evaluated. If the condition(Expression1) is True then we
will execute and return the result of Expression2 otherwise if the condition(Expression1) is false
then we will execute and return the result of Expression3.
• We may replace the use of if..else statements with conditional operators.
4.dot(.) and arrow(->) Operators:

• Member operators are used to reference individual members of classes, structures, and unions.
• The dot operator is applied to the actual object.
• The arrow operator is used with a pointer to an object.
5.Cast Operator:


Casting operators convert one data type to another. For example, int(2.2000) would return 2.
• A cast is a special operator that forces one data type to be converted into another.
• The most general cast supported by most of the C++ compilers is as follows − [ (type) expression
].
6.&, * operator

Pointer operator & returns the address of a variable. For example &a; will give the actual address of the
variable.

• Pointer operator * is a pointer to a variable. For example *var; will pointer to a variable var.
Operators Precedence
Condition Statements and Built-in Functions
Assignment
Assignment Operator: =

Returns assigned value

int a = (a=6) * (a=5);

Assignment Type
int a;

float b;

a = x;

b = x;

printf(a, b);

x printf(a) printf(b)

21 21 21.0

21/2 10 10.0

21.0/2.0 10 10.5

21.0/2 10 10.5

2/21 0 0.0

2.0/21 0 0.x

2/21.0 0 0.x

2.0/21.0 0 0.x

Condition Statements
• if
C++main()

if ()
{

• If- Else
C++main()

if ()

else if

else

}
}

Solve it!
C++void main()

int a = 5;

if (a=7)

printf(“Hi”);

else

printf(“Bye”);

printf(“%d”, a);

Ans is “Hi”.This is because a=7 is an assignment not checking the if condition.

printf

Returns number of symbols displayed on the screen by that printf.

C++void main()

int a;

a = printf(“Hello”);
printf(“%d”, a);

Prints “Hello5"

C++void main()

int a;

a = printf(“Geeks %d Geeks”, printf(“for”));

printf(“%d”, a);

printf first prints which is on the right hand side of the statement.So “for” will be printed first and then
print will return 3.Then “Geeks 3 Geeks” will be printed. Then 11 will be printed.
Output: forGeeks3Geeks11.

scanf
Returns the number of proper inputs given.

C++#include <iostream>
using namespace std;
int main() {
int a, b, check;

check = scanf("%d%d", &a, &b);

if (check == 2)
cout << "Proper inputs";
else
cout << "Improper inputs";

return 0;
}

Type Casting
Type casting is changing one data type to some other type.
In typecasting, the destination data type may be smaller than the source data type when converting the
data type to another data type, that’s why it is also called narrowing conversion.

Example :-

int x;
float y;
y = (float) x;
Marked as Read
Report An Issue

Storage Class and Scope

Storage Class
• Storage class determines use case of a variable
• Life of a variable
• There are four storage classes
• Auto
• Register
• Static
• Extern
Auto variable

The auto keyword provides type inference capabilities, using which automatic deduction of the
data type of an expression in a programming language can be done. This consumes less time
having to write out things the compiler already knows. As all the types are deduced in compiler
phase only, the time for compilation increases slightly but it does not affect the run time of the
program.
Static Variable
Static variables have a property of preserving their value even after they are out of their scope!
Hence, static variables preserve the value of their last use in their scope. So we can say that they
are initialized only once and exist until the termination of the program. Thus, no new memory is
allocated because they are not re-declared. Their scope is local to the function to which they
were defined. Global static variables can be accessed anywhere in the program. By default, they
are assigned the value 0 by the compiler.

Extern Variable

Extern storage class simply tells us that the variable is defined elsewhere and not within the same
block where it is used. Basically, the value is assigned to it in a different block and this can be
overwritten/changed in a different block as well. So an extern variable is nothing but a global
variable initialized with a legal value where it is declared in order to be used elsewhere. It can be
accessed within any function/block. Also, a normal global variable can be made extern as well by
placing the ‘extern’ keyword before its declaration/definition in any function/block. This basically
signifies that we are not initializing a new variable but instead we are using/accessing the global
variable only. The main purpose of using extern variables is that they can be accessed between
two different files which are part of a large program.

Register Variable
This storage class declares register variables which have the same functionality as that of the
auto variables. The only difference is that the compiler tries to store these variables in the register
of the microprocessor if a free register is available. This makes the use of register variables to be
much faster than that of the variables stored in the memory during the runtime of the program.
If a free register is not available, these are then stored in the memory only. Usually, a few
variables which are to be accessed very frequently in a program are declared with the register
keyword which improves the running time of the program. An important and interesting point to
be noted here is that we cannot obtain the address of a register variable using pointers.
Memory Segment

Heap Segment(Dynamic Memory Allocation)

Stack Segment(auto,Formal parameters,function calls,etc)

Data Segment(Global and static variables)

Code Segment(Editors)

Scope of a Variable

• Rules for free variables.


• Static & Dynamic Scoping.
• Static: Free variables are global variables
• Dynamic: Free variables are resolved from previous calling.
Questions on operators, Conditions, Storage Classes and Scope

Study the following program written in a block-structured Language:


Var x, y:interger;
procedure P(n:interger);
begin
x:=(n+2)/(n-3);
end;

procedure Q
Var x, y:interger;
begin
x:=3;
y:=4;
P(y);
Write(x) __(1)
end;

begin
x:=7;
y:=8;
Q;
Write(x); __(2)
end.

What will be printed by the write statements marked (1) and (2) in the program if the variables
are statically scoped?
1. 3,6
2. 6,7
3. 3,7
4. None of the above.
Ans 1
Using Static Scope:
First, procedure Q is called from the main procedure. Q has local variables x and y with values 3
and 4 respectively. This local variable y (value 4) is being passed to procedure P during call, and
received in local variable n inside procedure P. Now, as P does not have any local definition for
variable x, it will assign the evaluated value of (n+2)/(n-3) i.e. (4+2)/(4-3)=6 to the global variable
x, which was previously 7. After the call of procedure P, procedure Q writes the value of local
variable x which is still 3. Lastly, the main procedure writes the value of global variable x which
has been changed to 6 inside procedure P. So, the output will be 3, 6.

Consider the following program


Program P2
var n: int:
procedure W(var x: int)
begin
x=x+1;
print x;
end

procedure D
begin
var n: int;
n=3;
W(n);
end
begin //beginP2
n=10;
D;
end

If the language has dynamic scoping and parameters are passed by reference, what will be
printed by the program?
(A) 10
(B) 11
(C) 3
(D) None of the above

Answer: (D)

Explanation:
In static scoping or compile-time scoping the free variables (variables used in a function that are
neither local variables nor parameters of that function) are referred as global variables because
at compile only global variables are available.
In dynamic scoping or run-time scoping the free variables are referred as the variables in the
most recent frame of function call stack. In the given code in the function call of procedure W
the local variable x is printed i.e 4. Under dynamic scoping if x would have not been there in
procedure W then we would refer to x of the function in function call stack i.e procedure D and
the main function but since x is a local variable not a free variable we referred to the local variable
hence 4 will be printed.

What is the output of the following program?


#include <stdio.h>
int funcf (int x);
int funcg (int y);
main()
{
int x = 5, y = 10, count;
for (count = 1; count <= 2; ++count)
{
y += funcf(x) + funcg(x);
printf ("%d ", y);
}
}
funcf(int x)
{
int y;
y = funcg(x);
return (y);
}
funcg(int x)
{
static int y = 10;
y += 1;
return (y+x);
}
(A) 43 80
(B) 42 74
(C) 33 37
(D) 32 32

Answer: (A)

Explanation: The count=1 and it goes till two,so following statement will be executed twice.
y += funcf(x) + funcg(x);

1st call- funcg(x); // y = 11 y+x= 16.


2nd call funcg(x); // y= 12 y+x= 17.
First iteration-> main()->y = 16+17 +10 = 43
Second iteration-> main() y= 18+19 +43 =80

So the Answer is A.

Consider the following C function:


int f(int n)
{
static int i = 1;
if (n >= 5)
return n;
n = n+i;
i++;
return f(n);
}
The value returned by f(1) is
a) 5
b) 6
c) 7
d) 8
Answer (c)
Since i is static, first line of f() is executed only once.
Execution of f(1)
i = 1
n = 2
i = 2
Call f(2)
i = 2
n = 4
i = 3
Call f(4)
i = 3
n = 7
i = 4
Call f(7)
since n >= 5 return n(7)

Which one of the choices given below would be printed when the following program is executed?
#include
void swap (int *x, int *y)
{
static int *temp;
temp = x;
x = y;
y = temp;
}
void printab ()
{
static int i, a = -3, b = -6;
i = 0;
while (i <= 4)
{
if ((i++)%2 == 1) continue;
a = a + i;
b = b + i;
}
swap (&a, &b);
printf("a = %d, b = %d\n", a, b);
}
main()
{
printab();
printab();
}
(A) a = 0, b = 3
a = 0, b = 3
(B) a = 3, b = 0
a = 12, b = 9
(C) a = 3, b = 6
a = 3, b = 6
(D) a = 6, b = 3
a = 15, b = 12

Answer: (D)

Explanation:
Things to ponder:

• swap function doesn’t actually swaps two variables, rather just swaps their addresses in local
variables x and y – which is effectively nothing once swap function returns.
• printab function adds 9 to static variables a and b. The number 9 comes from the fact that the
while loop executes those arithmetic statements only when i = 1, 3, 5.
Hence, when printab is called for the first time, these are the local variable values: a = −3 + 9 and
b = −6 + 9. On the second time, a = −3 + 9 + 9 and b = −6 + 9 + 9 are the desired values.
Therefore, correct answer would be (D) a = 6, b = 3; a = 15, b = 12.

Consider the program below in a hypothetical programming language which allows global
variables and a choice of static or dynamic scoping.

int i ;
program main ()
{
i = 10;
call f();
}
procedure f()
{
int i = 20;
call g ();
}
procedure g ()
{
print i;
}

Let x be the value printed under static scoping and y be the value printed under dynamic scoping.
Then, x and y are
(A) x = 10, y = 10
(B) x = 20, y = 10
(C) x = 10, y = 20
(D) x = 20, y = 20

Answer: (C)

Explanation: Static scoping:


int i ;
program main ()
{
i = 10;
call f();
}

procedure f()
{
int i = 20;
call g ();
}
procedure g ()
{
print i; //as i=20 is scoped only within f() so it will point to global i
}
So, 10 is printed
Dynamic scoping:
int i ;
program main ()
{
i = 10;
call f();
}

procedure f()
{
int i = 20; // here global scoped i is changed
call g ();
}
procedure g ()
{
print i; // global value changed so, i=20 printed
}

Marked as Read
Report An Issue

Loops

Loops
• for loop
• while loop
• do-while loop
Loop Type Description

for loop first Initializes, then condition check, then


executes the body and at last, the update is
done.
while loop first Initializes, then condition checks, and then
executes the body, and updating can be inside
the body.

do-while loop do-while first executes the body and then the
condition check is done.

for Loop
A for loop is a repetition control structure that allows us to write a loop that is executed a specific
number of times. The loop enables us to perform n number of steps together in one line.
Syntax:
for (initialization expr; test expr; update expr)
{
// body of the loop
// statements we want to execute
}

Example:
for(int i = 0; i < n; i++){
}

In for loop, a loop variable is used to control the loop. First, initialize this loop variable to some
value, then check whether this variable is less than or greater than the counter value. If the
statement is true, then the loop body is executed and the loop variable gets updated. Steps are
repeated till the exit condition comes.


Initialization Expression: In this expression, we have to initialize the loop counter to some
value. for example: int i=1;
• Test Expression: In this expression, we have to test the condition. If the condition
evaluates to true then we will execute the body of the loop and go to update expression
otherwise we will exit from the for a loop. For example: i <= 10;
• Update Expression: After executing the loop body this expression
increments/decrements the loop variable by some value. for example: i++;
Equivalent Flow Diagram for loop:
While Loop
While studying for loop we have seen that the number of iterations is known beforehand, i.e. the
number of times the loop body is needed to be executed is known to us. while loops are used in
situations where we do not know the exact number of iterations of the loop beforehand. The
loop execution is terminated on the basis of the test conditions.
Syntax: We have already stated that a loop mainly consists of three statements – initialization
expression, test expression, and update expression. The syntax of the three loops – For, while,
and do while mainly differs in the placement of these three statements.
initialization expression;
while (test_expression)
{
// statements

update_expression;
}

Flow Diagram:
do-while loop
In do-while loops also the loop execution is terminated on the basis of test conditions. The main
difference between a do-while loop and the while loop is that in the do-while loop the condition
is tested at the end of the loop body, i.e do-while loop is exit controlled whereas the other two
loops are entry controlled loops.
Note: In a do-while loop, the loop body will execute at least once irrespective of the test
condition.
Syntax:
initialization expression;
do
{
// statements

update_expression;
} while (test_expression);

Note: Notice the semi – colon(“;”) at the end of the loop.


Flow Diagram:
Functions

A function is a set of statements that take inputs, do some specific computation and produces
output.
The idea is to put some commonly or repeatedly done task together and make a function so that
instead of writing the same code again and again for different inputs, we can call the function.
The general form of a function is:

return_type function_name([ arg1_type arg1_name, ... ]) { code }

• Code Re-utilisation
• Prototype
• Definition
• Calling
Why do we need functions?

• Functions help us in reducing code redundancy. If functionality is performed at multiple


places in software, then rather than writing the same code, again and again, we create a
function and call it everywhere. This also helps in maintenance as we have to change at
one place if we make future changes to the functionality.
• Functions make code modular. Consider a big file having many lines of code. It becomes
really simple to read and use the code if the code is divided into functions.
• Functions provide abstraction. For example, we can use library functions without
worrying about their internal working.
Function Declaration
A function declaration tells the compiler about the number of parameters function takes, data-
types of parameters, and return type of function. Putting parameter names in function
declaration is optional in the function declaration, but it is necessary to put them in the definition.
Below are an example of function declarations. (parameter names are not there in below
declarations)
Questions on Loops, Functions and Recursion

1.Consider the following C-program:

void foo(int n, int sum)


{
int k = 0, j = 0;
if (n == 0) return;
k = n % 10;
j = n / 10;
sum = sum + k;
foo (j, sum);
printf ("%d,", k);
}

int main ()
{
int a = 2048, sum = 0;
foo (a, sum);
printf ("%d\n", sum);

getchar();
}
What does the above program print?
(A) 8, 4, 0, 2, 14
(B) 8, 4, 0, 2, 0
(C) 2, 0, 4, 8, 14
(D) 2, 0, 4, 8, 0

Answer: (D)

sum has no use in foo(), it is there just to confuse. Function foo() just prints all digits of a
number. In main, there is one more printf statement after foo(), so one more 0 is printed
after all digits of n.

2.Consider the code fragment written in C below :


void f (int n)
{
if (n <=1) {
printf("%d",n);
}
else {
f (n/2);
printf("%d",n%2);
}
}
What does f(173) print?

(A)

010110101

(B)

010101101

(C)

10110101

(D)

10101101

Answer: (D)

Explanation:

Output of f is:- 10101101

Here, basically the function f prints the binary representation of the number.

3.void f (int n)
{
if (n <=1) {
printf("%d",n);
}
else {
f (n/2);
printf("%d",n%2);
}
}

Which of the following implementations will produce the same output for f(173) as the
above code?
P1

void f (int n)
{
if (n/2) {
f(n/2);
}
printf ("%d", n%2);
}

P2

void f (int n)
{
if (n <=1) {
printf ("%d", n);
}
else {
printf ("%d", n%2);
f (n/2);
}
}
(A) Both P1 and P2
(B) P2 only
(C) P1 only
(D) Neither P1 nor P2

Answer: (C)

Explanation:

Here, basically the function f prints the binary representation of the number.
function f1 also prints the binary representation of the number
function f2 prints the binary representation but in reverse order.
Output of f is:- 10101101
Output of f1 is:- 10101101
Output of f2 is:- 10110101

So, the answer is option (C) which is P1 only.

4.Consider the function func shown below:

int func(int num)


{
int count = 0;
while (num)
{
count++;
num >>= 1;
}
return (count);
}

The value returned by func(435)is __________.

(A) 8
(B) 9
(C) 10
(D) 11

Answer: (B)

Explanation: The function mainly returns position of Most significant bit in binary
representation of n. The MSD in binary representation of 435 is 9th bit.

Another explanation:
>> in right shift. In other words, it means divide by 2.
If keep on dividing by 2, we get: 435, 217, 108, 54, 27, 13, 6, 3, 1.
Therefore, the count is 9.

5.Consider the following C function.

int fun (int n)


{
int x=1, k;
if (n==1) return x;
for (k=1; k < n; ++k)
x = x + fun(k) * fun(n – k);
return x;
}
The return value of fun(5) is __________.

(A) 0
(B) 26
(C) 51
(D) 71

Answer: (C)

Explanation:
fun(5) = 1 + fun(1) * fun(4) + fun(2) * fun(3) +
fun(3) * fun(2) + fun(4) * fun(1)
= 1 + 2*[fun(1)*fun(4) + fun(2)*fun(3)]

Substituting fun(1) = 1
= 1 + 2*[fun(4) + fun(2)*fun(3)]

Calculating fun(2), fun(3) and fun(4)


fun(2) = 1 + fun(1)*fun(1) = 1 + 1*1 = 2
fun(3) = 1 + 2*fun(1)*fun(2) = 1 + 2*1*2 = 5
fun(4) = 1 + 2*fun(1)*fun(3) + fun(2)*fun(2)
= 1 + 2*1*5 + 2*2 = 15

Substituting values of fun(2), fun(3) and fun(4)


fun(5) = 1 + 2*[15 + 2*5] = 51

Marked as Read
Strings

What is String?

Strings are defined as an array of characters. The difference between a character array and a
string is the string is terminated with a special character ‘\0’.

Declaring a string is as simple as declaring a one dimensional array. Below is the basic syntax for
declaring a string in C programming language.

char str_name[size];

• Data Type
• Null character
• String name – base address
• ASCII values
Organisation in Memory
char str[21] = {‘g’, ‘e’, ‘e’, ‘k’, ‘s’, ‘\0’};
printf(“%c”, str[0]); //prints ‘g’
printf(“%s”, str[0]); //not a right instruction

Initialization of String
char str[6] = “India”;

char str[] = “India”;


char *p = “India”;//p takes base address of string “India”.
C++void main()
{
char str1[] = “Geeksforgeeks”;
char str2[10];
char *p1 = “Tathagata”;
char *p2;

str2 = str1;//Not allowed


p2 = p1;
}
C++void main()
{
char str1[] = “Geeksforgeeks”;
char str2[] = “Gate”;

if(str1 == str2)
printf(“Hi”);
else
printf(“Hello”);

if(*“Geeksforgeeks” == *“Gate”)//satisfies because both char pointers point


to the same char.
printf(“Hi”);
else
printf(“Hello”);
}
Output: HelloHi.

Operations

• Copy- strcpy() is a standard library function in C/C++ and is used to copy one string to
another. In C it is present in string.h header file and in C++ it is present in cstring header
file.
• Syntax: char* strcpy(char* dest, const char* src);
• Reverse -strrev()-etaG

• Upper-strupr()-GATE

• Lower-strlwr()-gate

• Length-The strlen() function calculates the length of a given string.The strlen() function is
defined in string.h header file. It doesn’t count null character ‘\0’.
Syntax: int strlen(const char *str);

structure
What is Structure?
A structure is a key word that creates user defined data type in C/C++. A structure creates
a data type that can be used to group items of possibly different types into a single type.

User defined data type

• Different operators
• Member variables
• Without member not allowed
• Structure in function
• Function in Structure
Ways to use structure

• Struct in struct member


• Struct defined in struct
• Struct in same struct
• Struct pointer in struct
Examples
struct emp
{
int id;
int sal;
char name[48];
}

struct emp e1;

struct emp
{
int id;
int sal;
char name[48];
}

Typedef struct emp Emp;//You can replace the name of the existing data type with the
name which you have provided. This keyword helps in creating a user-defined name for
an existing data type.

Marked as Read
Union in C

The Union is a user-defined data type in C language that can contain elements of the different
data types just like structure. But unlike structures, all the members in the C union are stored in
the same memory location. Due to this, only one member can store data at the given instance.

Syntax of Union in C
The syntax of the union in C can be divided into three steps which are as follows:

C Union Declaration
In this part, we only declare the template of the union, i.e., we only declare the members’ names
and data types along with the name of the union. No memory is allocated to the union in the
declaration.
union union_name {
datatype member1;
datatype member2;
...
};

Keep in mind that we have to always end the union declaration with a semi-colon.

Different Ways to Define a Union Variable


We need to define a variable of the union type to start using union members. There are two
methods using which we can define a union variable.
1. With Union Declaration
2. After Union Declaration
1. Defining Union Variable with Declaration
union union_name {
datatype member1;
datatype member2;
...
} var1, var2, ...;

2. Defining Union Variable after Declaration


union union_name var1, var2, var3...;

where union_name is the name of an already declared union.

Access Union Members


We can access the members of a union by using the ( . ) dot operator just like structures.
var1.member1;

where var1 is the union variable and member1 is the member of the union.
The above method of accessing the members of the union also works for the nested unions.
var1.member1.memberA;

Here,

• var1 is a union member.


• member1 is a member of the union.
• memberA is a member of member1.
Initialization of Union in C
The initialization of a union is the initialization of its members by simply assigning the value to it.
var1.member1 = some_value;

One important thing to note here is that only one member can contain some value at a given
instance of time.

Example of Union
C++// C Program to demonstrate how to use union
#include <stdio.h>

// union template or declaration


union un {
int member1;
char member2;
float member3;
};
// driver code
int main()
{

// defining a union variable


union un var1;

// initializing the union member


var1.member1 = 15;

printf("The value stored in member1 = %d",


var1.member1);

return 0;
}

Output
The value stored in member1 = 15

Size of Union
The size of the union will always be equal to the size of the largest member of the array. All the
less-sized elements can store the data in the same space without any overflow.

Difference between C Structure and C Union


The following table lists the key difference between the structure and union in C:
Structure Union
The size of the structure is equal to or greater The size of the union is the size of its largest
than the total size of all of its members. member.

The structure can contain data in multiple Only one member can contain data at the same
members at the same time. time.

It is declared using the struct keyword. It is declared using the union keyword.

Dynamic Memory Allocation

Since C is a structured language, it has some fixed rules for programming. One of them includes
changing the size of an array. An array is a collection of items stored at contiguous memory
locations.

As it can be seen that the length (size) of the array above made is 9. But what if there is a
requirement to change this length (size). For Example,

• If there is a situation where only 5 elements are needed to be entered in this array. In this
case, the remaining 4 indices are just wasting memory in this array. So there is a
requirement to lessen the length (size) of the array from 9 to 5.
• Take another situation. In this, there is an array of 9 elements with all 9 indices filled. But
there is a need to enter 3 more elements in this array. In this case, 3 indices more are
required. So the length (size) of the array needs to be changed from 9 to 12.
This procedure is referred to as Dynamic Memory Allocation in C.
Therefore, C Dynamic Memory Allocation can be defined as a procedure in which the size of a
data structure (like Array) is changed during the runtime.
C provides some functions to achieve these tasks. There are 4 library functions provided by C
defined under <stdlib.h> header file to facilitate dynamic memory allocation in C programming.
They are:
1. malloc()
2. calloc()
3. free()
4. realloc()
Let’s look at each of them in greater detail.

C malloc() method
The “malloc” or “memory allocation” method in C is used to dynamically allocate a single large
block of memory with the specified size. It returns a pointer of type void which can be cast into
a pointer of any form. It doesn’t Initialize memory at execution time so that it has initialized each
block with the default garbage value initially.
Syntax:
ptr = (cast-type*) malloc(byte-size)
For Example:
ptr = (int*) malloc(100 * sizeof(int));

Since the size of int is 4 bytes, this statement will allocate 400 bytes of memory. And, the pointer
ptr holds the address of the first byte in the allocated memory.

If space is insufficient, allocation fails and returns a NULL pointer.

C calloc() method
1. “calloc” or “contiguous allocation” method in C is used to dynamically allocate the
specified number of blocks of memory of the specified type. it is very much similar to
malloc() but has two different points and these are:
2. It initializes each block with a default value ‘0’.
3. It has two parameters or arguments as compare to malloc().
Syntax:
ptr = (cast-type*)calloc(n, element-size);
here, n is the no. of elements and element-size is the size of each element.
For Example:
ptr = (float*) calloc(25, sizeof(float));

This statement allocates contiguous space in memory for 25 elements each with the size of the
float.

If space is insufficient, allocation fails and returns a NULL pointer.


C free() method
“free” method in C is used to dynamically de-allocate the memory. The memory allocated using
functions malloc() and calloc() is not de-allocated on their own. Hence the free() method is used,
whenever the dynamic memory allocation takes place. It helps to reduce wastage of memory by
freeing it.
Syntax:
free(ptr);

C realloc() method
“realloc” or “re-allocation” method in C is used to dynamically change the memory allocation of
a previously allocated memory. In other words, if the memory previously allocated with the help
of malloc or calloc is insufficient, realloc can be used to dynamically re-allocate memory. re-
allocation of memory maintains the already present value and new blocks will be initialized with
the default garbage value.
Syntax:
ptr = realloc(ptr, newSize);
where ptr is reallocated with new size 'newSize'.

If space is insufficient, allocation fails and returns a NULL pointer.

Pointer Concepts

Pointers
• Variable
• Stores Address
• Cannot be negative
• Takes same bytes of memory
• Size depends upon memory and data type
Cases
int a = 10;
int *b = &a;// let the address of &a =1000=b

print(a) // prints 10
print(b) //prints 1000
print(*b) //prints 10
print(a*b) //Error
print(a**b) //*b=a=10 so, a*a=100

Syntax:
datatype *var_name;
int *ptr; //ptr can point to an address which holds int data

How to use a pointer?

• Define a pointer variable


• Assigning the address of a variable to a pointer using unary operator (&) which
returns the address of that variable.
• Accessing the value stored in the address using unary operator (*) which returns
the value of the variable located at the address specified by its operand.
The reason we associate data type to a pointer is that it knows how many bytes the data
is stored in. When we increment a pointer, we increase the pointer by the size of data type
to which it points.

Call by Value
Change in values of Formal parameter are not reflected in actual parameters

Cases
// C program to illustrate
// call by value
#include <stdio.h>
// Function Prototype
void swapx(int x, int y);
// Main function
int main()
{
int a = 10, b = 20;
// Pass by Values
swapx(a, b);
printf("a=%d b=%d\n", a, b);
return 0;
}
// Swap functions that swaps
// two values
void swapx(int x, int y)
{
int t;
t = x;
x = y;
y = t;
printf("x=%d y=%d\n", x, y);
}

Output:
x=20 y=10
a=10 b=20

Call by Reference
• Address is passed, not value
• Actual parameters are changed
• C by default supports call by value
Cases

// C program to illustrate
// Call by Reference

#include <stdio.h>
// Function Prototype
void swapx(int*, int*);

// Main function
int main()
{
int a = 10, b = 20;
// Pass reference
swapx(&a, &b);
printf("a=%d b=%d\n", a, b);
return 0;
}
// Function to swap two variables
// by references
void swapx(int* x, int* y)
{
int t;
t = *x;
*x = *y;
*y = t;
printf("x=%d y=%d\n", *x, *y);
}

Output:
x=20 y=10
a=20 b=10

Pointer to Array

Pointer to Array
• Array name without subscript contains base address allocated by the compiler
• This base address is constant and cannot be changed
1D array
• When the pointer is incremented, it’ll increment w.r.t to the size of the element it
is pointing to.
• Unary operator associativity is Right to left.
• In the previous example, ptr and a both are pointing to the array but ptr is pointer
variable and a is pointer constant.
void main()
{
int a[5] = {10, 20, 30, 40, 50};
int *ptr = &a[0];//let the starting address of a be 1000
++ptr;// ptr =1004
++ptr;// ptr=1008
--*ptr;//30 changes to 29
printf(“%d”, *ptr); //prints 29
}
Points
a[2] = *(a+2) = 30

a[2] = *(a+2) = *(2+a) = 2[a]

int a[5] = {10,20,30,40,50};

int *p1 = a;

int *p2 = a+4;

int a[5] = {10,20,30,40,50}

Expression Print Value Type

&a 1000 Int *[]

*&a 1000 Int *

a 1000 Int *

*a 10 Int

&a+1 1020 int*[]

a+1 1004 int*

*a+1 11 int

**a error

a+3 1012 int*

*(a+3) 40 int

a[3] 40 int
Pointer and Functions

Passing Array Pointer to Function


• Passing all the elements of the array
• Passing base address

void main()
{
int a[5] = {10, 20, 30, 40, 50};// address of a=1000
display(a,5);//display(1000,5)
}
void display(int *p, int n)
{
int i;
for(i=0; i<n; i++)
printf(“%d”, p[i]);
}

Pointer to Function
void test();
void main()
{
void (*p)(); // 1
p = test; // 2
(p)(); // 3
}
void test()
{
printf(“Hello World”);
}
Pointer Expressions
1. void(*p)(); //p is a pointer to a function which takes no argument.
2. int(*p)(int, int)//p is a pointer to a function which takes two integers as argument
and returns an integer.
3. int(*p)(int *)//p is a pointer to a function which takes input as an integer pointer
and returns an integer.
4. int a[n] // array of n integers
5. int *a[n] // array of n integer pointers
6. int (*a)[5]// a is a pointer to an array of n integers
7. int (*a)() //a is a pointer to function which takes no arguments as input and returns
integer.
8. **(*a)(*,*)// a is a function pointer which takes arguments as two pointer and
returns a double pointer(a pointer to a pointer).

Marked as Read
Report An Issue

Pointer Arithmetic

Pointer Operations
• Adding Constant
• Subtract Constant
• Pointer Addition
• Pointer Subtraction
void main()
{
int a[5] = {10, 20, 30, 40, 50};//let address of a be 1000
int *p1 = a; //1000
int *p2 = a+4;//1016
}

Void Pointer

• What is a void pointer?


A void pointer is a pointer that has no associated data type with it. A void pointer can
hold address of any type and can be typecasted to any type.

• Type Casting
• Usecase

void main()
{
void *ptr;
char ch = ‘g’;
int i = 10;
char *cp = “Geeksforgeeks”;// address of cp =3000
ptr = &ch;
printf(“%c”, *(char *)ptr);// prints ‘g’
ptr = &i;
printf(“%d”, *(int *)ptr);// prints 10
ptr = cp;
printf(“%s”, (char *)ptr+3);// prints ksforgeeks
}

TypeCasting
int a[3] = {300, 301, 302}; // address of a=1000

int *i = a;//1000

char *c = a;//error should be char *c=(char*)a;

float *f = a;// error should be float *f=(float*)a;

void *e = a; //error

Print(*i)

Print(*c)

Print(*f)

Print(*e)
Array

What is an Array?
• Collection
• Similar Elements
• Random Access
• Sequential Access
• It is a group of variables of similar data types referred to by a single element.
• Its elements are stored in a contiguous memory location.
• The size of the array should be mentioned while declaring it.
• Array elements are always counted from zero (0) onward.
• Array elements can be accessed using the position of the element in the array.
• The array can have one or more dimensions.
An array in C/C++ or be it in any programming language is a collection of similar data
items stored at contiguous memory locations and elements can be accessed randomly
using indices of an array. They can be used to store the collection of primitive data types
such as int, float, double, char, etc of any particular type. To add to it, an array in C/C++
can store derived data types such as structures, pointers etc. Given below is the picture
representation of an array.
1D array
• It is a list of the variables of similar data types.
• It allows random access and all the elements can be accessed with the help of their
index.
• The size of the array is fixed.
Representation of 1D array:

2D array
• It is a list of lists of the variable of the same data type.
• It also allows random access and all the elements can be accessed with the help of
their index.
• It can also be seen as a collection of 1D arrays. It is also known as the Matrix.
• Its dimension can be increased from 2 to 3 and 4 so on.
• They all are referred to as a multi-dimension array.
• The most common multidimensional array is a 2D array.
Representation of 2 D array:

3D array

The general form of declaring N-dimensional arrays is:


data_type array_name[size1][size2]....[sizeN];

• data_type: Type of data to be stored in the array.


• array_name: Name of the array
• size1, size2,… ,sizeN: Sizes of the dimension
Three dimensional array: int three_d[10][20][30];

array int x[5][10][20] can store total (5*10*20) = 1000 elements.

Initializing Three-Dimensional Array: Initialization in a Three-Dimensional array is the


same as that of Two-dimensional arrays. The difference is as the number of dimensions
increases so the number of nested braces will also increase.

Method 1:
int x[2][3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19,20, 21, 22, 23};

Method 2(Better):
int x[2][3][4] = {{ {0,1,2,3}, {4,5,6,7}, {8,9,10,11} },{ {12,13,14,15},
{16,17,18,19}, {20,21,22,23} }};

Accessing elements in Three-Dimensional Arrays: Accessing elements in Three-


Dimensional Arrays is also similar to that of Two-Dimensional Arrays. The difference is we
have to use three loops instead of two loops for one additional dimension in Three-
dimensional Arrays.

Array Arithmetic
Arranging Elements in an Array
• Row-Major
• Column-Major

Row Major Order:Row major order assigns successive elements, moving across the rows
and then down the next row, to successive memory locations. In simple language, the
elements of an array are stored in a Row-Wise fashion.

To find the address of the element using row-major order use the following formula:

Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))

I = Row Subset of an element whose address to be found,

J = Column Subset of an element whose address to be found,

B = Base address,

W = Storage size of one element store in an array(in byte),

LR = Lower Limit of row/start row index of the matrix(If not given assume it as zero),

LC = Lower Limit of column/start column index of the matrix(If not given assume it as
zero),

N = Number of column given in the matrix.

Example: Given an array, arr[1………10][1………15] with base value 100 and the size of each
element is 1 Byte in memory. Find the address of arr[8][6] with the help of row-major
order?

Solution:

Given:

Base address B = 100

Storage size of one element store in any array W = 1 Bytes

Row Subset of an element whose address to be found I = 8

Column Subset of an element whose address to be found J = 6


Lower Limit of row/start row index of matrix LR = 1

Lower Limit of column/start column index of matrix = 1

Number of column given in the matrix N = Upper Bound – Lower Bound + 1

= 15 – 1 + 1

= 15

Formula:

Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))

Solution:

Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))

= 100 + 1 * ((7) * 15 + (5))

= 100 + 1 * (110)

Address of A[I][J] = 210

Column Major Order: If elements of an array are stored in a column-major fashion means
moving across the column and then to the next column then it’s in column-major order.
To find the address of the element using column-major order use the following formula:

Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))

I = Row Subset of an element whose address to be found,

J = Column Subset of an element whose address to be found,

B = Base address,

W = Storage size of one element store in any array(in byte),

LR = Lower Limit of row/start row index of matrix(If not given assume it as zero),

LC = Lower Limit of column/start column index of matrix(If not given assume it as zero),

M = Number of rows given in the matrix.

Example: Given an array arr[1………10][1………15] with a base value of 100 and the size of
each element is 1 Byte in memory find the address of arr[8][6] with the help of column-
major order.
Solution:

Given:

Base address B = 100

Storage size of one element store in any array W = 1 Bytes

Row Subset of an element whose address to be found I = 8

Column Subset of an element whose address to be found J = 6

Lower Limit of row/start row index of matrix LR = 1

Lower Limit of column/start column index of matrix = 1

Number of column given in the matrix M = Upper Bound – Lower Bound + 1

= 10 – 1 + 1

= 10

Formula:

Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))

Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))

= 100 + 1 * ((5) * 10 + (7))

= 100 + 1 * (57)

Address of A[I][J] = 157

From the above examples, it can be observed that for the same position two different
address locations are obtained that’s because in row-major order movement is done
across the rows and then down to the next row, and in column-major order, first move
down to the first column and then next column. So both the answers are right.

So it’s all based on the position of the element whose address is to be found for some
cases the same answers is also obtained with row-major order and column-major order
and for some cases, different answers are obtained.

In Case of 3D array

Calculate the address of any element in the 3-D Array: A 3-Dimensional array is a
collection of 2-Dimensional arrays. It is specified by using three subscripts:
• Block size
• Row size
• Column size
More dimensions in an array mean more data can be stored in that array.

Example:

To find the address of any element in 3-Dimensional arrays there are the following two
ways-

• Row Major Order


• Column Major Order

Row Major Order: To find the address of the element using row-major order, use the
following formula:

Address of[i][j][k] = B + W * {[(I – LR) * N + (J – LC)]* R + [K – LB]}

I = Row Subset of an element whose address to be found,

J = Column Subset of an element whose address to be found,

K = Block Subset of an element whose address to be found,

B = Base address,

W = Storage size of one element store in any array(in byte),

LR = Lower Limit of row/start row index of matrix(If not given assume it as zero),

LC = Lower Limit of column/start column index of matrix(If not given assume it as zero),

LB = Lower Limit of blocks in matrix,

N = Number of column given in the matrix

R = Number of blocks given in the matrix.

Example: Given an array arr[1:9, -4:1, 5:10] with a base value of 400 and the size of each
element is 2 Bytes in memory find the address of element arr[5][-1][8] with the help of
row-major order?

Solution:
Given:

Row Subset of an element whose address to be found I = 5

Column Subset of an element whose address to be found J = -1

Block Subset of an element whose address to be found K = 8

Base address B = 400

Storage size of one element store in any array(in Byte) = 2

Lower Limit of row/start row index of matrix LR = 1

Lower Limit of column/start column index of matrix LC = -4

Lower Limit of blocks in matrix LB = 5

Number of column given in the matrix N = Upper Bound – Lower Bound + 1

= 1 – (-4) + 1

=6

Number of blocks given in the matrix R = Upper Bound – Lower Bound + 1

= 10 – 5 + 1

=6

Formula:

Address of[i][j][k] = B + W * {[(I – LR) * N] + [(J – LC)] * R + [K – LB]}

Solution:

Address of[][][] = 400 + 2 * {[(5 – 1) * 6] + [(-1 + 4)]} * 6 + [8 – 5]

= 400 + 2 * ((4 * 6 + 3) * 6 + 3)

= 400 + 2 * (165)

= 730

Column Major Order: To find the address of the element using column-major order, use
the following formula-

Address of[i][j][k] = B + W * {[(I – LR) + (J – LC) * M]* R + [K – LB]}

I = Row Subset of an element whose address to be found,


J = Column Subset of an element whose address to be found,

K = Block Subset of an element whose address to be found

B = Base address,

W = Storage size of one element store in any array(in byte),

LR = Lower Limit of row/start row index of matrix(If not given assume it as zero),

LC = Lower Limit of column/start column index of matrix(If not given assume it as zero),

LB = Lower Limit of blocks in matrix,

M = Number of rows given in the matrix,

R = Number of blocks given in the matrix.

Example: Given an array arr[1:8, -5:5, -10:5] with a base value of 400 and the size of each
element is 4 Bytes in memory find the address of element arr[3][3][3] with the help of
column-major order?

Solution:

Given:

Row Subset of an element whose address to be found I = 3

Column Subset of an element whose address to be found J = 3

Block Subset of an element whose address to be found K = 3

Base address B = 400

Storage size of one element store in any array(in Byte) = 4

Lower Limit of row/start row index of matrix LR = 1

Lower Limit of column/start column index of matrix LC = -5

Lower Limit of blocks in matrix LB = -10

Number of rows given in the matrix M = Upper Bound – Lower Bound + 1

=8–1+1

=8

Number of blocks given in the matrix R = Upper Bound – Lower Bound + 1


= 5 + 10 + 1

= 16

Formula:

Address of[i][j][k] = B + W * {[(I – LR)] + [(J – LC) * M]* R + [K – LB]}

Solution:

Address of[3][3][3] = 400 + 4 * {[(3 – 1)] + [3 + 5] * 8]} * 16 + [3 + 10]

= 400 + 4 * ((2 + 64) * 16 + 13)

= 400 + 4 * (1069)

= 400 + 4276

= 4676

Lower Triangular Matrix


A Lower triangular Matrix is a square matrix in which the lower triangular part of a matrix
consists of non-zero elements and the upper triangular part consists of 0s. The Lower
triangular Matrix for a 2D matrix Mat[][] is mathematically defined as:

A Lower triangular Matrix is a square matrix in which the lower triangular part of a matrix
consists of non-zero elements and the upper triangular part consists of 0s. The Lower
triangular Matrix for a 2D matrix Mat[][] is mathematically defined as:

• If i < j, set Mat[i][j] = 0.


• If i >= j, set Mat[i][j] > 0.
Illustration: Below is a 5×5 lower triangular matrix. In general, such matrices can be stored
in a 2D array, but when it comes to matrices of large size, it is not a good choice because of
its high memory consumption due to the storage of unwanted 0s.

Such a matrix can be implemented in an optimized manner.

The efficient way to store the Lower triangular Matrix of size N:

Count of non-zero elements = 1 + 2 + 3 + … + N = N * (N + 1) /2.

Count of 0s = N2 – (N * (N + 1) /2 = (N * (N – 1)/2.
Now let us see how to represent lower triangular matrices in our program. Notice that
storing 0s must be avoided to reduce memory consumption. As calculated, for storing
non-zero elements, N*(N + 1)/2 space is needed. Taking the above example, N = 5. Array
of size 5 * (5 + 1)/2 = 15 is required to store the non-zero elements.

Now, elements of the 2D matrix can be stored in a 1D array, row by row, as shown below:

Apart from storing the elements in an array, a procedure for extracting the element
corresponding to the row and column number is also required.

Using Row major order for storing lower triangular matrix, the element at index Mat[i][j]
can be represented as:

Index of Mat[i][j] matrix in the array A[] = [i*(i – 1)/2 + j – 1]

Linked List Construction

What is Linked List?


• Pointers
• Dynamic
• Insertion & Deletion
• Random Access
• Sequential Access

Like arrays, Linked List is a linear data structure. Unlike arrays, linked list elements are not stored
at a contiguous location; the elements are linked using pointers. They includes a series of
connected nodes. Here, each node stores the data and the address of the next node.
Representation:
A linked list is represented by a pointer to the first node of the linked list. The first node is called
the head. If the linked list is empty, then the value of the head points to NULL.

Each node in a list consists of at least two parts:


1. A Data Item (we can store integer, strings or any type of data).
2. Pointer (Or Reference) to the next node (connects one node to another) or An address of
another node
In C, we can represent a node using structures. Below is an example of a linked list node with
integer data.
// A linked list node
struct Node {
int data;
struct Node* next;
};

Void main(){
int i ; //data
Struct node temp;
struct node *head;
head=temp;
head.data=i;
head.next=NULL;
}

Addition of Node at the Beginning


The new node is always added before the head of the given Linked List. And the newly added
node becomes the new head of the Linked List. For example, if the given Linked List is 10->15-
>20->25 and we add an item 5 at the front, then the Linked List becomes 5->10->15->20->25. Let
us call the function that adds at the front of the list is push(). The push() must receive a pointer
to the head pointer, because push must change the head pointer to point to the new node.

/* Given a reference (pointer to pointer) to the head of a list


and an int, inserts a new node on the front of the list. */
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));
/* 2. put in the data */
new_node->data = new_data;

/* 3. Make next of new node as head */


new_node->next = (*head_ref);

/* 4. move the head to point to the new node */


(*head_ref) = new_node;
}

Addition of Node at the End


The new node is always added after the last node of the given Linked List. For example if the
given Linked List is 5->10->15->20->25 and we add an item 30 at the end, then the Linked List
becomes 5->10->15->20->25->30.
Since a Linked List is typically represented by the head of it, we have to traverse the list till the
end and then change the next to last node to a new node.

Following are the 6 steps to add node at the end.


/* Given a reference (pointer to pointer) to the head
of a list and an int, appends a new node at the end */
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*) malloc(sizeof(struct Node));

struct Node *last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so make next


of it as NULL*/
new_node->next = NULL;
/* 4. If the Linked List is empty, then make the new node as head */
if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


while (last->next != NULL)
last = last->next;

/* 6. Change the next of last node */


last->next = new_node;
return;
}

Time complexity of append is O(n) where n is the number of nodes in the linked list. Since there
is a loop from head to end, the function does O(n) work.
This method can also be optimized to work in O(1) by keeping an extra pointer to the tail of the
linked list.

Addition of Node at Given Position


We are given a pointer to a node, and the new node is inserted after the given node.
/* Given a node prev_node, insert a new node after the given
prev_node */
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;
/* 4. Make next of new node as next of prev_node */
new_node->next = prev_node->next;

/* 5. move the next of prev_node as new_node */


prev_node->next = new_node;
}

Time complexity of insertAfter() is O(n) as it depends on n where n is the size of the linked list.

Space complexity: O(1) since using constant space to modify pointers

Delete from Beginning:


Point head to the next node i.e. second node
temp = head

head = head->next

Make sure to free unused memory


free(temp); or delete temp;

Delete from End:


Point head to the previous element i.e. last second element

Change next pointer to null


struct node *end = head;
struct node *prev = NULL;
while(end->next)
{
prev = end;
end = end->next;
}
prev->next = NULL;

Make sure to free unused memory


free(end); or delete end;

Delete from Middle:


Keeps track of pointer before node to delete and pointer to node to delete
temp = head;
prev = head;
for(int i = 0; i < position; i++)
{
if(i == 0 && position == 1)
head = head->next;
free(temp)
else
{
if (i == position - 1 && temp)
{
prev->next = temp->next;
free(temp);
}
else
{
prev = temp;
if(prev == NULL) // position was greater than number of nodes in
the list
break;
temp = temp->next;
}
}
}

Linked List Operation

To find out length of a linked list

Iterative Solution

1) Initialize count as 0

2) Initialize a node pointer, current = head.


3) Do following while current is not NULL

a) current = current -> next

b) count++;

4) Return count

Following are the Iterative implementations of the above algorithm to find the count of nodes in a given
singly linked list.
// Iterative C program to find length or count of nodes in a linked list
#include<stdio.h>
#include<stdlib.h>

/* Link list node */


struct Node
{
int data;
struct Node* next;
};

/* Given a reference (pointer to pointer) to the head


of a list and an int, push a new node on the front
of the list. */
void push(struct Node** head_ref, int new_data)
{
/* allocate node */
struct Node* new_node =
(struct Node*) malloc(sizeof(struct Node));

/* put in the data */


new_node->data = new_data;

/* link the old list off the new node */


new_node->next = (*head_ref);

/* move the head to point to the new node */


(*head_ref) = new_node;
}

/* Counts no. of nodes in linked list */


int getCount(struct Node* head)
{
int count = 0; // Initialize count
struct Node* current = head; // Initialize current
while (current != NULL)
{
count++;
current = current->next;
}
return count;
}

/* Driver program to test count function*/


int main()
{
/* Start with the empty list */
struct Node* head = NULL;

/* Use push() to construct below list


1->2->1->3->1 */
push(&head, 1);
push(&head, 3);
push(&head, 1);
push(&head, 2);
push(&head, 1);

/* Check the count function */


printf("count of nodes is %d", getCount(head));
return 0;
}

Output

count of nodes is 5

Time complexity: O(n)

Where n is the size of the linked list, and we have to traverse the list only once.

Auxiliary Space: O(1)

As constant extra space is used.


Checking Cycles in a Linked List

Given a linked list, check if the linked list has loop or not. Below diagram shows a linked list with a loop.

• Traverse linked list using two pointers.


• Move one pointer(slow_p) by one and another pointer(fast_p) by two.
• If these pointers meet at the same node then there is a loop. If pointers do not meet then linked
list doesn’t have a loop.
The below image shows how the detect loop function works in the code:

// C program to detect loop in a linked list


#include <stdio.h>
#include <stdlib.h>

/* Link list node */


struct Node {
int data;
struct Node* next;
};

void push(struct Node** head_ref, int new_data)


{
/* allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

/* put in the data */


new_node->data = new_data;

/* link the old list off the new node */


new_node->next = (*head_ref);

/* move the head to point to the new node */


(*head_ref) = new_node;
}

int detectLoop(struct Node* list)


{
struct Node *slow_p = list, *fast_p = list;
while (slow_p && fast_p && fast_p->next) {
slow_p = slow_p->next;
fast_p = fast_p->next->next;
if (slow_p == fast_p) {
return 1;
}
}
return 0;
}

/* Driver program to test above function*/


int main()
{
/* Start with the empty list */
struct Node* head = NULL;

push(&head, 20);
push(&head, 4);
push(&head, 15);
push(&head, 10);

/* Create a loop for testing */


head->next->next->next->next = head;

if (detectLoop(head))
printf("Loop found");
else
printf("No Loop");
return 0;
}

Output

Loop found

Complexity Analysis:

Time complexity: O(n).


Only one traversal of the loop is needed.

Auxiliary Space: O(1).


There is no space required.
Marked as Read
Types of linked List
Disadvantages Of Linked List:
Memory usage: More memory is required in the linked list as compared to an array. Because in
a linked list, a pointer is also required to store the address of the next element and it requires
extra memory for itself.
Traversal: In a Linked List Traversal is more time-consuming as compared to an array. Direct
access to an element is not possible in a linked list as in an array by index. For example, for
accessing a node at position n, one has to traverse all the nodes before it.

Reverse Traversing: In a singly linked list reverse traversing is not possible, but in the case of a
doubly linked list, it can be possible as it contains a pointer to the previously connected nodes
with each node. For performing this extra memory is required for the back pointer hence, there
is a wastage of memory.

Random Access: Random access is not possible in a linked list due to its dynamic memory
allocation.

Doubly Linked List


A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together
with next pointer and data which are there in singly linked list.

Following is representation of a DLL node in C language.

/* Node of a doubly linked list */


struct Node {
int data;
struct Node* next; // Pointer to next node in DLL
struct Node* prev; // Pointer to previous node in DLL
};

Following are advantages/disadvantages of doubly linked list over singly linked list.

Advantages over singly linked list


1) A DLL can be traversed in both forward and backward direction.
2) The delete operation in the DLL is more efficient if a pointer to the node to be deleted is given.
3) We can quickly insert a new node before a given node.
In a singly linked list, to delete a node, a pointer to the previous node is needed. To get this
previous node, sometimes the list is traversed. In the DLL, we can get the previous node using
the previous pointer.

Disadvantages over singly linked list


1) Every node of the DLL requires extra space for a previous pointer. It is possible to implement
a DLL with a single pointer though.
2) All operations require an extra previous pointer to be maintained. For example, in insertion,
we need to modify previous pointers together with next pointers. For example in following
functions for insertions at different positions, we need 1 or 2 extra steps to set previous pointer.

Insert at beginning
The new node is always added before the head of the given Linked List. And newly added node
becomes the new head of DLL. For example if the given Linked List is 10152025 and we add an
item 5 at the front, then the Linked List becomes 510152025. Let us call the function that adds at
the front of the list is push(). The push() must receive a pointer to the head pointer, because push
must change the head pointer to point to the new node .

Following are the 5 steps to add node at the front.


/* Given a reference (pointer to pointer) to the head of a list
and an int, inserts a new node on the front of the list. */
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

/* 2. put in the data */


new_node->data = new_data;

/* 3. Make next of new node as head and previous as NULL */


new_node->next = (*head_ref);
new_node->prev = NULL;

/* 4. change prev of head node to new node */


if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;

/* 5. move the head to point to the new node */


(*head_ref) = new_node;
}

Delete at beginning

After the deletion of the middle node.

After the deletion of the last node.

All three mentioned cases can be handled in two steps if the pointer of the node to be deleted
and the head pointer is known.

If the node to be deleted is the head node then make the next node as head.
If a node is deleted, connect the next and previous node of the deleted node.

Algorithm:
Let the node to be deleted be del.
If node to be deleted is head node, then change the head pointer to next current head.
if headnode == del then
headnode = del.nextNode
Set prev of next to del, if next to del exists.
if del.nextNode != none
del.nextNode.previousNode = del.previousNode

Set next of previous to del, if previous to del exists.


if del.previousNode != none
del.previousNode.nextNode = del.next

Below is the implementation of the above approach:


#include <stdio.h>
#include <stdlib.h>

/* a node of the doubly linked list */


struct Node {
int data;
struct Node* next;
struct Node* prev;
};/* Function to delete a node in a Doubly Linked List.
head_ref --> pointer to head node pointer.
del --> pointer to node to be deleted. */
void deleteNode(struct Node** head_ref, struct Node* del)
{
/* base case */
if (*head_ref == NULL || del == NULL)
return;

/* If node to be deleted is head node */


if (*head_ref == del)
*head_ref = del->next;

/* Change next only if node to be deleted is NOT the last node */


if (del->next != NULL)
del->next->prev = del->prev;

/* Change prev only if node to be deleted is NOT the first node */


if (del->prev != NULL)
del->prev->next = del->next;

/* Finally, free the memory occupied by del*/


free(del);
return;
}

Output
Original Linked list 10 8 4 2

Modified Linked list 8


Complexity Analysis:
Time Complexity: O(1).
Since traversal of the linked list is not required so the time complexity is constant.

Auxiliary Space: O(1).


As no extra space is required, so the space complexity is constant.

Circular Linked List


A circular linked list is a linked list where all nodes are connected to form a circle. There is no
NULL at the end. A circular linked list can be a singly circular linked list or doubly circular linked
list.

Circular Doubly Linked List


Circular Doubly Linked List has properties of both doubly linked list and circular linked list in which
two consecutive elements are linked or connected by previous and next pointer and the last node
points to first node by next pointer and also the first node points to last node by the previous
pointer.
Following is representation of a Circular doubly linked list node in C/C++:
// Structure of the node
struct node
{
int data;
struct node *next; // Pointer to next node
struct node *prev; // Pointer to previous node
};
Questions on Array and Linked List

1.In a circular linked list organization, insertion of a record involves modification of


1. One pointer.
2. Two pointers.
3. Multiple pointers.
4. No pointer.
Ans B
Suppose we have to insert node p after node q then

• p→next=q→next
• q→next=p
So, two pointers,

Answer is (B).
2.Linked Lists are not suitable data structures for which of the following problems?

A.Insertion sort
B.Binary search
C.Radix sort
D.Polynomial manipulation

Ans B
Linked lists are suitable for:
Insertion sort: No need to swap here just find appropriate place and join the link
Polynomial manipulation: Linked List is a natural solution for polynomial manipulation

Radix sort: Here we are putting digits according to same position(unit,tens) into buckets; which
can be effectively handled by linked lists.
Not Suitable for:
Binary search: Because finding mid element itself takes O(n) time.

3.In the worst case, the number of comparisons needed to search a singly linked list of length n
for a given element is
(A) log2 n
(B) n/2
(C) log2n – 1
(D) n
Answer: (D)
Explanation: Singly linked list has uni – directional flow, i.e., it has only one pointer for moving
(the next pointer).

In the worst case, for searching an element in the singly linked list, we will have to traverse the
whole list (the case when the required element is either the last element or is not present in the
list).
So, in the worst case for a list of length n, we will have to go to each node for comparison and
thus, we would be needing ‘n’ comparisons.
4.Consider the function f defined below.
struct item
{
int data;
struct item * next;
};
int f(struct item *p)
{
return ((p == NULL) || (p->next == NULL) ||
((P->data <= p->next->data) &&
f(p->next)));
}
For a given linked list p, the function f returns 1 if and only if
(A) the list is empty or has exactly one element
(B) the elements in the list are sorted in non-decreasing order of data value
(C) the elements in the list are sorted in non-increasing order of data value
(D) not all elements in the list have the same data value.

Answer: (B)
The function checks if the current element is less than or equal to the next element, and
recursively applies the same check to the next element. If the end of the list is reached (i.e., p-
>next is NULL), or the next element is less than the current element, the function returns 1.
Otherwise, it returns 0. Therefore, the function returns 1 only if the linked list is sorted in non-
decreasing order.
5.Let P be a singly linked list. Let Q be the pointer to an intermediate node x in the list. What is
the worst-case time complexity of the best known algorithm to delete the node Q from the list?
(A)O(n)

(B)O(log2 n)
(C)O(logn)
(D)O(1)

Answer: (A)

Explanation:
To delete a node Q from a singly linked list, we need to modify the next pointer of the node that
precedes Q to point to the node that follows Q. However, in a singly linked list, we cannot traverse
the list backwards from a given node, so we need to start from the beginning of the list to find
the node that precedes Q.

Therefore, the worst-case time complexity of the best known algorithm to delete the node Q
from the list is O(n), where n is the length of the list. This is because we may need to traverse the
entire list to find the node that precedes Q.
However, if we have a pointer to the node that precedes Q, the deletion operation can be
performed in O(1) time complexity, since we can simply modify the next pointer of that node to
skip over Q. But, if we do not have a pointer to the node that precedes Q, we need to start from
the beginning of the list and traverse the list to find it, which takes O(n) time complexity in the
worst case.

6.An n x n array v is defined as follows:


v[i, j] = i-j for all i, j, 1 <= i <= n, 1 <= j <= n

The sum of the elements of the array v is


(A) 0
(B) n-1
(C) n2 – 3n + 2
(D) n2 (n+1)/2

Answer: (A)

Explanation: In this case, the matrix would be


0 -1 -2 -3 -4 -5 -6 -7 ... -n
1 0 -1 -2 -3 -4 -5 -6 ... -(n-1)
2 1 0 -1 -2 -3 -4 -5 ... -(n-2)
3 2 1 0 -1 -2 -3 -4 ... -(n-3)
4 3 2 1 0 -1 -2 -3 ... -(n-4)
5 4 3 2 1 0 -1 -2 ... -(n-5)
6 5 4 3 2 1 0 -1 ... -(n-6)
7 6 5 4 3 2 1 0 ... -(n-6)
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
n n-1 n-2 n-3 n-4 n-5 n-6 n-7 ... 2 1

Now, we take the sum of first row and first column, which comes out to be zero. Similarly, we
take the sum of second row and second column, third row and third column, and so on, and it is
found that all have a sum equal to zero.
So, the sum of all the elements in the matrix is zero.

Thus, A is the correct choice.


7.Suppose you are given an array s[1..n] and a procedure reverse (s, i, j) which reverses the order
of elements in a between positions i and j (both inclusive). What does the following sequence do,
where 1 <= k <= n:
reverse(s, 1, k) ;
reverse(s, k + 1, n);
reverse(s, l, n);

(A) Rotates s left by k positions


(B) Leaves s unchanged
(C) Reverses all elements of s
(D) None of the above

Answer: (A)

Explanation: Effect of the above 3 reversals for any k is equivalent to left rotation of the array of
size n by k. Please see this post for details.
If we rotate an array n times for k = 1 to n, we get the same array back.
Report An Issue

as Read

Hashing 1

What is Hashing?
Hashing is a technique or process of mapping keys, and values into the hash table by
using a hash function. It is done for faster access to elements. The efficiency of mapping
depends on the efficiency of the hash function used.

Let a hash function H(x) maps the value x at the index x%10 in an Array. For example if
the list of values is [11,12,13,14,15] it will be stored at positions {1,2,3,4,5} in the array or
Hash table respectively.
Components of Hashing

• Key space (n)


• Hash Table or Bucket (m)
• Hash Function
• Collision Resolution Technique (C.R.T.)

Direct Address Table


Direct Address Table is a data structure that has the capability of 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.

We can understand the concept using the following example. We create an array of size
equal to maximum value plus one (assuming 0 based index) and then use values as
indexes. For example, in the following diagram key 21 is used directly as index.

Advantages:
1. Searching in O(1) Time: Direct address tables use arrays which are random access
data structure, so, the key values (which are also the index of the array) can be
easily used to search the records in O(1) time.
2. Insertion in O(1) Time: We can easily insert an element in an array in O(1) time. The
same thing follows in a direct address table also.
3. Deletion in O(1) Time: Deletion of an element takes O(1) time in an array. Similarly,
to delete an element in a direct address table we need O(1) time.
Limitations:
1. Prior knowledge of maximum key value
2. Practically useful only if the maximum value is very less.
3. It causes wastage of memory space if there is a significant difference between total
records and maximum value.
4. Hashing can overcome these limitations of direct address tables.
Hash Function: Division Modulo Method

This is the most simple and easiest method to generate a hash value. The hash function
divides the value k by M and then uses the remainder obtained.

Formula:
h(K) = k mod M
Here,
k is the key value, and
M is the size of the hash table.

It is best suited that M is a prime number as that can make sure the keys are more
uniformly distributed. The hash function is dependent upon the remainder of a division.

Example:

k = 12345

M = 95

h(12345) = 12345 mod 95

= 90

k = 1276

M = 11
h(1276) = 1276 mod 11

=0

Pros:

• This method is quite good for any value of M.


• The division method is very fast since it requires only a single division operation.
Cons:

• This method leads to poor performance since consecutive keys map to consecutive
hash values in the hash table.
Sometimes extra care should be taken to choose the value of M.

Mid Square Method:


The mid-square method is a very good hashing method. It involves two steps to compute
the hash value-

Square the value of the key k i.e. k2

Extract the middle r digits as the hash value.

Formula:
h(K) = h(k x k)
Here,
k is the key value.

The value of r can be decided based on the size of the table.

Example:

Suppose the hash table has 100 memory locations. So r = 2 because two digits are
required to map the key to the memory location.

k = 60

k x k = 60 x 60

= 3600

h(60) = 60
The hash value obtained is 60

Pros:
• The performance of this method is good as most or all digits of the key value
contribute to the result. This is because all digits in the key contribute to generating
the middle digits of the squared result.
• The result is not dominated by the distribution of the top digit or bottom digit of
the original key value.
Cons:

• The size of the key is one of the limitations of this method, as the key is of big size
then its square will double the number of digits.
• Another disadvantage is that there will be collisions but we can try to reduce
collisions.

Digit Folding Method:


This method involves two steps:

Divide the key-value k into a number of parts i.e. k1, k2, k3,….,kn, where each part has the
same number of digits except for the last part that can have lesser digits than the other
parts.

Add the individual parts. The hash value is obtained by ignoring the last carry if any.

Formula:
k = k1, k2, k3, k4, ….., kn
s = k1+ k2 + k3 + k4 +….+ kn
h(K)= s
Here,
s is obtained by adding the parts of the key k

Example:

k = 12345

k1 = 12, k2 = 34, k3 = 5

s = k1 + k2 + k3

= 12 + 34 + 5

= 51

h(K) = 51

Note:
The number of digits in each part varies depending upon the size of the hash table.
Suppose for example the size of the hash table is 100, then each part must have two digits
except for the last part which can have a lesser number of digits.

Hashing 2

The purpose of collision resolution during insertion is to locate an open location in the
hash table when the record’s home position is already taken. Any collision resolution
technique may be thought of as creating a series of hash table slots that may or may not
contain the record. The key will be in its home position in the first position in the sequence.
The collision resolution policy shifts to the following location in the sequence if the home
position is already occupied. Another slot needs to be sought if this is also taken, and so
on. The probe sequence is a collection of slots that is produced by a probe function that
we will refer to as p. This is how insertion operates.

Collision in Hashing
In this, the hash function is used to find the index of the array. The hash value is used to
create an index for the key in the hash table. The hash function may return the same hash
value for two or more keys. When two or more keys have the same hash value, a collision
happens. To handle this collision, we use collision resolution techniques.

Collision Resolution Techniques


There are two types of collision resolution techniques.

• Separate chaining (open hashing)


• Open addressing (closed hashing)
Separate chaining: This method involves making a linked list out of the slot where the
collision happened, then adding the new key to the list. Separate chaining is the term
used to describe how this connected list of slots resembles a chain. It is more frequently
utilized when we are unsure of the number of keys to add or remove.

Time complexity
• Its worst-case complexity for searching is o(n).
• Its worst-case complexity for deletion is o(n).
Advantages of separate chaining

• It is easy to implement.
• The hash table never fills full, so we can add more elements to the chain.
• It is less sensitive to the function of the hashing.
Disadvantages of separate chaining

• In this, the cache performance of chaining is not good.


• Memory wastage is too much in this method.
• It requires more space for element links.
Open addressing: To prevent collisions in the hashing table open, addressing is
employed as a collision-resolution technique. No key is kept anywhere else besides the
hash table. As a result, the hash table’s size is never equal to or less than the number of
keys. Additionally known as closed hashing.

The following techniques are used in open addressing:

• Linear probing
• Quadratic probing
• Double hashing
Linear probing: This involves doing a linear probe for the following slot when a collision
occurs and continuing to do so until an empty slot is discovered.
The worst time to search for an element in linear probing is O. The cache performs best
with linear probing, but clustering is a concern. This method’s key benefit is that it is
simple to calculate.

Disadvantages of linear probing:

• The main problem is clustering.


• It takes too much time to find an empty slot.
Quadratic probing: When a collision happens in this, we probe for the i2-nd slot in the
ith iteration, continuing to do so until an empty slot is discovered. In comparison to linear
probing, quadratic probing has a worse cache performance. Additionally, clustering is less
of a concern with quadratic probing.

Double hashing: In this, you employ a different hashing algorithm, and in the i th iteration,
you look for (i * hash 2(x)). The determination of two hash functions requires more time.
Although there is no clustering issue, the performance of the cache is relatively poor when
using double probing.
Challenges in Linear Probing :
• Primary Clustering: One of the problems with linear probing is Primary clustering,
many consecutive elements form groups and it starts taking time to find a free slot
or to search for an element.
• Secondary Clustering: Secondary clustering is less severe, two records only have
the same collision chain (Probe Sequence) if their initial position is the same.
Example: Let us consider a simple hash function as “key mod 5” and a sequence of keys
that are to be inserted are 50, 70, 76, 93.

• Step1: First draw the empty hash table which will have a possible range of hash
values from 0 to 4 according to the hash function provided.

• Step 2: Now insert all the keys in the hash table one by one. The first key is 50. It
will map to slot number 0 because 50%5=0. So insert it into slot number 0.
• Step 3: The next key is 70. It will map to slot number 0 because 70%5=0 but 50 is
already at slot number 0 so, search for the next empty slot and insert it.

• Step 4: The next key is 76. It will map to slot number 1 because 76%5=1 but 70 is
already at slot number 1 so, search for the next empty slot and insert it.
• Step 5: The next key is 93 It will map to slot number 3 because 93%5=3, So insert
it into slot number 3.

Quadratic Probing
If you observe carefully, then you will understand that the interval between probes will
increase proportionally to the hash value. Quadratic probing is a method with the help of
which we can solve the problem of clustering that was discussed above. This method is
also known as the mid-square method. In this method, we look for the i2‘th slot in the ith
iteration. We always start from the original hash location. If only the location is occupied
then we check the other slots.

let hash(x) be the slot index computed using hash function.

If slot hash(x) % S is full, then we try (hash(x) + 1*1) % S


If (hash(x) + 1*1) % S is also full, then we try (hash(x) + 2*2) % S
If (hash(x) + 2*2) % S is also full, then we try (hash(x) + 3*3) % S
…………………………………………..
…………………………………………..
Example: Let us consider table Size = 7, hash function as Hash(x) = x % 7 and collision
resolution strategy to be f(i) = i2 . Insert = 22, 30, and 50.

• Step 1: Create a table of size 7.

• Step 2 – Insert 22 and 30


o Hash(22) = 22 % 7 = 1, Since the cell at index 1 is empty, we can easily insert
22 at slot 1.
o Hash(30) = 30 % 7 = 2, Since the cell at index 2 is empty, we can easily insert
30 at slot 2.
• Step 3: Inserting 50
o Hash(50) = 50 % 7 = 1
o In our hash table slot 1 is already occupied. So, we will search for slot 1+1 2,
i.e. 1+1 = 2,
o Again slot 2 is found occupied, so we will search for cell 1+2 2, i.e.1+4 = 5,
o Now, cell 5 is not occupied so we will place 50 in slot 5.
Marked as Read

Stack Definition

What is Stack?
Stack is a linear data structure which follows a particular order in which the operations are
performed. The order may be LIFO(Last In First Out) or FILO(First In Last Out).

There are many real-life examples of a stack. Consider an example of plates stacked over
one another in the canteen. The plate which is at the top is the first one to be removed,
i.e. the plate which has been placed at the bottommost position remains in the stack for
the longest period of time. So, it can be simply seen to follow LIFO(Last In First
Out)/FILO(First In Last Out) order.

Application of Stack
• Recursive Calls
• Balanced Parenthesis
• HTML Tag
• Infix to Postfix
• Tower of Hanoi
• Postfix Evaluation
• Fibonacci Series
Stack ADT
• In Stack ADT Implementation instead of data being stored in each node, the
pointer to data is stored.
• The program allocates memory for the data and address is passed to the stack ADT.
• The head node and the data nodes are encapsulated in the ADT. The calling
function can only see the pointer to the stack.
• The stack head structure also contains a pointer to top and count of number of
entries currently in stack.
• push() – Insert an element at one end of the stack called top.
• pop() – Remove and return the element at the top of the stack, if it is not empty.
• peek() – Return the element at the top of the stack without removing it, if the stack
is not empty.
• size() – Return the number of elements in the stack.
• isEmpty() – Return true if the stack is empty, otherwise return false.
• isFull() – Return true if the stack is full, otherwise return false.

Array Implementation of Stack


In array implementation, the stack is formed by using the array. All the operations
regarding the stack are performed using arrays. Lets see how each operation can be
implemented on the stack using array data structure.
Adding an element onto the stack (push operation)
Adding an element into the top of the stack is referred to as push operation. Push
operation involves following two steps.

Increment the variable Top so that it can now refere to the next memory location.

Add element at the position of incremented top. This is referred to as adding new element
at the top of the stack.

Stack is overflown when we try to insert an element into a completely filled stack
therefore, our main function must always avoid stack overflow condition.
Algorithm:
begin
if top = n then stack full
top = top + 1
stack (top) : = item;
end

Deletion of an element from a stack (Pop operation)


Deletion of an element from the top of the stack is called pop operation. The value of the
variable top will be incremented by 1 whenever an item is deleted from the stack. The top
most element of the stack is stored in an another variable and then the top is decremented
by 1. the operation returns the deleted value that was stored in another variable as the
result.

The underflow condition occurs when we try to delete an element from an already empty
stack.
Algorithm :
begin
if top = 0 then stack empty;
item := stack(top);
top = top - 1;
end;

Implement two stacks in an array


Create a data structure twoStacks that represents two stacks. Implementation of twoStacks
should use only one array, i.e., both stacks should use the same array for storing elements.

Following functions must be supported by twoStacks.

• push1(int x) –> pushes x to first stack


• push2(int x) –> pushes x to second stack
• pop1() –> pops an element from first stack and return the popped element
• pop2() –> pops an element from second stack and return the popped element
Implementation of twoStack should be space efficient.
Method 1 (Divide the space in two halves):
A simple way to implement two stacks is to divide the array in two halves and assign the
half space to two stacks, i.e., use arr[0] to arr[n/2] for stack1, and arr[(n/2) + 1] to arr[n-1]
for stack2 where arr[] is the array to be used to implement two stacks and size of array be
n.

The problem with this method is inefficient use of array space. A stack push operation
may result in stack overflow even if there is space available in arr[]. For example, say the
array size is 6 and we push 3 elements to stack1 and do not push anything to second
stack2. When we push 4th element to stack1, there will be overflow even if we have space
for 3 more elements in array.

Method 2 (A space efficient implementation) :


This method efficiently utilizes the available space. It doesn’t cause an overflow if there is
space available in arr[]. The idea is to start two stacks from two extreme corners of arr[].
stack1 starts from the leftmost element, the first element in stack1 is pushed at index 0.
The stack2 starts from the rightmost corner, the first element in stack2 is pushed at index
(n-1). Both stacks grow (or shrink) in opposite direction. To check for overflow, all we need
to check is for space between top elements of both stacks.
Applications of Stack

Linked List Implementation

To implement a stack using singly linked list concept , all the singly linked list operations
are performed based on Stack operations LIFO(last in first out) and with the help of that
knowledge we are going to implement a stack using single linked list. Using singly linked
lists , we implement stack by storing the information in the form of nodes and we need
to follow the stack rules and implement using singly linked list nodes . So we need to
follow a simple rule in the implementation of a stack which is last in first out and all the
operations can be performed with the help of a top variable .

A stack can be easily implemented using the linked list. In stack Implementation, a stack
contains a top pointer. which is “head” of the stack where pushing and popping items
happens at the head of the list. First node have null in link field and second node link have
first node address in link field and so on and last node address in “top” pointer.

The main advantage of using linked lists over arrays is that it is possible to implement a
stack that can shrink or grow as much as needed. Using an array will put a restriction to
the maximum capacity of the array which can lead to stack overflow. Here each new node
will be dynamically allocated. so overflow is not possible.

Stack Operations:
1. push() : Insert a new element into stack i.e just inserting a new element at the
beginning of the linked list.
2. pop(): Return top element of the Stack i.e simply deleting the first element from
the linked list.
Push
void push(int data)
{

// Create new node temp and allocate memory in heap


Node* temp = new Node();

// Check if stack (heap) is full.


// Then inserting an element would
// lead to stack overflow
if (!temp)
{
cout << "\nStack Overflow";
exit(1);
}

// Initialize data into temp data field


temp->data = data;

// Put top pointer reference into temp link


temp->link = top;

// Make temp as top of Stack


top = temp;
}

Pop

void pop()
{
Node* temp;

// Check for stack underflow


if (top == NULL)
{
cout << "\nStack Underflow" << endl;
exit(1);
}
else
{

// Assign top to temp


temp = top;

// Assign second node to top


top = top->link;

//This will automatically destroy


//the link between first node and second node

// Release memory of top node


//i.e delete the node
free(temp);
}
}
What is Infix, Prefix, Postfix?
Infix expression: The expression of the form a op b. When an operator is in-between every
pair of operands.

Postfix expression: The expression of the form a b op. When an operator is followed for
every pair of operands.

Example : AB+CD-* (Infix : (A+B * (C-D) )

Prefix: An expression is called the prefix expression if the operator appears in the
expression before the operands. Simply of the form (operator operand1 operand2).

Example : *+AB-CD (Infix : (A+B) * (C-D) )

Infix to Prefix and Postfix


Why postfix representation of the expression?

The compiler scans the expression either from left to right or from right to left.

Consider the below expression: a op1 b op2 c op3 d

i.e : a + b * c + d

The compiler first scans the expression to evaluate the expression b * c, then again scans
the expression to add a to it. The result is then added to d after another scan.

The repeated scanning makes it very in-efficient. It is better to convert the expression to
postfix(or prefix) form before evaluation.

The corresponding expression in postfix form is abc*+d+. The postfix expressions can be
evaluated easily using a stack. We will cover postfix expression evaluation in a separate
post.

Algorithm

1. Scan the infix expression from left to right.

2. If the scanned character is an operand, output it.

3. Else,
• If the precedence and associativity of the scanned operator is greater than the
precedence and associativity of the operator in the stack(or the stack is empty or
the stack contains a ‘(‘ ), push it.
• ‘^’ operator is right associative and other operators like ‘+’,’-‘,’*’ and ‘/’ are left
associative. Check especially for a condition when both top of the operator stack
and scanned operator are ‘^’. In this condition the precedence of the scanned
operator is higher due to it’s right associativity. So it will be pushed in the operator
stack. In all the other cases when the top of the operator stack is the same as the
scanned operator we will pop the operator from the stack because of left
associativity due to which the scanned operator has less precedence.
• Else, Pop all the operators from the stack which are greater than or equal to in
precedence than that of the scanned operator. After doing that Push the scanned
operator to the stack. (If you encounter parenthesis while popping then stop there
and push the scanned operator in the stack.)
4. If the scanned character is an ‘(‘, push it to the stack.

5. If the scanned character is an ‘)’, pop the stack and output it until a ‘(‘ is encountered,
and discard both the parenthesis.

6. Repeat steps 2-6 until the infix expression is scanned.

7. Print the output

8. Pop and output from the stack until it is not empty.

Infix: a+b*(c^d-e)^(f+g*h)-i

Output: abcd^e-fgh*+^*+i-

Infix to Prefix Conversion

Algorithm of Infix to Prefix


Step 1. Push “)” onto STACK, and add “(“ to end of the A

Step 2. Scan A from right to left and repeat step 3 to 6 for each element of A until the
STACK is empty

Step 3. If an operand is encountered add it to B

Step 4. If a right parenthesis is encountered push it onto STACK

Step 5. If an operator is encountered then:


a. Repeatedly pop from STACK and add to B each operator (on the top of STACK) which
has same

or higher precedence than the operator.

b. Add operator to STACK

Step 6. If left parenthesis is encountered then

a. Repeatedly pop from the STACK and add to B (each operator on top of stack until a left
parenthesis is encountered)

b. Remove the left parenthesis

Step 7. Exit

Expression = (A+B^C)*D+E^5

Step 1. Reverse the infix expression.

5^E+D*)C^B+A(

Step 2. Make Every '(' as ')' and every ')' as '('

5^E+D*(C^B+A)

Step 3. Convert expression to postfix form.

A+(B*C-(D/E-F)*G)*H

Prefix to Postfix
Conversion of Prefix expressions directly to Postfix without going through the process of
converting them first to Infix and then to Postfix is much better in terms of computation
and better understanding the expression (Computers evaluate using Postfix expression).

Examples:

Input : Prefix : *+AB-CD

Output : Postfix : AB+CD-*

Explanation : Prefix to Infix : (A+B) * (C-D)

Infix to Postfix : AB+CD-*


Input : Prefix : *-A/BC-/AKL

Output : Postfix : ABC/-AK/L-*

Explanation : Prefix to Infix : (A-(B/C))*((A/K)-L)

Infix to Postfix : ABC/-AK/L-*

Algorithm for Prefix to Postfix:


• Read the Prefix expression in reverse order (from right to left)
• If the symbol is an operand, then push it onto the Stack
• If the symbol is an operator, then pop two operands from the Stack
Create a string by concatenating the two operands and the operator after them.
string = operand1 + operand2 + operator
And push the resultant string back to Stack
• Repeat the above steps until end of Prefix expression.
Postfix to Infix

Examples:

Input : abc++

Output : (a + (b + c))

Input : ab*c+

Output : ((a*b)+c)

Algorithm

1.While there are input symbol left

…1.1 Read the next symbol from the input.

2.If the symbol is an operand

…2.1 Push it onto the stack.

3.Otherwise,

…3.1 the symbol is an operator.


…3.2 Pop the top 2 values from the stack.

…3.3 Put the operator, with the values as arguments and form a string.

…3.4 Push the resulted string back to stack.

4.If there is only one value in the stack

…4.1 That value in the stack is the desired infix string.


Prefix to Infix

Examples:

Input : Prefix : *+AB-CD

Output : Infix : ((A+B)*(C-D))

Input : Prefix : *-A/BC-/AKL

Output : Infix : ((A-(B/C))*((A/K)-L))

Algorithm for Prefix to Infix:

• Read the Prefix expression in reverse order (from right to left)


• If the symbol is an operand, then push it onto the Stack
• If the symbol is an operator, then pop two operands from the Stack
Create a string by concatenating the two operands and the operator between
them.
string = (operand1 + operator + operand2)
And push the resultant string back to Stack
• Repeat the above steps until the end of Prefix expression.
• At the end stack will have only 1 string i.e resultant string

Queue Implementation

What is Queue?

A Queue is a linear structure which follows a particular order in which the operations are
performed. The order is First In First Out (FIFO). A good example of a queue is any queue
of consumers for a resource where the consumer that came first is served first. The
difference between stacks and queues is in removing. In a stack we remove the item the
most recently added; in a queue, we remove the item the least recently added.

• FIFO/LILO
• Insertion-Deletion
• Front-Rear
Application of Queue
• Ticket Reservation
• Job Scheduling
• Printer Spooler Daemon

Queue ADT
Basic Operations on Queue:
• void enqueue(int data): Inserts an element at the end of the queue i.e. at the rear
end.
• dequeue(): This operation removes and returns an element that is at the front end
of the queue.
Auxiliary Operations on Queue:

• front(): This operation returns the element at the front end without removing it.
• rear(): This operation returns the element at the rear end without removing it.
• boolean isEmpty(): This operation indicates whether the queue is empty or not.
• int size(): This operation returns the size of the queue i.e. the total number of
elements it contains.
Implementing Queue with Array
In queue, insertion and deletion happen at the opposite ends, so implementation is not
as simple as stack.

To implement a queue using array, create an array arr of size n and take two variables
front and rear both of which will be initialized to 0 which means the queue is currently
empty. Element rear is the index upto which the elements are stored in the array and front
is the index of the first element of the array. Now, some of the implementation of queue
operations are as follows:
1. Enqueue: Addition of an element to the queue. Adding an element will be
performed after checking whether the queue is full or not. If rear < n which
indicates that the array is not full then store the element at arr[rear] and increment
rear by 1 but if rear == n then it is said to be an Overflow condition as the array is
full.
2. Dequeue: Removal of an element from the queue. An element can only be deleted
when there is at least an element to delete i.e. rear > 0. Now, element at arr[front]
can be deleted but all the remaining elements have to shifted to the left by one
position in order for the dequeue operation to delete the second element from the
left on another dequeue operation.
3. Front: Get the front element from the queue i.e. arr[front] if queue is not empty.
4. Display: Print all element of the queue. If the queue is non-empty, traverse and
print all the elements from index front to rear.

Queue Applications

Circular Queue
A Circular Queue is a special version of queue where the last element of the queue is
connected to the first element of the queue forming a circle.

The operations are performed based on FIFO (First In First Out) principle. It is also called
‘Ring Buffer’.

In a normal Queue, we can insert elements until queue becomes full. But once queue
becomes full, we can not insert the next element even if there is a space in front of queue.
Operations on Circular Queue:
• Front: Get the front item from queue.
• Rear: Get the last item from queue.
• enQueue(value) This function is used to insert an element into the circular queue.
In a circular queue, the new element is always inserted at Rear position.
• Check whether queue is Full – Check ((rear == SIZE-1 && front == 0) || (rear ==
front-1)).
• If it is full then display Queue is full. If queue is not full then, check if (rear == SIZE
– 1 && front != 0) if it is true then set rear=0 and insert element.
• deQueue() This function is used to delete an element from the circular queue. In a
circular queue, the element is always deleted from front position.
• Check whether queue is Empty means check (front==-1).
• If it is empty then display Queue is empty. If queue is not empty then step 3
• Check if (front==rear) if it is true then set front=rear= -1 else check if (front==size-
1), if it is true then set front=0 and return the element.
Implementing Queue using Linked list

Node structure for Queue


struct node
{
int data;
struct node *next;
};

To point the front and rear node


struct node *front = NULL, *rear = NULL;

Enqueue function
Enqueue function will add the element at the end of the linked list.

Using the rear pointer, we can track the last inserted element.

1. Declare a new node and allocate memory for it.

2. If front == NULL,

make both front and rear points to the new node.

3. Otherwise,
add the new node in rear->next.

make the new node as the rear node. i.e. rear = new node
void enqueue(int val)
{
struct node *newNode = malloc(sizeof(struct node));
newNode->data = val;
newNode->next = NULL;

//if it is the first node


if(front == NULL && rear == NULL)
//make both front and rear points to the new node
front = rear = newNode;
else
{
//add newnode in rear->next
rear->next = newNode;

//make the new node as the rear node


rear = newNode;
}
}

Visual representation of the above algorithm


Insert 10

Insert 20

Insert 30

Dequeue function
Dequeue function will remove the first element from the queue.
1.Check whether the queue is empty or not

2.If it is the empty queue (front == NULL)

We can't dequeue the element.

3.Otherwise,

Make the front node points to the next node. i.e front = front->next;

if front pointer becomes NULL, set the rear pointer also NULL.

Free the front node's memory.


void dequeue()
{
//used to freeing the first node after dequeue
struct node *temp;

if(front == NULL)
printf("Queue is Empty. Unable to perform dequeue\n");
else
{
//take backup
temp = front;

//make the front node points to the next node


//logically removing the front element
front = front->next;

//if front == NULL, set rear = NULL


if(front == NULL)
rear = NULL;

//free the first node


free(temp);
}

Implementation of the queue using linked list


/*
* Program : Queue using linked list
* Language : C
*/

#include<stdio.h>
#include<stdlib.h>

struct node
{
int data;
struct node *next;
};

struct node *front = NULL, *rear = NULL;

void enqueue(int val)


{
struct node *newNode = malloc(sizeof(struct node));
newNode->data = val;
newNode->next = NULL;

//if it is the first node


if(front == NULL && rear == NULL)
//make both front and rear points to the new node
front = rear = newNode;
else
{
//add newnode in rear->next
rear->next = newNode;

//make the new node as the rear node


rear = newNode;
}
}

void dequeue()
{
//used to free the first node after dequeue
struct node *temp;
if(front == NULL)
printf("Queue is Empty. Unable to perform dequeue\n");
else
{
//take backup
temp = front;

//make the front node points to the next node


//logically removing the front element
front = front->next;

//if front == NULL, set rear = NULL


if(front == NULL)
rear = NULL;

//free the first node


free(temp);
}

void printList()
{
struct node *temp = front;

while(temp)
{
printf("%d->",temp->data);
temp = temp->next;
}
printf("NULL\n");
}

int main()
{
enqueue(10);
enqueue(20);
enqueue(30);
printf("Queue :");
printList();
dequeue();
printf("After dequeue the new Queue :");
printList();
dequeue();
printf("After dequeue the new Queue :");
printList();

return 0;
}

Implementing Queue using Stack

A queue can be implemented using two stacks. Let queue to be implemented be q and
stacks used to implement q be stack1 and stack2. q can be implemented in two ways:

Method 1 (By making enQueue operation costly):

This method makes sure that oldest entered element is always at the top of stack 1, so
that deQueue operation just pops from stack1. To put the element at top of stack1, stack2
is used.

enQueue(q, x):

• While stack1 is not empty, push everything from stack1 to stack2.


• Push x to stack1 (assuming size of stacks is unlimited).
• Push everything back to stack1.
Here time complexity will be O(n)

deQueue(q):

• If stack1 is empty then error


• Pop an item from stack1 and return it
Here time complexity will be O(1)

Method 2 (By making deQueue operation costly):

In this method, in en-queue operation, the new element is entered at the top of stack1.
In de-queue operation, if stack2 is empty then all the elements are moved to stack2 and
finally top of stack2 is returned.
enQueue(q, x)

1) Push x to stack1 (assuming size of stacks is unlimited).

Here time complexity will be O(1)

deQueue(q)

1) If both stacks are empty then error.

2) If stack2 is empty

While stack1 is not empty, push everything from stack1 to stack2.

3) Pop the element from stack2 and return it.

Here time complexity will be O(n)

Method 2 is definitely better than method 1.

Method 1 moves all the elements twice in enQueue operation, while method 2 (in
deQueue operation) moves the elements once and moves elements only if stack2 empty.
So, the amortized complexity of the dequeue operation becomes O(1).

Implementing Stack using Queue

A stack can be implemented using two queues. Let stack to be implemented be ‘s’ and
queues used to implement be ‘q1’ and ‘q2’. Stack ‘s’ can be implemented in two ways:

Method 1 (By making push operation costly)

This method ensures that the newly entered element is always at the front of ‘q1’ so that
pop operation dequeues from ‘q1’. ‘q2’ is used to put every new element in front of ‘q1’.

1.push(s, x) operation’s steps are described below:

• Enqueue x to q2
• One by one dequeue everything from q1 and enqueue to q2.
• Swap the names of q1 and q2
2.pop(s) operation’s function is described below:

• Dequeue an item from q1 and return it.


Method 2 (By making pop operation costly):

In a push operation, the new element is always enqueued to q1. In pop() operation, if q2
is empty then all the elements except the last, are moved to q2. Finally, the last element
is dequeued from q1 and returned.

1.push(s, x) operation:

• Enqueue x to q1 (assuming size of q1 is unlimited).


2.pop(s) operation:

• One by one dequeue everything except the last element from q1 and enqueue to
q2.
• Dequeue the last item of q1, the dequeued item is result, store it.
• Swap the names of q1 and q2
• Return the item stored in step 2.
Marked as Read
Report An Issue

Trees Part 1

What is a Tree?
A tree is non-linear and a hierarchical data structure consisting of a collection of nodes such
that each node of the tree stores a value and a list of references to other nodes (the
“children”).

This data structure is a specialized method to organize and store data in the computer to
be used more effectively. It consists of a central node, structural nodes, and sub-nodes,
which are connected via edges. We can also say that tree data structure has roots,
branches, and leaves connected with one another.

Implementation of tree
Basic Terminologies In Tree Data Structure:
• Parent Node: The node which is a predecessor of a node is called the parent node
of that node. {2} is the parent node of {6, 7}.
• Child Node: The node which is the immediate successor of a node is called the
child node of that node. Examples: {6, 7} are the child nodes of {2}.
• Root Node: The topmost node of a tree or the node which does not have any
parent node is called the root node. {1} is the root node of the tree. A non-empty
tree must contain exactly one root node and exactly one path from the root to all
other nodes of the tree.
• Leaf Node or External Node: The nodes which do not have any child nodes are
called leaf nodes. {6, 14, 8, 9, 15, 16, 4, 11, 12, 17, 18, 19} are the leaf nodes of the
tree.
• Ancestor of a Node: Any predecessor nodes on the path of the root to that node
are called Ancestors of that node. {1, 2} are the ancestor nodes of the node {7}
• Descendant: Any successor node on the path from the leaf node to that node. {7,
14} are the descendants of the node. {2}.
• Sibling: Children of the same parent node are called siblings. {8, 9, 10} are called
siblings.
• Level of a node: The count of edges on the path from the root node to that node.
The root node has level 0.
• Internal node: A node with at least one child is called Internal Node.
• Neighbour of a Node: Parent or child nodes of that node are called neighbors of
that node.
• Subtree: Any node of the tree along with its descendant.
Properties of a Tree:
• Number of edges: An edge can be defined as the connection between two nodes.
If a tree has N nodes then it will have (N-1) edges. There is only one path from
each node to any other node of the tree.
• Depth of a node: The depth of a node is defined as the length of the path from the
root to that node. Each edge adds 1 unit of length to the path. So, it can also be
defined as the number of edges in the path from the root of the tree to the node.
• Height of a node: The height of a node can be defined as the length of the longest
path from the node to a leaf node of the tree.
• Height of the Tree: The height of a tree is the length of the longest path from the
root of the tree to a leaf node of the tree.
• Degree of a Node: The total count of subtrees attached to that node is called the
degree of the node. The degree of a leaf node must be 0. The degree of a tree is
the maximum degree of a node among all the nodes in the tree.
Some more properties are:

• Traversing in a tree is done by depth first search and breadth first search algorithm.
• It has no loop and no circuit
• It has no self-loop
• Its hierarchical model.
Syntax:
struct Node
{
int data;
struct Node *left_child;
struct Node *right_child;
};

Basic Operation Of Tree:


Create – create a tree in data structure.

Insert − Inserts data in a tree.

Search − Searches specific data in a tree to check it is present or not.

Preorder Traversal – perform Traveling a tree in a pre-order manner in data structure .

In order Traversal – perform Traveling a tree in an in-order manner.

Post order Traversal –perform Traveling a tree in a post-order manner.

Example of Tree data structure

Here,

Node A is the root node

B is the parent of D and E

D and E are the siblings

D, E, F and G are the leaf nodes

A and B are the ancestors of E

Tree Traversals

Unlike linear data structures (Array, Linked List, Queues, Stacks, etc) which have only one
logical way to traverse them, trees can be traversed in different ways. Following are the
generally used ways for traversing trees.
Depth First Traversals:
(a) Inorder (Left, Root, Right) : 4 2 5 1 3

(b) Preorder (Root, Left, Right) : 1 2 4 5 3

(c) Postorder (Left, Right, Root) : 4 5 2 3 1

Breadth-First or Level Order Traversal: 1 2 3 4 5

Inorder Traversal:
Algorithm Inorder(tree)

1. Traverse the left subtree, i.e., call Inorder(left-subtree)

2. Visit the root.

3. Traverse the right subtree, i.e., call Inorder(right-subtree)

Uses of Inorder

In the case of binary search trees (BST), Inorder traversal gives nodes in non-decreasing
order. To get nodes of BST in non-increasing order, a variation of Inorder traversal where
Inorder traversal s reversed can be used.

Example: In order traversal for the above-given figure is 4 2 5 1 3.

Preorder Traversal:
Algorithm Preorder(tree)

1. Visit the root.

2. Traverse the left subtree, i.e., call Preorder(left-subtree)

3. Traverse the right subtree, i.e., call Preorder(right-subtree)

Uses of Preorder

Preorder traversal is used to create a copy of the tree. Preorder traversal is also used to
get prefix expression on an expression tree.

Example: Preorder traversal for the above-given figure is 1 2 4 5 3.

Postorder Traversal:
Algorithm Postorder(tree)
1. Traverse the left subtree, i.e., call Postorder(left-subtree)

2. Traverse the right subtree, i.e., call Postorder(right-subtree)

3. Visit the root.

Uses of Postorder

Postorder traversal is used to delete the tree.


Constructing Unique Trees
Let us consider the below traversals:

• Inorder sequence: D B E A F C
• Preorder sequence: A B D E C F

In a Preorder sequence, the leftmost element is the root of the tree. So we know ‘A’ is the
root for given sequences. By searching ‘A’ in the Inorder sequence, we can find out all
elements on the left side of ‘A’ is in the left subtree and elements on right in the right
subtree. So we know the below structure now.
A
/ \
/ \
D B E F C

We recursively follow the above steps and get the following tree.
A
/ \
/ \
B C
/ \ /
/ \ /
D E F

Given Postorder and Inorder traversals, construct the tree.


Examples:

Input:

in[] = {2, 1, 3}

post[] = {2, 3, 1}
Output: Root of below tree
1
/ \
2 3

Input:

in[] = {4, 8, 2, 5, 1, 6, 3, 7}

post[] = {8, 4, 5, 2, 6, 7, 3, 1}

Output: Root of below tree


1
/ \
2 3
/ \ / \
4 5 6 7
\
8

Let us see the process of constructing tree from in[] = {4, 8, 2, 5, 1, 6, 3, 7} and post[] = {8,
4, 5, 2, 6, 7, 3, 1}

1) We first find the last node in post[]. The last node is “1”, we know this value is root as
the root always appears at the end of postorder traversal.

2) We search “1” in in[] to find the left and right subtrees of the root. Everything on the
left of “1” in in[] is in the left subtree and everything on right is in the right subtree.
1
/ \
[4, 8, 2, 5] [6, 3, 7]

3) We recur the above process for the following two.

….b) Recur for in[] = {6, 3, 7} and post[] = {6, 7, 3}

…….Make the created tree as right child of root.

….a) Recur for in[] = {4, 8, 2, 5} and post[] = {8, 4, 5, 2}.


…….Make the created tree as left child of root

Trees part 2

What is a Binary Search Tree (BST)?

Binary Search Tree is a node-based binary tree data structure which has the following
properties:

• The left subtree of a node contains only nodes with keys lesser than the node’s
key.
• The right subtree of a node contains only nodes with keys greater than the node’s
key.
• The left and right subtree each must also be a binary search tree.
Construct BST

Searching an element in BST


Illustration to search 6 in below tree:

1. Start from the root.


2. Compare the searching element with root, if less than root, then recursively call left
subtree, else recursively call right subtree.
3. If the element to search is found anywhere, return true, else return false.

// C function to search a given key in a given BST


struct node* search(struct node* root, int key)
{
// Base Cases: root is null or key is present at root
if (root == NULL || root->key == key)
return root;
// Key is greater than root's key
if (root->key < key)
return search(root->right, key);

// Key is smaller than root's key


return search(root->left, key);
}

Suppose that we have numbers between 1 and 100 in a binary search tree and want to
search for the number 55. Which of the following sequences CANNOT be the sequence
of nodes examined?

(A) {10, 75, 64, 43, 60, 57, 55}

(B) {90, 12, 68, 34, 62, 45, 55}

(C) {9, 85, 47, 68, 43, 57, 55}

(D) {79, 14, 72, 56, 16, 53, 55}

Answer: (C)

Explanation: In BST, on right child of parent should be greater than parent and left child
should be smaller than the parent, but in C after 47, 68 goes on the right side because it
greater then parent, now everything below this point should be greater then 47 but 43
appears that does not satisfy the BST property.

Deletion in BST

1) Node to be deleted is the leaf: Simply remove from the tree.


50 50
/ \ delete(20) / \
30 70 ---------> 30 70
/ \ / \ \ / \
20 40 60 80 40 60 80

2) Node to be deleted has only one child: Copy the child to the node and delete the child
50 50
/ \ delete(30) / \
30 70 ---------> 40 70
\ / \ / \
40 60 80 60 80

3) Node to be deleted has two children: Find inorder successor of the node. Copy contents
of the inorder successor to the node and delete the inorder successor. Note that inorder
predecessor can also be used.
50 60
/ \ delete(50) / \
40 70 ---------> 40 70
/ \ \
60 80 80

The important thing to note is, inorder successor is needed only when the right child is
not empty. In this particular case, inorder successor can be obtained by finding the
minimum value in the right child of the node.

AVL Trees
AVL tree is a self-balancing Binary Search Tree where the difference between heights of
left and right subtrees cannot be more than one for all nodes.

An Example Tree that is an AVL Tree

Insertion
To make sure that the given tree remains AVL after every insertion, we must augment the
standard BST insert operation to perform some re-balancing. Following are two basic
operations that can be performed to re-balance a BST without violating the BST property
(keys(left) < key(root) < keys(right)).

1. Left Rotation
2. Right Rotation
T1, T2 and T3 are subtrees of the tree
rooted with y (on the left side) or x (on
the right side)
y x
/ \ Right Rotation / \
x T3 - - - - - - - > T1 y
/ \ < - - - - - - - / \
T1 T2 Left Rotation T2 T3
Keys in both of the above trees follow the
following order
keys(T1) < key(x) < keys(T2) < key(y) < keys(T3)
So BST property is not violated anywhere.

Steps to follow for insertion

Let the newly inserted node be w

1. Perform standard BST insert for w.


2. Starting from w, travel up and find the first unbalanced node. Let z be the first
unbalanced node, y be the child of z that comes on the path from w to z and x be
the grandchild of z that comes on the path from w to z.
3. Re-balance the tree by performing appropriate rotations on the subtree rooted
with z. There can be 4 possible cases that needs to be handled as x, y and z can be
arranged in 4 ways. Following are the possible 4 arrangements:
4. y is left child of z and x is left child of y (Left Left Case)
5. y is left child of z and x is right child of y (Left Right Case)
6. y is right child of z and x is right child of y (Right Right Case)
7. y is right child of z and x is left child of y (Right Left Case)

Following are the operations to be performed in above mentioned 4 cases. In all of the
cases, we only need to re-balance the subtree rooted with z and the complete tree
becomes balanced as the height of subtree (After appropriate rotations) rooted with z
becomes same as it was before insertion.

a) Left Left Case

T1, T2, T3 and T4 are subtrees.


z y
/ \ / \
y T4 Right Rotate (z) x z
/ \ - - - - - - - - -> / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2

b) Left Right Case


z z x
/ \ / \ \ /
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2

c) Right Right Case


z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4

d) Right Left Case


z z x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4

Insertion Examples:
Deletion in AVL Trees

The deletion procedure is similar to that of a normal AVL tree without a parent pointer,
but in this case, the references to the parent pointers need to be updated with every
deletion and rotation accordingly. Follow the steps below to perform the delete operation:

1. Perform the delete operation as in normal BST.


2. From the node that has been deleted, move towards the root.
3. At each node on the path, update the height of the node.
4. Check for AVL conditions at each node. Let there be 3 nodes: w, x, y where w is the
current node, x is the root of the subtree of w which has greater height and y is the
root of the subtree of x which has greater height.
5. If the node w is unbalanced, there exists one of the following 4 cases:
6. Left Left Case (x is left child of w and y is left child of x)
7. Left Right Case (x is left child of w and y is right child of x)
8. Right Left Case (x is right child of w and y is left child of x)
9. Right Right Case (x is right child of w and y is right child of x)

Min Number of Nodes


Given the height of an AVL tree ‘h’, the task is to find the minimum number of nodes the
tree can have.

Examples :

Input : H = 0

Output : N = 1

Only '1' node is possible if the height

of the tree is '0' which is the root node.

Input : H = 3
Output : N = 7

Recursive Approach : In an AVL tree, we have to maintain the height balance property, i.e.
difference in the height of the left and the right subtrees can not be other than -1, 0 or 1
for each node.

We will try to create a recurrence relation to find the minimum number of nodes for a
given height, n(h).

• For height = 0, we can only have a single node in an AVL tree, i.e. n(0) = 1
• For height = 1, we can have a minimum of two nodes in an AVL tree, i.e. n(1) = 2
• Now for any height ‘h’, root will have two subtrees (left and right). Out of which
one has to be of height h-1 and other of h-2. [root node excluded]
• So, n(h) = 1 + n(h-1) + n(h-2) is the required recurrence relation for h>=2 [1 is
added for the root node]

Representation of Graphs

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).

Components of a Graph

• Vertices: Vertices are the fundamental units of the graph. Sometimes, vertices are
also known as vertex or nodes. Every node/vertex can be labeled or unlabelled.
• Edges: Edges are drawn or used to connect two nodes of the graph. It can be
ordered pair of nodes in a directed graph. Edges can connect any two nodes in any
possible way. There are no rules. Sometimes, edges are also known as arcs. Every
edge can be labeled/unlabelled.

Graphs are used to represent many real-life applications: Graphs are used to represent
networks. The networks may include paths in a city or telephone network or circuit
network. Graphs are also used in social networks like linkedIn, Facebook. For example, in
Facebook, each person is represented with a vertex(or node). Each node is a structure and
contains information like person id, name, gender, and locale. See this for more
applications of graph.

Following is an example of an undirected graph with 5 vertices.

The following two are the most commonly used representations of a graph.

1. Adjacency Matrix
2. Adjacency List

There are other representations also like, Incidence Matrix and Incidence List. The choice
of graph representation is situation-specific. It totally depends on the type of operations
to be performed and ease of use.

Adjacency Matrix:

Adjacency Matrix is a 2D array of size V x V where V is the number of vertices in a graph.


Let the 2D array be adj[][], a slot adj[i][j] = 1 indicates that there is an edge from vertex i
to vertex j. Adjacency matrix for undirected graph is always symmetric. Adjacency Matrix
is also used to represent weighted graphs. If adj[i][j] = w, then there is an edge from vertex
i to vertex j with weight w.
The adjacency matrix for the above example graph is:

Pros: Representation is easier to implement and follow. Removing an edge takes O(1)
time. Queries like whether there is an edge from vertex ‘u’ to vertex ‘v’ are efficient and
can be done O(1).

Cons: Consumes more space O(V^2). Even if the graph is sparse(contains less number of
edges), it consumes the same space. Adding a vertex is O(V^2) time. Computing all
neighbors of a vertex takes O(V) time (Not efficient).
Adjacency List:

An array of lists is used. The size of the array is equal to the number of vertices. Let the
array be an array[]. An entry array[i] represents the list of vertices adjacent to the ith vertex.
This representation can also be used to represent a weighted graph. The weights of edges
can be represented as lists of pairs. Following is the adjacency list representation of the
above graph.

Pros: Saves space O(|V|+|E|) . In the worst case, there can be C(V, 2) number of edges in a
graph thus consuming O(V^2) space. Adding a vertex is easier. Computing all neighbors
of a vertex takes optimal time.
Cons: Queries like whether there is an edge from vertex u to vertex v are not efficient and
can be done O(V).

In Real-life problems, graphs are sparse(|E| <<|V|2). That’s why adjacency lists Data
structure is commonly used for storing graphs. Adjacency matrix will enforce (|V|2) bound
on time complexity for such algorithms.

Depth First tree traversal

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.

Example:

Input: n = 4, e = 6
0 -> 1, 0 -> 2, 1 -> 2, 2 -> 0, 2 -> 3, 3 -> 3

Output: DFS from vertex 1 : 1 2 0 3

Explanation:
DFS Diagram:
Input: n = 4, e = 6
2 -> 0, 0 -> 2, 1 -> 2, 0 -> 1, 3 -> 3, 1 -> 3

Output: DFS from vertex 2 : 2 0 1 3

Explanation:
DFS Diagram:

Depth-first search is an algorithm for traversing or searching tree or graph data structures.
The algorithm starts at the root node (selecting some arbitrary node as the root node in
the case of a graph) and explores as far as possible along each branch before
backtracking.

So the basic idea is to start from the root or any arbitrary node and mark the node and
move to the adjacent unmarked node and continue this loop until there is no unmarked
adjacent node. Then backtrack and check for other unmarked nodes and traverse them.
Finally, print the nodes in the path.

Breadth First tree traversal

Breadth-First Traversal (or Search) for a graph is similar to Breadth-First Traversal of a tree.

The only catch here is, that, unlike trees, graphs may contain cycles, so we may come to
the same node again. To avoid processing a node more than once, we divide the vertices
into two categories:

• Visited and
• Not visited.
A boolean visited array is used to mark the visited vertices. For simplicity, it is assumed
that all vertices are reachable from the starting vertex. BFS uses a queue data structure
for traversal.

Example:

In the following graph, we start traversal from vertex 2.


When we come to vertex 0, we look for all adjacent vertices of it.

• 2 is also an adjacent vertex of 0.


• If we don’t mark visited vertices, then 2 will be processed again and it will become a
non-terminating process.
There can be multiple BFS traversals for a graph. Different BFS traversals for the above graph
:

2, 3, 0, 1

2, 0, 3, 1

Implementation of BFS traversal:


Follow the below method to implement BFS traversal.

• Declare a queue and insert the starting vertex.


• Initialize a visited array and mark the starting vertex as visited.
• Follow the below process till the queue becomes empty:
o Remove the first vertex of the queue.
o Mark that vertex as visited.
o Insert all the unvisited neighbours of the vertex into the queue.
The implementation uses an adjacency list representation of graphs. STL‘s list container
stores lists of adjacent nodes and the queue of nodes needed for BFS traversal.
C++// Program to print BFS traversal from a given
// source vertex. BFS(int s) traverses vertices
// reachable from s.
#include<bits/stdc++.h>
using namespace std;

// This class represents a directed graph using


// adjacency list representation
class Graph
{
int V; // No. of vertices

// Pointer to an array containing adjacency


// lists
vector<list<int>> adj;
public:
Graph(int V); // Constructor
// function to add an edge to graph
void addEdge(int v, int w);

// prints BFS traversal from a given source s


void BFS(int s);
};

Graph::Graph(int V)
{
this->V = V;
adj.resize(V);
}

void Graph::addEdge(int v, int w)


{
adj[v].push_back(w); // Add w to v’s list.
}

void Graph::BFS(int s)
{
// Mark all the vertices as not visited
vector<bool> visited;
visited.resize(V,false);

// Create a queue for BFS


list<int> queue;

// Mark the current node as visited and enqueue it


visited[s] = true;
queue.push_back(s);

while(!queue.empty())
{
// Dequeue a vertex from queue and print it
s = queue.front();
cout << s << " ";
queue.pop_front();

// Get all adjacent vertices of the dequeued


// vertex s. If a adjacent has not been visited,
// then mark it visited and enqueue it
for (auto adjecent: adj[s])
{
if (!visited[adjecent])
{
visited[adjecent] = true;
queue.push_back(adjecent);
}
}
}
}

// Driver program to test methods of graph class


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);

cout << "Following is Breadth First Traversal "


<< "(starting from vertex 2) \n";
g.BFS(2);

return 0;
}

C++
Output
Following is Breadth First Traversal (starting from vertex 2)
2 0 3 1

Time Complexity: O(V+E), where V is the number of nodes and E is the number of edges.

Auxiliary Space: O(V)

BFS for Disconnected Graph:


Note that the above code traverses only the vertices reachable from a given source vertex.
In every situation, all the vertices may not be reachable from a given vertex (i.e. for a
disconnected graph).

To print all the vertices, we can modify the BFS function to do traversal starting from all
nodes one by one (Like the DFS modified version).
Below is the implementation for BFS traversal for the entire graph (valid for directed as
well as undirected graphs) with possible multiple disconnected components:
C++
/*
-> Generic Function for BFS traversal of a Graph
(valid for directed as well as undirected graphs
which can have multiple disconnected components)
-- Inputs --
-> V - represents number of vertices in the Graph
-> adj[] - represents adjacency list for the Graph
-- Output --
-> bfs_traversal - a vector containing bfs traversal
for entire graph
*/

vector<int> bfsOfGraph(int V, vector<int> adj[])


{
vector<int> bfs_traversal;
vector<bool> vis(V, false);
for (int i = 0; i < V; ++i) {

// To check if already visited


if (!vis[i]) {
queue<int> q;
vis[i] = true;
q.push(i);

// BFS starting from ith node


while (!q.empty()) {
int g_node = q.front();
q.pop();
bfs_traversal.push_back(g_node);
for (auto it : adj[g_node]) {
if (!vis[it]) {
vis[it] = true;
q.push(it);
}
}
}
}
}
return bfs_traversal;
}
C++

Report

You might also like