Data Structures C++ II-Unit
Data Structures C++ II-Unit
Data Structures C++ II-Unit
Abstract Data type (ADT) is a type (or class) for objects whose behavior is defined by a set of value and a
set of operations.
The definition of ADT only mentions what operations are to be performed but not how these operations
will be implemented. It does not specify how data will be organized in memory and what algorithms will
be used for implementing the operations.
It is called “abstract” because it gives an implementation-independent view. The process of providing only
the essentials and hiding the details is known as abstraction.
The user of data type does not need to know how that data type is implemented, for example, we have been
using Primitive values like int, float, char data types only with the knowledge that these data type can
operate and be performed on without any idea of how they are implemented. So a user only needs to know
what a data type can do, but not how it will be implemented.
Think of ADT as a black box which hides the inner structure and design of the data type.
Consider the following examples of ADTs namely List ADT, Stack ADT, Queue ADT.
List ADT
A list contains elements of the same type arranged in sequential order and following operations can
be performed on the list.
o get() – Return an element from the list at any given position.
o insert() – Insert an element at any position of the list.
o remove() – Remove the first occurrence of any element from a non-empty list.
o removeAt() – Remove the element at a specified location from a non-empty list.
o replace() – Replace an element at any position by another element.
o size() – Return the number of elements in the list.
o isEmpty() – Return true if the list is empty, otherwise return false.
o isFull() – Return true if the list is full, otherwise return false.
Stack ADT
A Stack contains elements of the same type arranged in sequential order. All operations take place
at a single end that is top of the stack and following operations can be performed:
o push() – Insert an element at one end of the stack called top.
o pop() – Remove and return the element at the top of the stack, if it is not empty.
o peek() – Return the element at the top of the stack without removing it, if the stack is not
empty.
o size() – Return the number of elements in the stack.
o isEmpty() – Return true if the stack is empty, otherwise return false.
o isFull() – Return true if the stack is full, otherwise return false.
Queue ADT
A Queue contains elements of the same type arranged in sequential order. Operations take place at
both ends, insertion is done at the end and deletion is done at the front. Following operations can be
performed:
o enqueue() – Insert an element at the end of the queue.
o dequeue() – Remove and return the first element of the queue, if the queue is not empty.
o peek() – Return the element of the queue without removing it, if the queue is not empty.
o size() – Return the number of elements in the queue.
o isEmpty() – Return true if the queue is empty, otherwise return false.
o isFull() – Return true if the queue is full, otherwise return false.
From these definitions, it is clear that the definitions do not specify how these ADTs will be
represented and how the operations will be carried out.
There can be different ways to implement an ADT, for example, the List ADT can be implemented
using arrays, or singly linked list or doubly linked list.
Similarly, stack ADT and Queue ADT can be implemented using arrays or linked lists.
STACK
A stack is a linear data structure in which insertions (also called additions and pushes) and removals (also
called deletion and pops) take place at the same end. This end is called as the top of the stack.
The other end is known as bottom of the stack.
In a stack insertion and deletion of elements will be performed in LIFO fashion (Last In First Out).
Stack Errors:
Two things can go wrong with stacks:
Underflow: An attempt was made to pop an empty stack. It doesn't make sense to remove something from
an empty collection. Stack underflow is a common error - calls to isEmpty should be used to guard against
it.
Overflow: An attempt was made to push an item onto a full stack. Items can only be added to a collection
if there is enough available memory to store it into the collection.
Stack ADT
A Stack contains elements of the same type arranged in sequential order. All operations take place at a
single end that is top of the stack and following operations can be performed:
o push() – Insert an element at one end of the stack called top, if it is not full.
o pop() – Remove and return the element at the top of the stack, if it is not empty.
o peek() – Return the element at the top of the stack without removing it, if the stack is not empty.
o size() – Return the number of elements in the stack.
o isEmpty() – Return true if the stack is empty, otherwise return false.
o isFull() – Return true if the stack is full, otherwise return false.
Array representation of Stack – using template
#include <iostream>
using namespace std;
// Class for stack
template <class T>
class StackDemo
{
T* arr;
int top;
int capacity;
public:
StackDemo(int); // constructor
void push(T);
T pop();
T peek();
int size();
bool isEmpty();
bool isFull();
// destructor
~ StackDemo(){
delete[] arr;
}
};
// main function
int main()
{
StackDemo<string> st(2);
st.push("A");
st.push("B");
st.pop();
st.pop();
st.push("C");
st.pop();
return 0;
}
Output:
Inserting A
Inserting B
Removing B
Removing A
Inserting C
Top element is: C
Stack size is 1
Removing C
Stack Is Empty
APPLICATIONS OF STACK
1. Reversing a String.
2. Parsing - Balancing of Parenthesis.
3. Infix to Postfix /Prefix conversion.
4. Postfix expression evaluation.
5. Redo-undo features at many places like editors, Photoshop.
6. Forward and backward feature in web browsers.
7. Used in many algorithms like Tower of Hanoi, tree traversals, stock span problem, histogram problem.
8. Other applications can be Backtracking, Knight tour problem, rat in a maze, N queen problem and Sudoku
solver.
9. In Graph Algorithms like Topological Sorting and Strongly Connected Components.
Algorithm:
1. Scan the infix expression from left to right.
2. If the scanned character is an operand, output it.
3. Else,
a. If the precedence of the scanned operator is greater than the precedence of the operator in the
stack (or the stack is empty or the stack contains a ‘(‘ ), push it.
b. Else, Pop all the operators from the stack which are greater than or equal to in precedence than
the scanned operator. (If you encounter parenthesis while popping then stop there and push the
scanned operator in the stack.)
c. After doing that Push the scanned operator to 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 and output it until a ‘(‘ is encountered, and discard
both the parenthesis.
6. Repeat steps 2-6 until infix expression is scanned.
7. Pop and output from the stack until it is not empty.
public:
StackDemo(int); // constructor
void push(T);
T pop();
T peek();
int size();
bool isEmpty();
bool isFull();
// destructor
~ StackDemo(){
delete[] arr;
}
};
st.push('(');
// If the scanned character is an ')', pop and to output string from the stack
// until an '(' is encountered.
else if(s[i] == ')')
{
while(st.peek() != 'N' && st.peek() != '(')
{
char c = st.peek();
st.pop();
ns += c;
}
if(st.peek() == '(')
{
char c = st.peek();
st.pop();
}
}
}
//Pop all the remaining elements from the stack
while(st.peek() != 'N')
{
char c = st.peek();
st.pop();
ns += c;
}
// main function
int main()
{
char exp[] = "a+b*(c^d-e)^(f+g*h)-i";
infixToPostfix(exp);
return 0;
}
Output:
Inserting N
Inserting +
Inserting *
Inserting (
Inserting ^
Removing ^
Inserting -
Removing -
Removing (
Inserting ^
Inserting (
Inserting +
Inserting *
Removing *
Removing +
Removing (
Removing ^
Removing *
Removing +
Inserting -
Removing -
abcd^e-fgh*+^*+i-
Examples:
Input : A * B + C / D
Output : + * A B/ C D
Input : (A - B/C) * (A/K-L)
Output : *-A/BC-/AKL
Algorithm:
Step-1: Reverse the infix expression i.e A+B*C will become C*B+A. Note while reversing each ‘(‘ will
become ‘)’ and each ‘)’ becomes ‘(‘.
Step-2: Obtain the postfix expression of the modified expression i.e CB*A+.
Step-3: Reverse the postfix expression. Hence in our example prefix is +A*BC.
// CPP program to convert infix to prefix
#include <iostream>
#include <string.h>
#include <bits/stdc++.h>
using namespace std;
bool isOperator(char c)
{
return (!isalpha(c) && !isdigit(c));
}
int getPriority(char C)
{
if (C == '-' || C == '+')
return 1;
else if (C == '*' || C == '/')
return 2;
else if (C == '^')
return 3;
return 0;
}
// Operator found
else {
if (isOperator(char_stack.top())) {
while (getPriority(infix[i])
<= getPriority(char_stack.top())) {
output += char_stack.top();
char_stack.pop();
}
// Reverse infix
reverse(infix.begin(), infix.end());
if (infix[i] == '(') {
infix[i] = ')';
i++;
}
else if (infix[i] == ')') {
infix[i] = '(';
i++;
}
}
// Reverse postfix
reverse(prefix.begin(), prefix.end());
return prefix;
}
// Driver code
int main()
{
string s = ("(a-b/c)*(a/k-l)");
cout << infixToPrefix(s) << std::endl;
return 0;
}
Output:
*-a/bc-/akl
The complexity is linear in time i.e O(n).
Evaluation of Postfix Expression
The Postfix notation is used to represent algebraic expressions.
The expressions written in postfix form are evaluated faster compared to infix notation as parenthesis are
not required in postfix, and the need of remembering operator precedence can be avoided.
Following is algorithm for evaluation postfix expressions.
Algorithm:
1. Create a stack to store operands (or values).
2. Scan the given expression and do following for every scanned element.
…..a) If the element is a number, push it into the stack
…..b) If the element is a operator, pop operands for the operator from stack. Evaluate the operator and
push the result back to the stack
3. When the expression is ended, the number in the stack is the final answer
Example: Let the given expression be “2 3 1 * + 9 -“. We scan all elements one by one.
class StackDemo
{
private:
int* arr;
int top, res, capacity;
public:
StackDemo(int);
void push(int);
int pop();
int peek();
int size();
bool isEmpty();
bool isFull();
void postEval(char[]);
// destructor
~ StackDemo(){
delete[] arr;
}
};
Queue ADT
A Queue contains elements of the same type arranged in sequential order. Operations take place at
both ends, insertion is done at the rear end and deletion is done at the front end.
Following operations can be performed:
o enQueue() – Insert an element at the rear end of the queue, if the queue is not full.
o deQueue() – Remove and return the first element of the queue, if the queue is not empty.
o peekFront() – Return the front element of the queue without removing it, if the queue is not
empty.
o peekRear() – Return the rear element of the queue without removing it, if the queue is not
empty.
o size() – Return the number of elements in the queue.
o isEmpty() – Return true if the queue is empty, otherwise return false.
o isFull() – Return true if the queue is full, otherwise return false.
public:
QueueDemo(int);
void deQueue();
void enQueue(T);
T peekFront();
T peekRear();
int size();
bool isEmpty();
bool isFull();
};
queue[++rear] = item;
count++;
}
int main()
{
QueueDemo<string> que(4);
que.enQueue("a");
que.enQueue("b");
que.enQueue("c");
cout << "Front element is: " << que.peekFront() << endl;
cout << "Rear element is: " << que.peekRear() << endl;
que.deQueue();
que.enQueue("d");
que.deQueue();
que.deQueue();
que.deQueue();
if (que.isEmpty())
cout << "Queue Is Empty\n";
else
cout << "Queue Is Not Empty\n";
que.enQueue("e");
que.deQueue();
return 0;
}
Output:
Inserting a
Inserting b
Inserting c
Front element is: a
Rear element is: c
Removing a
Inserting d
Queue size is 3
Removing b
Removing c
Removing d
Queue Is Empty
OverFlow
Program Terminated
UnderFlow
Program Terminated
CIRCULAR QUEUE:
Circular Queue is a linear data structure in which the operations are performed based on FIFO (First In
First Out) principle and the last position is connected back to the first position to make a circle.
It is also called ‘Ring Buffer’.
In a normal Queue, we can insert elements until queue becomes full. But once queue becomes full, we
cannot insert the next element even if there is an empty space in front of the queue.
The problem can be overcome with the help of a circular queue. In this the last position of the queue is
connected back to the first position to form the memory locations in a circular fashion.
Here when the queue is full, and if there are empty locations before the front they can be reused to perform
more enQueue operations.
Applications of Circular Queue:
1. Traffic lights functioning is the best example for circular queues. The colors in a traffic light follow a
circular pattern.
2. In page replacement algorithm, a circular list of pages is maintained and when a page needs to be replaced,
the page in the front of the queue will be chosen.
// Enqueue function
template <class T>
void CQueueDemo<T>::enQueue(T item)
{
if (isFull())
{
cout << "Circular Queue is full\n";
exit(1);
}
if(front==-1)
{
front=0;
rear=0;
}
else
rear=(rear+1)%capacity;
cqueue[rear] = item; //inserting the item in the circular queue
}
//Dequeue function
template <class T>
T CQueueDemo<T>::deQueue()
{
T val;
if(isEmpty())
{
cout << "Circular Queue is empty\n";
exit(1);
}
val= cqueue[front]; //item to be deleted
if(front==rear)
front=rear=-1;
else
front=(front+1)%capacity;
return val;
}