Data Types and Operator
Data Types and Operator
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
Header file
• 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;
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. 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.
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.
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)
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)
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)
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)
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:
• 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
• 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: =
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);
printf
C++void main()
int a;
a = printf(“Hello”);
printf(“%d”, a);
Prints “Hello5"
C++void main()
int a;
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;
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
• 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
Code Segment(Editors)
Scope of a Variable
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.
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.
Answer: (A)
Explanation: The count=1 and it goes till two,so following statement will be executed twice.
y += funcf(x) + funcg(x);
So the Answer is A.
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)
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
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);
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:
• Code Re-utilisation
• Prototype
• Definition
• Calling
Why do we need functions?
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.
(A)
010110101
(B)
010101101
(C)
10110101
(D)
10101101
Answer: (D)
Explanation:
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
(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.
(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)]
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”;
if(str1 == str2)
printf(“Hi”);
else
printf(“Hello”);
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.
• Different operators
• Member variables
• Without member not allowed
• Structure in function
• Function in Structure
Ways to use structure
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.
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,
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>
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.
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.
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.
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.
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'.
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
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
int *p1 = a;
a 1000 Int *
*a 10 Int
*a+1 11 int
**a error
*(a+3) 40 int
a[3] 40 int
Pointer and Functions
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
• 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
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
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} }};
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:
B = Base address,
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),
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:
= 15 – 1 + 1
= 15
Formula:
Solution:
= 100 + 1 * (110)
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:
B = Base address,
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),
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:
= 10 – 1 + 1
= 10
Formula:
= 100 + 1 * (57)
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: To find the address of the element using row-major order, use the
following formula:
B = Base address,
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),
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:
= 1 – (-4) + 1
=6
= 10 – 5 + 1
=6
Formula:
Solution:
= 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-
B = Base address,
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),
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:
=8–1+1
=8
= 16
Formula:
Solution:
= 400 + 4 * (1069)
= 400 + 4276
= 4676
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:
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:
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.
Void main(){
int i ; //data
Struct node temp;
struct node *head;
head=temp;
head.data=i;
head.next=NULL;
}
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.
Time complexity of insertAfter() is O(n) as it depends on n where n is the size of the linked list.
head = head->next
Iterative Solution
1) Initialize count as 0
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>
Output
count of nodes is 5
Where n is the size of the linked list, and we have to traverse the list only once.
Given a linked list, check if the linked list has loop or not. Below diagram shows a linked list with a loop.
push(&head, 20);
push(&head, 4);
push(&head, 15);
push(&head, 10);
if (detectLoop(head))
printf("Loop found");
else
printf("No Loop");
return 0;
}
Output
Loop found
Complexity Analysis:
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.
Following are advantages/disadvantages of doubly linked list over singly linked list.
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 .
Delete at beginning
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
Output
Original Linked list 10 8 4 2
• 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.
Answer: (A)
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.
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
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
= 90
k = 1276
M = 11
h(1276) = 1276 mod 11
=0
Pros:
• 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.
Formula:
h(K) = h(k x k)
Here,
k is the key value.
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.
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.
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
• 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.
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.
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.
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
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;
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.
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)
{
Pop
void pop()
{
Node* temp;
Postfix expression: The expression of the form a b op. When an operator is followed for
every pair of operands.
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).
The compiler scans the expression either from left to right or from right to left.
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
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.
Infix: a+b*(c^d-e)^(f+g*h)-i
Output: abcd^e-fgh*+^*+i-
Step 2. Scan A from right to left and repeat step 3 to 6 for each element of A until the
STACK is empty
a. Repeatedly pop from the STACK and add to B (each operator on top of stack until a left
parenthesis is encountered)
Step 7. Exit
Expression = (A+B^C)*D+E^5
5^E+D*)C^B+A(
5^E+D*(C^B+A)
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:
Examples:
Input : abc++
Output : (a + (b + c))
Input : ab*c+
Output : ((a*b)+c)
Algorithm
3.Otherwise,
…3.3 Put the operator, with the values as arguments and form a string.
Examples:
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
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.
2. If front == NULL,
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;
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
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.
if(front == NULL)
printf("Queue is Empty. Unable to perform dequeue\n");
else
{
//take backup
temp = front;
#include<stdio.h>
#include<stdlib.h>
struct node
{
int data;
struct node *next;
};
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;
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;
}
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:
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):
deQueue(q):
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)
deQueue(q)
2) If stack2 is empty
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).
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:
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’.
• 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:
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:
• 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;
};
Here,
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
Inorder Traversal:
Algorithm Inorder(tree)
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.
Preorder Traversal:
Algorithm Preorder(tree)
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.
Postorder Traversal:
Algorithm Postorder(tree)
1. Traverse the left subtree, i.e., call Postorder(left-subtree)
Uses of Postorder
• 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
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}
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]
Trees part 2
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
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?
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
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.
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.
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.
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:
Examples :
Input : H = 0
Output : N = 1
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.
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:
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 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
Explanation:
DFS Diagram:
Input: n = 4, e = 6
2 -> 0, 0 -> 2, 1 -> 2, 0 -> 1, 3 -> 3, 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 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:
2, 3, 0, 1
2, 0, 3, 1
Graph::Graph(int V)
{
this->V = V;
adj.resize(V);
}
void Graph::BFS(int s)
{
// Mark all the vertices as not visited
vector<bool> visited;
visited.resize(V,false);
while(!queue.empty())
{
// Dequeue a vertex from queue and print it
s = queue.front();
cout << s << " ";
queue.pop_front();
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.
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
*/
Report