UNIT I DS (Updated)
UNIT I DS (Updated)
As stated above, all the data elements of an array are stored at contiguous locations
in the main memory. The name of the array represents the base address or the
address of the first element in the main memory. Each element of the array is
represented by proper indexing.
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:
Initialize: Make a stack empty.
Push: Adds an item in the stack. If the stack is full, then it is said to be an
Overflow condition.
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.
Peek or Top: Returns top element of the stack.
isEmpty: Returns true if the stack is empty, else false.
Queue: Like Stack, Queue is a linear structure which follows a particular order in
which the operations are performed. The order is First In First Out (FIFO). In the
queue, items are inserted at one end and deleted from the other end. A good
example of the 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.
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).
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.
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.
Calculating Time Complexity:
Now lets tap onto the next big topic related to Time complexity, which is How to
Calculate Time Complexity. It becomes very confusing some times, but we will try
to explain it in the simplest way.
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.
for(i=0; i < N; i++)
{
statement;
}
The time complexity for the above algorithm will be Linear. The running time of
the loop is directly proportional to N. When N doubles, so does the running time.
for(i=0; i < N; i++)
{
for(j=0; j < N;j++)
{
statement;
}
}
This time, the time complexity for the above code will be Quadratic. The running
time of the two loops is proportional to the square of N. When N doubles, the
running time increases by N * N.
while(low <= high)
{
mid = (low + high) / 2;
if (target < list[mid])
high = mid - 1;
else if (target > list[mid])
low = mid + 1;
else break;
}
This is an algorithm to break a set of numbers into halves, to search a particular
field(we will study this in detail later). Now, this algorithm will have a
Logarithmic Time Complexity. The running time of the algorithm is proportional
to the number of times N can be divided by 2(N is high-low here). This is because
the algorithm divides the working area in half with each iteration.
void quicksort(int list[], int left, int right)
{
int pivot = partition(list, left, right);
quicksort(list, left, pivot - 1);
quicksort(list, pivot + 1, right);
}
Taking the previous algorithm forward, above we have a small logic of Quick
Sort(we will study this in detail later). Now in Quick Sort, we divide the list into
halves every time, but we repeat the iteration N times(where N is the size of list).
Hence time complexity will be N*log( N ). The running time consists of N loops
(iterative or recursive) that are logarithmic, thus the algorithm is a combination of
linear and logarithmic.
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
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.
There are mainly three asymptotic notations:
1.Big-O notation
2.Omega notation
3.Theta notation
Big-O Notation (O-notation)
Big-O notation represents the upper bound of the running time of an algorithm.
Thus, it gives the worst-case complexity of an algorithm.
The above expression can be described as a function f(n) belongs to the set Ω(g(n))
if there exists a positive constant c such that it lies above cg(n), for sufficiently
large n.
For any value of n, the minimum time required by the algorithm is given by
Omega Ω(g(n)).
Theta Notation (Θ-notation)
Theta notation encloses the function from above and below. Since it represents the
upper and the lower bound of the running time of an algorithm, it is used for
analyzing the average-case complexity of an algorithm.
4).Stack DS:
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.
Stack Implementation Using Array:
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.
push() to insert an element into the stack
pop() to remove an element from the stack
top() Returns the top element of the stack.
isEmpty() returns true is stack is empty else false
size() returns the size of stack
Operations Complexity
push() O(1)
pop() O(1)
isEmpty() O(1)
size() O(1)
If the operator arrives and the stack is found to be empty, then simply push the
operator into the stack.
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.
Example
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 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
+ + KL+MN*
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.
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.
5. QUEUE IN DS:
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.
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...
}
}
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!!!");
}
}
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.
In approach [B] there is no such overhead, but whenever we move head one
position ahead, after removal of first element, the size on Queue is reduced by
one space each time.
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…
TYPES OF QUEUES:
Depending on the way of performing operations on the queue data structure, queues can
be classified into four types as:
a)LINEAR QUEUE
A linear queue is a linear data structure in which elements can be inserted only at one end, called
REAR end and elements can be deleted from the other end, called FRONT end of the list. The
diagrammatic representation of a linear queue is as follows:
10 20 30
FRONT REAR
For implementing these operations, consider array representation of the queue. Assume
Q is an array used to perform queue operations with a maximum size as ‘N’. Initially elements
are not available in the queue Q. At this stage, set two variables F and R at -1 that represents
FRONT and REAR ends of the list.
Example: Let N = 3
0 1 2
Status of Queue: F = -1
R = -1
Q
Enqueue Operation:
The process of inserting an element into the queue is known as enqueue operation.
For every enqueue operation, first the variable REAR is incremented by 1 and then the
new element is inserted into REAR position of the queue.
While performing enqueue operation, if empty locations are not available to insert the
element then the status of the queue is called as “ QUEUE OVERFLOW” or “QUEUE
FULL”.
Algorithm enqueue(x):This procedure inserts a new element x into REAR position of the queue
Q.
Dequeue Operation:
The process of deleting an element from the queue is known as dequeue operation.
For every dequeue operation, first delete an element from FRONT position of the queue
and then the variable FRONT is incremented 1.
While performing dequeue operation, if elements are not available to delete from the
queue then the status of the queue is called as “ QUEUE UNDERFLOW” or “QUEUE
EMPTY”.
Algorithm dequeue():Function is used to delete the FRONT position element from the queue Q.
Step 1: IF F = -1 THEN
WRITE ‘LINEAR QUEUE UNDERFLOW’
RETURN -1
ENDIF
Step 2: K ← Q[F]
Step 3: IF F = R THEN
F←R←-1
ELSE
F←F+1
ENDIF
Step 4: RETURN K
Front Element Operation: This operation is used to print FRONT position element of
the Queue Q.
Step 1: IF F = -1 THEN
RETURN -1
ELSE
RETURN Q[F]
ENDIF
Rear Element Operation: This operation is used to print REAR position element of
the Queue Q.
Step 1: IF R = -1 THEN
RETURN -1
ELSE
RETURN Q[R]
ENDIF
Empty Operation: Empty operation is used to check whether the queue is empty or
not.
Step 1: IF F = -1 THEN
RETURN 1
ELSE
RETURN 0
ENDIF
Full Operation:
Full operation is used to check whether the queue is full with elements or not.
Display Operation:
Step 1: IF F = -1 THEN
WRITE ‘LINEAR QUEUE EMPTY’
ELSE
REPEAT FOR i ← F TO R DO STEPS BY 1
WRITE Q[i]
ENDREPEAT
ENDIF
Step 2: RETURN
Example: Let N = 5
0 1 2 3 4
Status of Queue Q:
30 40 50
F R
At this stage, if we tried to insert an element into the queue, it prints a message as “Queue
Overflow” that refers to empty locations is not available. But still empty locations are available
at the FRONT end of the list.
From this, Even though empty locations are available at FRONT end and REAR reached
to N-1th locations then further insertion is not possible in linear queues.
b) CIRCULAR QUEUE
In circular queue, elements are arranged in circular fashion. Here, when REAR end
reaches to N-1th location and still empty locations are available at the FRONT end of the list then
REAR variable is set back to starting location. The logical view of a circular queue can be
represented as:
Consider the basic operations on circular queue as Insertion, Deletion and Display
operations.
Algorithm Insertion(x): This procedure inserts a new element x into REAR position of the
circular queue CQ.
Step 1: IF F = -1 THEN
WRITE ‘CIRCULAR QUEUE UNDERFLOW’
RETURN -1
ENDIF
Step 2: K ← CQ[F]
Step 3: IF F = R THEN
F←R←-1
ELSEIF F = N-1 THEN
F←0
ELSE
F←F+1
ENDIF
Step 4: RETURN K
Step 1: IF F = -1 THEN
WRITE ‘CIRCULAR QUEUE EMPTY’
ELSE
IF F ≤ R THEN
REPEAT FOR i ← F TO R DO STEPS BY 1
WRITE CQ[i]
ENDREPEAT
ELSE
REPEAT FOR i ← F TO N-1 DO STEPS BY 1
WRITE CQ[i]
ENDREPEAT
REPEAT FOR i ← 0 TO R DO STEPS BY 1
WRITE CQ[i]
ENDREPEAT
ENDIF
ENDIF
Step 2: RETURN
c) DEQUE
A deque (Double Ended Queue) is a linear data structure in which elements may be
inserted and deleted at either ends of the list. The diagrammatic representation of a deque is as
follows:
Insertions Insertions
FRONT REAR
Deletions Deletions
Insertions
FRONT REAR
Deletions Deletions
Insertions Insertions
FRONT REAR
Deletions
d) PRIORITY QUEUE
A priority queue is a collection of elements such that each element has been assigned a
priority and such that the order in which elements are deleted and processed comes from the
following rules:
Then the status of the priority queue after processing these elements is:
0 1 2 3 4
30 20 40 10
FRONT REAR
APPLICATION OF QUEUES
Queues are used in different application areas such as: simulation, multiprogramming
environments, job scheduling applications etc.
In multiprogramming environment a single CPU has to serve more than one program
simultaneously. In this case, scheduling is to classify the work load according to its
characteristics and to maintain separate process queues. Process will assign to their respective
queues. Then CPU will service the processes as per the priority of the queues.
Example:
Here, high priority programs are processed first before the medium and low priority
queue jobs. After completing high priority queue program, then CPU serve its service to
medium priority queue programs. Finally it serves to lowest priority programs.
Round Robin Algorithm
Round Robin (RR) algorithm is a scheduling algorithm designed for time sharing
systems. The problem statement can be stated as:
Assume there are n processes P1, P2, - - - - , Pn served by the CPU. Different processes
require different execution time. Suppose sequence of processes are arrivals according to their
subscripts as P1 comes first than P2 and P2 comes first than P3 and so on.
Consider the following table consists of different processes and their burst time to
complete the execution.
Assume the time quantum is 4 units. For this the total execution procedure with RR
algorithm can be shown as:
P1 P2 P3 P1 P2 P3 P2 P2 P2
0 4 8 12 15 19 20 24 28 30
To implement such execution, all the programs are currently under execution are
maintained in a circular queue. When a process finished its execution then this process is deleted
from the queue and whenever a new process arrives it is inserted into tail of the queue.
Advantages of Queues:
As similar to stacks, insertion and deletion operations can be performed easily in queues.
Time complexity of enqueue operation is O(1) time.
Time complexity of dequeue operation is O(1) time.