Unit i Notes
Unit i Notes
Linked Lists: 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.
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). In stack, all insertion and deletion are permitted at only
one end of the list.
Mainly the following three basic operations are performed in the stack:
Enqueue: Adds an item to the queue. If the queue is full, then it is said to be
an Overflow condition.
Dequeue: Removes an item from the queue. The items are popped in the same
order in which they are pushed. If the queue is empty, then it is said to be an
Underflow condition.
Front: Get the front item from the queue.
Rear: Get the last item from the queue.
Binary Tree: Unlike Arrays, Linked Lists, Stack and queues, which are linear
data structures, trees are hierarchical data structures. A binary tree is a tree data
structure in which each node has at most two children, which are referred to as
the left child and the right child. It is implemented mainly using Links.
A Binary Tree is represented by a pointer to the topmost node in the tree. If the
tree is empty, then the value of root is NULL. A Binary Tree node contains the
following parts.
1. Data
Binary Search Tree: A Binary Search Tree is a Binary Tree following the
additional properties:
The left part of the root node contains keys less than the root node key.
The right part of the root node contains keys greater than the root node key.
There is no duplicate key present in the binary tree.
A Binary tree having the following properties is known as Binary search tree
(BST).
Max-Heap: In a Max-Heap the key present at the root node must be greatest
among the keys present at all of its children. The same property must be
recursively true for all sub-trees in that Binary Tree.
Min-Heap: In a Min-Heap the key present at the root node must be minimum
among the keys present at all of its children. The same property must be
recursively true for all sub-trees in that Binary Tree.
Why we need data structures?
Speed: Speed is an important factor when we are processing the data. Suppose an
organization has thousands of employee records and they want to look up to an
employee record, the process needs to be fast as possible to save time, nobody is
willing to spend 10 minutes to look up a record in database.
Save disk space: When we are dealing with large amount of data, it becomes
important to save the disk space as much as possible. Data structure allows
efficient storing of data thus allows disk space saving.
2) Insertion: Insertion can be defined as the process of adding the elements to the
data structure at any location.
If the size of data structure is n then we can only insert n-1 data elements into it.
If we try to delete an element from an empty data structure then underflow occurs.
4) Searching: The process of finding the location of an element within the data
structure is called Searching. There are two algorithms to perform searching,
Linear Search and Binary Search. We will discuss each one of them later in this
tutorial.
5) Sorting: The process of arranging the data structure in a specific order is known
as Sorting. There are many algorithms that can be used to perform sorting, for
example, insertion sort, selection sort, bubble sort, etc.
6) Merging: When two lists List A and List B of size M and N respectively, of
similar type of elements, clubbed or joined to produce the third list, List C of size
(M+N), then this process is called merging
Algorithm Analysis:
Algorithm Complexity:
Suppose X is treated as an algorithm and N is treated as the size of input data, the
time and space implemented by the Algorithm X are the two main factors which
determine the efficiency of X.
Time Factor − The time is calculated or measured by counting the number of key
operations such as comparisons in sorting algorithm.
The complexity of an algorithm f(N) provides the running time and / or storage
space needed by the algorithm with respect of N as the size of input data.
Space Complexity:
A fixed part that is a space required to store certain data and variables (i.e. simple
variables and constants, program size etc.), that are not dependent of the size of
the problem.
Algorithm
SUM(P, Q)
Step 1 - START
Step 2 - R ← P + Q + 10
Step 3 - Stop
Here we have three variables P, Q and R and one constant. Hence S(p) = 1+3.
Now space is dependent on data types of given constant types and variables and it
will be multiplied accordingly.
Time Complexity:
Time complexity of an algorithm signifies the total time required by the program to
run till its completion.
Time Complexity is most commonly estimated by counting the number of
elementary steps performed by any algorithm to finish execution. Like in the
example above, for the first code the loop will run n number of times, so the time
complexity will be n atleast and as the value of n will increase the time taken will
also increase. While for the second code, time complexity is constant, because it
will never be dependent on the value of n, it will always give the result in 1 step.
And since the algorithm's performance may vary with different types of input data,
hence for an algorithm we usually use the worst-case Time complexity of an
algorithm because that is the maximum time taken for any input size.
Now the most common metric for calculating time complexity is Big O notation.
This removes all constant factors so that the running time can be estimated in
relation to N, as N approaches infinity. In general you can think of it like this :
statement;
Above we have a single statement. Its Time Complexity will be Constant. The
running time of the statement will not change in relation to N.
NOTE: In general, doing something with every item in one dimension is linear,
doing something with every item in two dimensions is quadratic, and dividing the
working area in half is logarithmic.
Big O
Notation Name Example(s)
Linearithmi
O(n log n) # Sorting elements in array with merge sort
c
Big O
Notation Name Example(s)
O(1) < O(log n) < O(n) < O(n logn) < O(n2) < O(n3) < - - - - - - - < O(2n)
.
The order of time complexities from best to worst.
3)Asymptotic Analysis:
The efficiency of an algorithm depends on the amount of time, storage and other
resources required to execute the algorithm. The efficiency is measured with the
help of asymptotic notations.
An algorithm may not have the same performance for different types of inputs.
With the increase in the input size, the performance will change.
The study of change in performance of the algorithm with the change in the order
of the input size is defined as asymptotic analysis.
Asymptotic Notations
Asymptotic notations are the mathematical notations used to describe the running
time of an algorithm when the input tends towards a particular value or a limiting
value.
For example: In bubble sort, when the input array is already sorted, the time taken
by the algorithm is linear i.e. the best case.
But, when the input array is in reverse condition, the algorithm takes the maximum
time (quadratic) to sort the elements i.e. the worst case.
When the input array is neither sorted nor in reverse order, then it takes average
time. These durations are denoted using asymptotic notations.
Big-O notation
Omega notation
Theta notation
For any value of n, the minimum time required by the algorithm is given by
Omega Ω(g(n)).
If a function f(n) lies anywhere in between c1g(n) and c2g(n) for all n ≥ n0, then
f(n) is said to be asymptotically tight bound.
Stack:
Stack is a linear data structure that follows the LIFO (Last In First Out) principle,
where it performs all operations. It performs insertion and deletion operations on
the stack from only one end from the top of the stack. Inserting a new element on
the top of the stack is known as push operation, and deleting a data element from
the top of the stack is known as pop operation. You can perform the
implementation of the stack in memory using two data structures: stack
implementation using array and stack implementation using linked-list.
In Stack implementation using arrays, it forms the stack using the arrays. All the
operations regarding the stack implementation using arrays.
Basic Operations on Stack
In order to make manipulations in a stack, there are certain operations provided to
us.
Before implementing actual operations, first follow the below steps to create an
empty stack.
Step 1 - Include all the header files which are used in the program and define a
constant 'SIZE' with specific value.
Step 2 - Declare all the functions used in stack implementation.
Step 3 - Create a one dimensional array with fixed size (int stack[SIZE])
Step 4 - Define a integer variable 'top' and initialize with '-1'. (int top = -1)
Step 5 - In main method, display menu with list of operations and make suitable
function calls to perform operation selected by the user on the stack.
push(value) - Inserting value into the stack
In a stack, push() is a function used to insert an element into the stack. In a stack,
the new element is always inserted at top position. Push function takes one integer
value as parameter and inserts that value into the stack. We can use the following
steps to push an element on to the stack...
begin
if stack is full
return
endif
else
increment top
stack[top] assign value
end else
end procedure
Pop:
Removes an item from the stack. The items are popped in the reversed order in
which they are pushed. If the stack is empty, then it is said to be an Underflow
condition.
begin
if stack is empty
return
endif
else
store value of stack[top]
decrement top
return value
end else
end procedure
Top:
Returns the top element of the stack.
isEmpty:
Returns true if the stack is empty, else false.
begin
if top < 1
return true
else
return false
end procedure
Understanding stack practically:
There are many real-life examples of a stack. Consider the simple example of
plates stacked over one another in a 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 the LIFO/FILO order.
Complexity Analysis:
Time Complexity
Operations Complexity
push() O(1)
Operations Complexity
pop() O(1)
isEmpty() O(1)
size() O(1)
#include<stdio.h>
#define N 10
int stack[N], top = -1;
void push(int x){
if(top == N-1)
printf("\nStack is Full!!! Insertion is not possible!!!");
else{
top++;
stack[top] = x;
printf("\nInsertion success!!!");
}
}
void pop(){
if(top == -1)
printf("\nStack is Empty!!! Deletion is not possible!!!");
else{
printf("\nDeleted : %d", stack[top]);
top--;
}
}
void display(){
if(top == -1)
printf("\nStack is Empty!!!");
else{
int i;
printf("\nStack elements are:\n");
for(i=top; i>=0; i--)
printf("%d\n",stack[i]);
}
}
void main()
{
int x, choice;
while(choice!=4){
printf("\n\n***** MENU *****\n");
printf("1. Push\n2. Pop\n3. Display\n4. Exit");
printf("\nEnter your choice: ");
scanf("%d",&choice);
switch(choice){
case 1: printf("Enter the value to be insert: ");
scanf("%d",&x);
push(x);
break;
case 2: pop();
break;
case 3: display();
break;
case 4: exit(0);
default: printf("invalid choice");
}
}
}
Advantages of Stack:
Example: A + (B - C)
To evaluate the expressions, one needs to be aware of the standard precedence
rules for arithmetic expression. The precedence rules for operators are:
Infix Notation
Prefix Notation
Postfix Notation
Infix Notation
Example: A + B, (C - D) etc.
All these expressions are in infix notation because the operator comes between the
operands.
Prefix Notation
The prefix notation places the operator before the operands. This notation was
introduced by the Polish mathematician and hence often referred to as polish
notation.
All these expressions are in postfix notation because the operator comes after the
operands.
Conversion of Arithmetic Expression into various Notations:
If the incoming operator has higher precedence than the TOP of the stack, push the
incoming operator into the stack.
If the incoming operator has the same precedence with a TOP of the stack, check
the associativity if it is L to R, then push the incoming operator into the stack.
If the incoming operator has lower precedence than the TOP of the stack, pop, and
print the top of the stack. Test the incoming operator against the top of the stack
again and pop the operator from the stack till it finds the operator of a lower
precedence or same precedence.
If the incoming operator has the same precedence with the top of the stack and the
incoming operator is ^, then pop the top of the stack till the condition is true. If the
condition is not true, push the ^ operator.
When we reach the end of the expression, pop, and print all the operators from the
top of the stack.
If the operator is '(', then pop all the operators from the stack till it finds ) opening
bracket in the stack.
If the top of the stack is ')', push the operator on the stack.
Q Empty Q
+ + Q
T + QT
* +* QT
V +* QTV
/ +*/ QTV
U +*/ QTVU
/ +*// QTVU
W +*// QTVUW
* +*//* QTVUW
) +*//*) QTVUW
P +*//*) QTVUWP
^ +*//*)^ QTVUWP
O +*//*)^ QTVUWPO
( +*//*) QTVUWPO^
+*//* QTVUWPO^
EMPTY QTVUWPO^*//*+
Rules:
If the stack is empty or contains a left parenthesis on top, push the incoming
operator on to the stack.
If the incoming symbol is ')', pop the stack and print the operators until the left
parenthesis is found.
If the incoming symbol has higher precedence than the top of the stack, push it on
the stack.
If the incoming symbol has lower precedence than the top of the stack, pop and
print the top of the stack. Then test the incoming operator against the new top of
the stack.
If the incoming operator has the same precedence with the top of the stack then use
the associativity rules. If the associativity is from left to right then pop and print
the top of the stack then push the incoming operator. If the associativity is from
right to left then push the incoming operator.
At the end of the expression, pop and print all the operators of the stack.
K K
+ +
L + KL
- - K L+
M - K L+ M
* -* K L+ M
N -* KL+MN
+ + K L + M N*
K L + M N* -
( +( K L + M N *-
O +( KL+MN*-O
^ +(^ K L + M N* - O
P +(^ K L + M N* - O P
) + K L + M N* - O P ^
* +* K L + M N* - O P ^
W +* K L + M N* - O P ^ W
/ +/ K L + M N* - O P ^ W *
U +/ K L + M N* - O P ^W*U
/ +/ K L + M N* - O P ^W*U/
V +/ KL + MN*-OP^W*U/V
* +* KL+MN*-OP^W*U/V/
T +* KL+MN*-OP^W*U/V/T
+ + KL+MN*-OP^W*U/V/T*
KL+MN*-OP^W*U/V/T*+
Q + KL+MN*-OP^W*U/V/T*Q
KL+MN*-OP^W*U/V/T*+Q+
2. Backtracking
3. Delimiter Checking
The common application of Stack is delimiter checking, i.e., parsing that involves
analyzing a source program syntactically. It is also called parenthesis checking.
When the compiler translates a source program written in some programming
language such as C, C++ to a machine language, it parses the program into
multiple individual parts such as variable names, keywords, etc. By scanning from
left to right. The main problem encountered while translating is the unmatched
delimiters. We make use of different types of delimiters include the parenthesis
checking (,), curly braces {,} and square brackets [,], and common delimiters /*
and */. Every opening delimiter must match a closing delimiter, i.e., every opening
parenthesis should be followed by a matching closing parenthesis. Also, the
delimiter can be nested. The opening delimiter that occurs later in the source
program should be closed before those occurring earlier.
Valid Delimiter Invalid Delimiter
{ ( a + b) - c } { ( a + b) - c
o If the delimiters are of the same type, then the match is considered
successful, and the process continues.
o If the delimiters are not of the same type, then the syntax error is reported.
When the end of the program is reached, and the Stack is empty, then the
processing of the source program stops.
To reverse a given set of data, we need to reorder the data so that the first and last
elements are exchanged, the second and second last element are exchanged, and so
on for all other elements.
o Reversing a string
o Converting Decimal to Binary
Reverse a String
A Stack can be used to reverse the characters of a string. This can be achieved by
simply pushing one by one each character onto the Stack, which later can be
popped from the Stack one by one. Because of the last in first out property of the
Stack, the first character of the Stack is on the bottom of the Stack and the last
character of the String is on the Top of the Stack and after performing the pop
operation in the Stack, the Stack returns the String in Reverse order.
Stack plays an important role in programs that call several functions in succession.
Suppose we have a program containing three functions: A, B, and C. function A
invokes function B, which invokes the function C.
When we invoke function A, which contains a call to function B, then its
processing will not be completed until function B has completed its execution and
returned. Similarly for function B and C. So we observe that function A will only
be completed after function B is completed and function B will only be completed
after function C is completed. Therefore, function A is first to be started and last to
be completed. To conclude, the above function activity matches the last in first out
behavior and can easily be handled using Stack.
Consider addrA, addrB, addrC be the addresses of the statements to which control
is returned after completing the function A, B, and C, respectively.
The above figure shows that return addresses appear in the Stack in the reverse
order in which the functions were called. After each function is completed, the pop
operation is performed, and execution continues at the address removed from the
Stack. Thus the program that calls several functions in succession can be handled
optimally by the stack data structure. Control returns to each function at a correct
place, which is the reverse order of the calling sequence.
QUEUE:
A queue data structure can be implemented using one dimensional array. The
queue implemented using array stores only fixed number of data values. The
implementation of queue data structure using array is very simple. Just define a
one dimensional array of specific size and insert or delete the values into that array
by using FIFO (First In First Out) principle with the help of
variables 'front' and 'rear'. Initially both 'front' and 'rear' are set to -1. Whenever,
we want to insert a new value into the queue, increment 'rear' value by one and
then insert at that position. Whenever we want to delete a value from the queue,
then delete the element which is at 'front' position and increment 'front' value by
one.
Before we implement actual operations, first follow the below steps to create an
empty queue.
Step 1 - Include all the header files which are used in the program and
define a constant 'SIZE' with specific value.
Step 2 - Declare all the user defined functions which are used in queue
implementation.
Step 3 - Create a one dimensional array with above defined SIZE (int
queue[SIZE])
Step 4 - Define two integer variables 'front' and 'rear' and initialize both
with '-1'. (int front = -1, rear = -1)
Step 5 - Then implement main method by displaying menu of operations list
and make suitable function calls to perform operation selected by the user on
queue.
In a queue data structure, enQueue() is a function used to insert a new element into
the queue. In a queue, the new element is always inserted at rear position. The
enQueue() function takes one integer value as a parameter and inserts that value
into the queue. We can use the following steps to insert an element into the queue...
#include<stdio.h>
#define N 10
}
}
int display()
{
int i;
if(front==-1 &&rear ==-1)
{
printf("queue is empty");
}
else
{
for(i=front;i<rear+1;i++)
{
printf("%d",queue[i]);
}
}
}
int main()
{
int x, choice;
while(choice!=4){
printf("\n\n***** MENU *****\n");
printf("1. Insertion\n2. Deletion\n3. Display\n4. Exit");
printf("\nEnter your choice: ");
scanf("%d",&choice);
switch(choice){
case 1: printf("Enter the value to be insert: ");
scanf("%d",&x);
enQueue(x);
break;
case 2: deQueue();
break;
case 3: display();
break;
case 4: exit(0);
default: printf("\nWrong selection!!! Try again!!!");
}
}
}
Implementation of Queue Data Structure
Queue can be implemented using an Array, Stack or Linked List. The easiest way
of implementing a queue is by using an Array.
Initially the head(FRONT) and the tail(REAR) of the queue points at the first
index of the array (starting the index of array from 0). As we add elements to the
queue, the tail keeps on moving ahead, always pointing to the position where the
next element will be inserted, while the head remains at the first index.
When we remove an element from Queue, we can follow two possible approaches
(mentioned [A] and [B] in above diagram). In [A] approach, we remove the
element at head position, and then one by one shift all the other elements in
forward position.
In approach [B] we remove the element from head position and then move head to
the next position.
Managing requests on a single shared resource such as CPU scheduling and disk
scheduling.
A stack can be implemented by means of Array and Linked List. Stack can
either be a fixed size one or it may have a sense of dynamic resizing. Here, we
are going to implement stack using arrays, which makes it a fixed size stack
implementation.
We can also implement stack dynamically by making use of pointers.
Similarly for queue also…