Data Structure Complete PDF
Data Structure Complete PDF
VANIYAMBADI
PG and Department of Computer Applications
st
1 B.C.A – Semester - II
Overview
- Abstract Data Types (ADTs)
- List ADT
- Array-based implementation-
- Linked list implementation
- Singly linked lists
- Circular linked lists
- Doubly-linked lists
- Applications of lists
- All operations -Insertion-Deletion-Merge
- Polynomial Manipulation
Data:
A collection of raw facts, concepts, figures, observations, occurrences or
instructions in a formalized manner.
Information:
The meaning that is currently assigned to data by means of the conventions
applied to those data (i.e. processed data)
Record:
Collection of related fields.
Data type:
A data type is the characteristic of a variable that determines what kind of data it
can hold.
Data Structures:
Data Structure is the way of organizing, storing, and retrieving data and their
relationship with each other.
Characteristics of data structures:
• It depicts the logical representation of data in computer memory.
• It represents the logical relationship between the various data elements.
• It helps in efficient manipulation of stored data elements.
• It allows the programs to process the data in an efficient manner
Operations on Data Structures: [Link] [Link] [Link] [Link]
What is Data Structure?
A data structure is a storage that is used to store and organize data. It is a way of
arranging data on a computer so that it can be accessed and updated efficiently.
A data structure is not only used for organizing the data. It is also used for
processing, retrieving, and storing data. There are different basic and advanced
types of data structures that are used in almost every program or software system
that has been developed. So we must have good knowledge about data structures.
Why should we learn Data Structures?
● Data Structures and Algorithms are two of the key aspects of Computer
Science.
● Data Structures allow us to organize and store data, whereas Algorithms
allow us to process that data meaningfully.
● Learning Data Structures and Algorithms will help us become better
Programmers.
● We will be able to write code that is more effective and reliable.
● We will also be able to solve problems more quickly and efficiently.
Declaration of Array:
#define maxsize 10
int list[maxsize], n ;
1. Create Operation:
Create operation is used to create the list with „ n „ number of elements .If „ n
„ exceeds the array‟s max size, then elements cannot be inserted into the list.
Otherwise the array elements are stored in the consecutive array locations (i.e.) list
[0], list [1] and so on.
void Create ( )
{
int i;
cout<<"\nEnter the number of elements to be added in the list:\t";
cin>>n;
cout<<"\nEnter the array elements:\t";
for(i=0;i<n;i++)
cin>>list[i];
}
If n=6, the output of creation is as follows:
list[6]
2. Insert Operation:
Insert operation is used to insert an element at particular position in the existing
list. Inserting the element in the last position of an array is easy. But inserting the
element at a particular position in an array is quite difficult since it involves all the
subsequent elements to be shifted one position to the right.
Routine to insert an element in the array:
void Insert( )
{
int i,data,pos;
cout<<"\nEnter the data to be inserted:\t";
cin>>data;
cout<<"\nEnter the position at which element to be inserted:\t";
cin>>pos;
if (pos==n)
cout<<<“Array overflow”;
for(i = n-1 ; i >= pos-1 ; i--)
list[i+1] = list[i];
list[pos-1] = data;
n=n+1;
Display();
}
Consider
an array with 5 elements [max elements = 10 ]
After this four data movement, 15 is inserted in the 2nd position of the array.
3. Deletion Operation:
Deletion is the process of removing an element from the array at any
[Link] an element from the end is easy. If an element is to be deleted
from any particular position ,it requires all subsequent element from that position is
shifted one position towards left.
Routine to delete an element in the array:
void Delete( )
{
int i, pos ;
cout<<"\nEnter the position of the data to be deleted:\t";
cin>>pos;
cout<<("\nThe data deleted is:\t %d", list[pos-1];
for(i=pos-1;i<n-1;i++)
list[i]=list[i+1];
n=n-1;
Display();
}
Consider an array with 5 elements [max elements = 10]
5. Search Operation:
Search( ) operation is used to determine whether a particular element is present in
the list or not. Input the search element to be checked in the list.
Routine to search an element in the array:
void Search( )
{
int search,i,count = 0;
cout<<"\nEnter the element to be searched:\t";
cin>>search;
for(i=0;i<n;i++)
{
if(search == list[i])
count++;
}
if(count==0)
cout<<"\nElement not present in the list";
else
cout<<"\nElement present in the list";
}
Program for array implementation of List
#include<iostream>
#define maxsize 10
int list[maxsize],n;
using namespace std;
void Create();
void Insert();
void Delet();
void Display();
void Search();
void main()
{
int choice;
clrscr();
do
{
cout<<"\n Array Implementation of List\n";
cout<<"\[Link]\n";
cout<<"\[Link]\n";
cout<<"\[Link]\n";
cout<<"\[Link]\n";
cout<<"\[Link]\n";
cout<<"\[Link]\n";
cout<<"\nEnter your choice:\t";
cin>>choice;
switch(choice)
{
case 1: Create();
break;
case 2: Insert();
break;
case 3: Delete();
break;
case 4: Display();
break;
case 5: Search();
break;
case 6: exit(1);
default: cout<<"\nEnter option between 1 - 6\n";
break;
}
}while(choice<7);
}
void Create()
{
int i;
cout<<"\nEnter the number of elements to be added in the list:\t";
cin>>n;
cout<<”\nEnter the array elements:\t";
for(i=0;i<n;i++)
cin>>list[i];
Display();
}
void Insert()
{
int i,data,pos;
cout<<"\nEnter the data to be inserted:\t";
cin>>data;
cout<<"\nEnter the position at which element to be inserted:\t";
cin>>pos;
for(i = n-1 ; i >= pos-1 ; i--)
list[i+1] = list[i];
list[pos-1] = data;
n+=1;
Display();
}
void Delete( )
{
int i,pos;
cout<<"\nEnter the position of the data to be deleted:\t";
cin>>pos;
cout<<"\nThe data deleted is:\t %d", list[pos-1];
for(i=pos-1;i<n-1;i++)
list[i]=list[i+1];
n=n-1;
Display();
}
void Display()
{
int i;
printf("\n**********Elements in the array**********\n");
for(i=0;i<n;i++)
cout<<"%d\t",list[i];
}
void Search()
{
int search,i,count = 0;
cout<<"\nEnter the element to be searched:\t";
cin>>search;
for(i=0;i<n;i++)
{
if(search == list[i])
{
count++;
}
}
if(count==0)
cout<<"\nElement not present in the list";
else
cout<<"\nElement present in the list";
}
Output
Array Implementation of List
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
Enter your choice: 1
Enter the number of elements to be added in the list: 5
Enter the array elements: 1 2 3 4 5
**********Elements in the array**********
12345
Array Implementation of List
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
Enter your choice: 2
Enter the data to be inserted: 3
Enter the position at which element to be inserted: 1
**********Elements in the array**********
312345
Array Implementation of List
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
Enter your choice: 3
Enter the position of the data to be deleted: 4
The data deleted is: 3
**********Elements in the array**********
31245
Array Implementation of List
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
Enter your choice: 5
Enter the element to be searched: 1
Element present in the list
Array Implementation of List
[Link]
[Link]
[Link]
[Link]
[Link]
[Link]
Enter your choice:6
Advantages of array implementation:
[Link] elements are faster to access using random access
[Link] an element is easier
Limitation of array implementation
• An array store its nodes in consecutive memory locations.
• The number of elements in the array is fixed and it is not possible to change
the number of elements .
• Insertion and deletion operation in array are expensive. Since insertion is
performed by pushing the entire array one position down and deletion is
performed by shifting the entire array one position up.
Applications of arrays:
Arrays are particularly used in programs that require storing large collection of
similar type data elements
Step-by-step approach:
1. Create a new node with the given value.
2. Set the next pointer of the new node to the current head.
3. Move the head to point to the new node.
4. Return the new head of the linked list.
To insert a node
at a specific position, traverse the list to the desired position, link the new node to the next
node, and update the links accordingly.
We mainly find the node after which we need to insert the new node. If we encounter a NULL
before reaching that node, it means that the given position is invalid.
Node* insertPos(Node* head, int pos, int data)
{
if (pos < 1) {
cout << "Invalid position!" << endl;
return head;
}
// Special case for inserting at the head
if (pos == 1)
{
Node* temp = new Node(data);
temp->next = head;
return temp;
}
// Traverse the list to find the node before the insertion point
Node* prev = head;
int count = 1;
while (count < pos - 1 && prev != nullptr)
{
prev = prev->next;
count++;
}
// If position is greater than the number of nodes
if (prev == nullptr)
{
cout << "Invalid position!" << endl;
return head;
}
// Insert the new node at the specified position
Node* temp = new Node(data);
temp->next = prev->next;
prev->next = temp;
return head;
}
Deletion in Singly Linked List
Deletion involves removing a node from the linked list. Similar to insertion, there are different
scenarios for deletion:
a. Deletion at the Beginning of Singly Linked List:
To delete
the first node, update the head to point to the second node in the list.
Steps-by-step approach:
Check if the head is NULL.
1. If it is, return NULL (the list is empty).
Store the current head node in a temporary variable temp.
Move the head pointer to the next node.
Delete the temporary node.
Return the new head of the linked list.
Node* removeFirstNode(Node* head)
{
if (head == nullptr)
return nullptr;
// Move the head pointer to the next node
Node* temp = head;
head = head->next;
delete temp;
return head;
}
b. Deletion at the End of Singly Linked List:
To
delete the last node, traverse the list until the second-to-last node and update its next field to
None.
Step-by-step approach:
1. Check if the head is NULL.
If it is, return NULL (the list is empty).
2. Check if the head's next is NULL (only one node in the list).
If true, delete the head and return NULL.
3. Traverse the list to find the second last node (second_last).
4. Delete the last node (the node after second_last).
5. Set the next pointer of the second last node to NULL.
6. Return the head of the linked list.
Node* removeLastNode(Node* head)
{
if (head == nullptr)
return nullptr;
if (head->next == nullptr) {
delete head;
return nullptr;
}
// Find the second last node
Node* second_last = head;
while (second_last->next->next != nullptr)
second_last = second_last->next;
// Delete last node
delete (second_last->next);
// Change next of second last
second_last->next = nullptr;
return head;
}
c. Deletion at a Specific Position of Singly Linked List:
To
delete a node at a specific position, traverse the list to the desired position, update the links to
bypass the node to be deleted.
Step-by-step approach:
1. Check if the list is empty or the position is invalid, return if so.
2. If the head needs to be deleted, update the head and delete the node.
3. Traverse to the node before the position to be deleted.
4. If the position is out of range, return.
5. Store the node to be deleted.
6. Update the links to bypass the node.
7. Delete the stored node.
Node* deleteAtPosition(Node* head, int position)
{
// If the list is empty or the position is invalid
if (head == nullptr || position < 1)
{
return head;
}
// If the head needs to be deleted
if (position == 1)
{
Node* temp = head;
head = head->next;
delete temp;
return head;
}
// Traverse to the node before the position to be
// deleted
Node* current = head;
for (int i = 1; i < position - 1 && current != nullptr; i++)
{
current = current->next;
}
// If the position is out of range
if (current == NULL || current->next == nullptr)
{
return;
}
// Store the node to be deleted
Node* temp = current->next;
// Update the links to bypass the node to be deleted
current->next = current->next->next;
// Delete the node
delete temp;
return head;
}
Example Program using singly linked list
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
void insertStart(Node** head, int data)
{
Node* newNode = new Node;
newNode->data = data;
newNode->next = *head;
*head = newNode;
cout << "\n" << newNode->data << " Inserted" << endl;
}
void deleteStart(Node** head)
{
Node* temp = *head;
if (*head == nullptr)
{
cout << "Linked List Empty, nothing to delete" << endl;
return;
}
*head = (*head)->next;
cout << "\n" << temp->data << " deleted" << endl;
delete temp;
}
void display(Node* node)
{
cout << "\nLinked List: ";
while (node != nullptr)
{
cout << node->data << " ";
node = node->next;
}
cout << endl;
}
int main()
{
Node* head = nullptr;
insertStart(&head, 100);
insertStart(&head, 80);
insertStart(&head, 60);
insertStart(&head, 40);
insertStart(&head, 20);
display(head);
deleteStart(&head);
deleteStart(&head);
display(head);
return 0;
}
Output
100 Inserted
80 Inserted
60 Inserted
40 Inserted
20 Inserted
Linked List: 20 40 60 80 100
20 deleted
40 deleted
Linked List: 60 80 100
Disadvantages of singly linked list in CPP programming
• Members could be assigned anywhere in the memory.
• Each member shall include an address size member, hence it utilizes poor memory.
• Some operations like reversing a list is complicated when compared with arrays,
2. Doubly Linked List or Two-Way Linked List
A Doubly Linked List (DLL) is a two-way list in which each node has two pointers,
the next and previous that have reference to both the next node and previous node
respectively. Unlike a singly linked list where each node only points to the next
node, a doubly linked list has an extra previous pointer that allows traversal in both
the forward and backward directions.
Creating a Node in Doubly Linked List in C++
In the code above, each node has data and a pointer to the next node. When we create
multiple nodes for a circular linked list, we only need to connect the last node back to
the first one.
Example of Creating a Circular Linked List
Here’s an example of creating a circular linked list with three nodes (2, 3, 4):
// Connect nodes
first->next = second;
second->next = last;
#include <iostream>
using namespace std;
struct Node
{
int data;
Node *next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to insert a node into an empty circular singly linked list
Node *insertInEmptyList(Node *last, int data)
{
if (last != nullptr) return last;
// Create a new node
Node *newNode = new Node(data);
// Point newNode to itself
newNode->next = newNode;
// Update last to point to the new node
last = newNode;
return last;
}
void printList(Node* last)
{
if(last == NULL) return;
// Start from the head node
Node* head = last->next;
while (true)
{
cout << head->data << " ";
head = head->next;
if (head == last->next) break;
}
cout << endl;
}
int main()
{
Node *last = nullptr;
// Insert a node into the empty list
last = insertInEmptyList(last, 1);
// Print the list
cout << "List after insertion: ";
printList(last);
return 0;
}
OUTPUT
List after insertion: 1
B. Insertion at the beginning in circular linked list
To insert a new node at the beginning of a circular linked list, we first create the
new node and allocate memory for it. If the list is empty (indicated by the last
pointer being NULL), we make the new node point to itself. If the list already
contains nodes then we set the new node’s next pointer to point to the current
head of the list (which is last->next), and then update the last node’s next pointer
to point to the new node. This maintains the circular structure of the list.
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to insert a node at the beginning of the circular linked list
Node* insertAtBeginning(Node* last, int value)
{
// Allocate memory for the new node and set its data
Node* newNode = new Node(value);
// If the list is empty, make the new node point to itself and set it as last
if (last == nullptr)
{
newNode->next = newNode;
return newNode;
}
// Insert the new node at the beginning
newNode->next = last->next;
last->next = newNode;
return last;
}
void printList(Node* last)
{
if(last == NULL) return;
// Start from the head node
Node* head = last->next;
while (true)
{
cout << head->data << " ";
head = head->next;
if (head == last->next)
break;
}
cout << endl;
}
int main()
{
// Create circular linked list: 2, 3, 4
Node* first = new Node(2);
first->next = new Node(3);
first->next->next = new Node(4);
Node* last = first->next->next;
last->next = first;
cout << "Original list: ";
printList(last);
// Insert 5 at the beginning
last = insertAtBeginning(last, 5);
cout << "List after inserting 5 at the beginning: ";
printList(last);
return 0;
}
Output
Original list: 2 3 4
List after inserting 5 at the beginning: 5 2 3 4
C. Insertion at the end in circular linked list
To insert a new node at the end of a circular linked list, we first create the new
node and allocate memory for it. If the list is empty (mean, last or tail pointer
being NULL), we initialize the list with the new node and making it point to itself
to form a circular structure. If the list already contains nodes then we set the
new node’s next pointer to point to the current head (which is tail->next), then
update the current tail’s next pointer to point to the new node. Finally, we update
the tail pointer to the new node. This will ensure that the new node is now the
last node in the list while maintaining the circular linkage.
#include <iostream>
using namespace std;
struct Node{
int data;
Node *next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to insert a node at the end of a circular linked list
Node *insertEnd(Node *tail, int value)
{
Node *newNode = new Node(value);
if (tail == nullptr){
// If the list is empty, initialize it with the new node
tail = newNode;
// Point to itself to form a circular structure
newNode->next = newNode;
}
else{
// Insert new node after the current tail
// and update the tail pointer.
// New node points to the head node
newNode->next = tail->next;
// Tail node points to the new node
tail->next = newNode;
// Update tail to be the new node
tail = newNode;
}
return tail;
}
void printList(Node *last)
{
if(last == NULL) return;
// Start from the head node
Node *head = last->next;
while (true)
{
cout << head->data << " ";
head = head->next;
if (head == last->next)
break;
}
cout << endl;
}
int main()
{
// Create circular linked list: 2, 3, 4
Node *first = new Node(2);
first->next = new Node(3);
first->next->next = new Node(4);
Node *last = first->next->next;
last->next = first;
cout << "Original list: ";
printList(last);
// Insert elements at the end of the circular linked list
last = insertEnd(last, 5);
last = insertEnd(last, 6);
cout << "List after inserting 5 and 6: ";
printList(last);
return 0;
}
Output
Original list: 2 3 4
List after inserting 5 and 6: 2 3 4 5 6
D. Insertion at specific position in circular linked list
To insert a new node at a specific position in a circular linked list, we first check
if the list is empty. If it is and the position is not 1 then we print an error message
because the position doesn’t exist in the list. If the position is 1 then we create
the new node and make it point to itself. If the list is not empty, we create the
new node and traverse the list to find the correct insertion point. If the position is
1, we insert the new node at the beginning by adjusting the pointers accordingly.
For other positions, we traverse through the list until we reach the desired
position and inserting the new node by updating the pointers. If the new node is
inserted at the end, we also update the last pointer to reference the new node,
maintaining the circular structure of the list.
#include <iostream>
using namespace std;
struct Node
{
int data;
Node *next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to insert a node at a specific position in a circular linked list
Node *insertAtPosition(Node *last, int data, int pos)
{
if (last == nullptr)
{
// If the list is empty
if (pos != 1)
{
cout << "Invalid position!" << endl;
return last;
}
// Create a new node and make it point to itself
Node *newNode = new Node(data);
last = newNode;
last->next = last;
return last;
}
// Create a new node with the given data
Node *newNode = new Node(data);
// curr will point to head initially
Node *curr = last->next;
if (pos == 1)
{
// Insert at the beginning
newNode->next = curr;
last->next = newNode;
return last;
}
To
delete a specific node from a circular linked list, we first check if the list is empty.
If it is then we print a message and return nullptr. If the list contains only one
node and it matches the key then we delete that node and set last to nullptr. If
the node to be deleted is the first node then we update the next pointer of the
last node to skip the head node and delete the head. For other nodes, we
traverse the list using two pointers: curr (to find the node) and prev (to keep
track of the previous node). If we find the node with the matching key then we
update the next pointer of prev to skip the curr node and delete it. If the node is
found and it is the last node, we update the last pointer accordingly. If the node
is not found then do nothing and tail or last as it is. Finally, we return the updated
last pointer.
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to delete a specific node in the circular linked list
Node* deleteSpecificNode(Node* last, int key)
{
if (last == nullptr)
{
// If the list is empty
cout << "List is empty, nothing to delete." << endl;
return nullptr;
}
Node* curr = last->next;
Node* prev = last;
// If the node to be deleted is the only node in the list
if (curr == last && curr->data == key)
{
delete curr;
last = nullptr;
return last;
}
// If the node to be deleted is the first node
if (curr->data == key)
{
last->next = curr->next;
delete curr;
return last;
}
// Traverse the list to find the node to be deleted
while (curr != last && curr->data != key)
{
prev = curr;
curr = curr->next;
}
// If the node to be deleted is found
if (curr->data == key)
{
prev->next = curr->next;
if (curr == last) {
last = prev;
}
delete curr;
}
else
{
// If the node to be deleted is not found
cout << "Node with data " << key
<< " not found." << endl;
}
return last;
}
// Function to print the circular linked list
void printList(Node* last)
{
if (last == NULL)
{
cout << "List is Empty";
return;
}
Node *head = last->next;
while (true)
{
cout << head->data << " ";
head = head->next;
if (head == last->next) break;
}
cout << endl;
}
int main()
{
// Create circular linked list: 2, 3, 4
Node* first = new Node(2);
first->next = new Node(3);
first->next->next = new Node(4);
Node* last = first->next->next;
last->next = first;
To
delete the last node in a circular linked list, we first check if the list is empty. If it
is, we print a message and return nullptr. If the list contains only one node (where
the head is the same as the last), we delete that node and set last to nullptr. For
lists with multiple nodes, we need to traverse the list to find the second last
node. We do this by starting from the head and moving through the list until we
reach the node whose next pointer points to last. Once we find the second last
node then we update its next pointer to point back to the head, this effectively
removing the last node from the list. We then delete the last node to free up
memory and return the updated last pointer, which now points to the last node.
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
Node(int value)
{
data = value;
next = nullptr;
}
};
// Function to delete the last node in the circular linked list
Node* deleteLastNode(Node* last)
{
if (last == nullptr)
{
// If the list is empty
cout << "List is empty, nothing to delete." << endl;
return nullptr;
}
Node* head = last->next;
// If there is only one node in the list
if (head == last)
{
delete last;
last = nullptr;
return last;
}
// Traverse the list to find the second last node
Node* curr = head;
while (curr->next != last)
{
curr = curr->next;
}
// Update the second last node's next pointer
// to point to head
curr->next = head;
delete last;
last = curr;
return last;
}
void printList(Node* last)
{
if(last == NULL) return;
Node *head = last->next;
while (true)
{
cout << head->data << " ";
head = head->next;
if (head == last->next) break;
}
cout << endl;
}
int main()
{
// Create circular linked list: 2, 3, 4
Node* first = new Node(2);
first->next = new Node(3);
first->next->next = new Node(4);
Node* last = first->next->next;
last->next = first;
cout << "Original list: ";
printList(last);
// Delete the last node
last = deleteLastNode(last);
cout << "List after deleting last node: ";
printList(last);
return 0;
}
Output
Original list: 2 3 4
List after deleting last node: 2 3
4 3 2
Consider a polynomial P(x) = 7x + 15x - 2 x + 9. Here 7, 15, -2, and 9 are the
coefficients, and 4,3,2,0 are the exponents of the terms in the polynomial. On
representing this polynomial using a linked list, we have
Observe that the number of nodes equals the number of terms in the polynomial. So
we have 4 nodes. Moreover, the terms are stored to decrease exponents in the linked
list. Such representation of polynomial using linked lists makes the operations like
subtraction, addition, multiplication, etc., on polynomial very easy.
Addition of Polynomials:
To add two polynomials,
● we traverse the list P and Q. We take corresponding terms of the list P and Q
and compare their exponents.
● If the two exponents are equal, the coefficients are added to create a new
coefficient.
o If the new coefficient is equal to 0, then the term is dropped, and if it is not
zero, it is inserted at the end of the new linked list containing the resulting
polynomial.
● If one of the exponents is larger than the other, the corresponding term is
immediately placed into the new linked list, and the term with the smaller
exponent is held to be compared with the next term from the other list. If one list
ends before the other, the rest of the terms of the longer list is inserted at the
end of the new linked list containing the resulting polynomial.
Let us consider an example an example to show how the addition of two polynomials is
performed,
4 3 2
P(x) = 3x + 2x - 4 x + 7
3 2
Q (x) = 5x + 4 x - 5
These polynomials are represented using a linked list in order of decreasing exponents as
follows:
To generate a new linked list for the resulting polynomials that is formed on the
addition of given polynomials P(x) and Q(x), we perform the following steps,
1. Traverse the two lists P and Q and examine all the nodes.
2. We compare the exponents of the corresponding terms of two polynomials.
The first term of polynomials P and Q contain exponents 4 and 3, respectively.
Since the exponent of the first term of the polynomial P is greater than the other
polynomial Q, the term having a larger exponent is inserted into the new list. The
new list initially looks as shown below:
3. We then compare the exponent of the next term of the list P with the exponents
of the present term of list Q. Since the two exponents are equal, so their
coefficients are added and appended to the new list as follows:
4.
Then we move to the next term of P and Q lists and compare their exponents.
Since exponents of both these terms are equal and after addition of their
coefficients, we get 0, so the term is dropped, and no node is appended to the
new list after this,
5.
Moving to the next term of the two lists, P and Q, we find that the corresponding
terms have the same exponents equal to 0. We add their coefficients and
append them to the new list for the resulting polynomial as shown below:
Example:
C++ program to add two polynomials
Input:
2 1 0
1st number = 5x + 4x + 2x
1 0
2nd number = -5x - 5x
Output:
2 1 0
5x -1x -3x
-1 -3
// Driver code
int main()
{
struct Node *poly1 = NULL,
*poly2 = NULL,
*poly = NULL;
// Create first list of 5x^2 + 4x^1 + 2x^0
create_node(5, 2, &poly1);
create_node(4, 1, &poly1);
create_node(2, 0, &poly1);
// Create second list of -5x^1 - 5x^0
create_node(-5, 1, &poly2);
create_node(-5, 0, &poly2);
printf("1st Number: ");
show(poly1);
printf("2nd Number: ");
show(poly2);
poly = (struct Node*)malloc(sizeof(struct Node));
// Function add two polynomial // numbers
polyadd(poly1, poly2, poly);
Additional Resources
1. [Link]
2. [Link]
using-linked-list/
3. [Link] -strudtures/
4. [Link]
Practice Questions:
1. Define data, Information.
2. Define data structure.
3. What are the operations we can do in data structure?
4. What do you mean by non-linear data structure? Give example.
5. What do you linear data structure? Give example.
6. List the various operations that can be performed on data structure.
7. What is abstract data type?
8. Explain Features of ADT.
9. What is a linked list?
10. Define doubly linked list.
11. Difference between arrays and lists.
12. Write a program to implement list based on array.
13. What are the types of Linked List?
14. State the advantages of circular lists over doubly linked list.
15. What are the advantages of doubly linked list over singly linked list?
16. Why is the linked list used for polynomial arithmetic?
17. What is the advantage of linked list over arrays?
18. What is the circular linked list?
19. What is the advantage of an ADT?
20. Define Node.
21. What are the different types of data structure?
22. Write a program to implement singly linked list using c++.
23. Write a program to implement Doubly linked list using c++.
24. Write a program to implement Circular linked list using c++.
25. Explain Polynomial Manipulation.
MARUDHAR KESARI JAIN COLLEGE FOR WOMEN (AUTONOMOUS)
VANIYAMBADI
PG and Department of Computer Applications
st
1 B.C.A – Semester - II
Overview
- Stack ADT Operations and Applications
- Evaluating arithmetic expressions
- Conversion of infix to postfix expression
- Queue ADT-Operations
- Circular Queue
- Priority Queue
- deQueue
- Applications of queues.
STACK
√Stack is a Linear Data Structure that follows Last In First Out (LIFO) principle.
√Insertion and deletion can be done at only one end of the stack called TOP of
the stack.
STACK ADT:
TOP pointer
- It will always point to the last element inserted in the stack.
- For empty stack, top will be pointing to -1. (TOP = -1)
Implementation of Stack
Stack can be implemented in 2 ways.
1. Static Implementation (Array implementation of Stack)
2. Dynamic Implementation (Linked List Implementation of Stack)
1. Implement Stack using Array:
To implement a stack using an array, initialize an array and treat its end as the
stack’s top. Implement push (add to end), pop (remove from end), and peek (check
end) operations, handling cases for an empty or full stack.
Operations on Stack (Stack ADT)
Basic operations performed on the stack are:
1. Push Operation on Stack: Adds an item to the stack. If the stack is full, then
it is said to be an Overflow condition.
1. Push :
√ Before pushing the element to the stack, we check if the stack is full .
√ If the stack is full (top == size-1) , then Stack Overflows and we cannot
insert the element to the stack.
√ Otherwise, we increment the value of top by 1 (top = top + 1) and the new
value is inserted at top position .
√ The elements can be pushed into the stack till we reach the capacity of the
stack.
void push(int x)
{
top++;
arr[top] = x;
}
2. 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.
Pop Operation:
√ Before popping the element from the stack, we check if the stack is
empty .
√ If the stack is empty (top == -1), then Stack Underflows and we cannot
remove any element from the stack.
√ Otherwise, we store the value at top, decrement the value of top by 1
(top = top – 1) and return the stored top value.
int pop()
{
int x = arr[top];
top--;
return x;
}
3. Top or Peek : Returns the top element of the stack.
Top Operation:
√ Before returning the top element from the stack, we check if the stack is
empty.
√ If the stack is empty (top == -1), we simply print “Stack is empty”.
√ Otherwise, we return the element stored at index = top.
int Top()
{
return arr[top];
}
4. isEmpty: Returns true if the stack is empty, else false.
isEmpty Operation:
√ Check for the value of top in stack.
√ If (top == -1), then the stack is empty so return true .
√ Otherwise, the stack is not empty so return false.
int isempty()
{
If(arr[top]==-1)
return 1;
else
return 0;
}
5. isFull: Returns true if the stack is full, else false.
isFull Operation:
√ Check for the value of top in stack.
√
int Top()
{
return arr[top];
}
int Size()
{
return top + 1;
}
int main()
{
push(6);
push(3);
push(7);
cout << "Elements of the stack is :" << endl;
display();
cout << "Top of stack is before deleting any element " << [Link]() << endl;
cout << "Size of stack before deleting any element " << [Link]() << endl;
cout << "The element deleted is " << [Link]() << endl;
cout << "Size of stack after deleting an element " << [Link]() << endl;
cout << "Top of stack after deleting an element " << [Link]() << endl;
return 0;
}
OUTPUT:
Elements of the stack is : 6 3 7
Top of stack is before deleting any element 7
Size of stack before deleting any element 3
The element deleted is 7
Size of stack after deleting an element 2
Top of stack after deleting an element 3
Stack Operations:
1. push(): Insert a new element into the stack i.e just insert a new element at
the beginning of the linked list.
2. pop(): Return the top element of the Stack i.e simply delete the first element
from the linked list.
3. peek(): Return the top element.
4. display(): Print all elements in Stack.
1. Push Operation:
√ Initialise a node
√ Update the value of that node by data i.e. node->data = data
√ Now link this node to the top of the linked list
√ And update top pointer to the current node
2. Pop Operation:
√ First Check whether there is any node present in the linked list or not, if
not then return
√ Otherwise make pointer let say temp to the top node and move forward
the top node by 1 step
√ Now free this temp node
3. Peek Operation:
√ Check if there is any node present or not, if not then return.
√ Otherwise return the value of top node of the linked list
4. Display Operation:
√ Take a temp node and initialize it with top pointer
√ Now start traversing temp till it encounters NULL
√ Simultaneously print the value of the temp node
Write C++ program to implement stack using Linked list
#include <bits/stdc++.h>
using namespace std;
struct node
{
int data;
node* next;
};
struct node* head = NULL;
void push(int x)
{
node* temp;
temp = new node();
temp->data = x;
temp->next = head;
head = temp;
}
bool isEmpty()
{
if (head == NULL)
return true;
else
return false;
}
int top_element()
{
if (head == NULL)
{
cout << "stack is empty" << endl;
}
else
return head->data;
}
void pop()
{
node* temp;
if (isEmpty())
{
cout << "stack is empty" << endl;
}
else
{
temp = head;
head = head->next;
delete(temp);
}
}
void print_stack()
{
struct node* curr;
if (isEmpty())
{
cout << "stack is empty" << endl;
}
else
{
curr = head;
cout << "Elements are: ";
while (curr != NULL)
{
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
}
int main()
{
push(5);
push(3);
push(6);
print_stack();
isEmpty();
cout << "Top: "
<< top_element() << endl;
pop();
print_stack();
cout << "Top: "
<< top_element() << endl;
return 0;
}
OUTPUT:
Elements are: 6 3 5
Top: 6
Elements are: 3 5
Top: 3
Benifits of Linked List Stack in C++
The following are the major benifits of the linked list implementation over the array
implementation:
√ The dynamic memory management of linked list provide dynamic size to
the stack that changes with the change in the number of elements.
√ Rarely reaches the condition of the stack overflow.
APPLICATIONS of STACK
1. Reversing a list
2. Parentheses Checking
3. Conversion of an infix expression into a postfix expression
4. Evaluation of a postfix expression
5. Conversion of an infix expression into a prefix expression
6. Evaluation of a prefix expression
7. Recursion
Evaluation of Arithmetic Expressions
The stack organization is very effective in evaluating arithmetic expressions.
Arithmetic expressions can be represented in 3 forms:
√ Infix Expression: Operators are written between the operands they operate
on,
E.g. A + B.
√ Postfix Expression: Operators are written after operands. E.g A B +
√ Prefix Expression: Operators are written before the operands, E.g + A B
Conversion of Infix to Postfix expression
● An infix and postfix are the expressions. An expression consists of
constants, variables, and symbols. Symbols can be operators or parenthesis.
●
All these
components must be arranged according to a set of rules so that all these
expressions can be evaluated using the set of rules.
In the algebraic expression, the order of the operator precedence is given in the
below table:
The first preference is given to the parenthesis; then next preference is given to the
exponents. In the case of multiple exponent operators, then the operation will be
applied from right to left.
Infix
expression: K + L - M*N + (O^P) * W/U/V * T + Q
The final postfix expression of infix expression(K + L - M*N + (O^P) * W/U/V * T + Q) is
KL+MN*-OP^W*U/V/T*+Q+.
QUEUE
Queue is a linear data structure that follows FIFO (First In First Out) Principle, so the first
element inserted is the first to be popped out.
FIFO Principle states that the first element added to the Queue will be the first one to be
removed or processed. So, Queue is like a line of people waiting to purchase tickets, where
the first person in line is the first person served. (i.e. First Come First Serve).
Basic Terminologies of Queue
● Front: Position of the entry in a queue ready to be served, that is, the first
entry that will be removed from the queue, is called the front of the queue. It
is also referred as the head of the queue.
● Rear: Position of the last entry in the queue, that is, the one most recently
added, is called the rear of the queue. It is also referred as the tail of the
queue.
● Size: Size refers to the current number of elements in the queue.
● Capacity: Capacity refers to the maximum number of elements the queue
can hold.
Representation of Queue
Operations on Queue
1. Enqueue: Enqueue operation adds (or stores) an element to the end of the
queue.
Steps:
√ Check if the queue is full. If so, return an overflow error and exit.
√ If the queue is not full, increment the rear pointer to the next available
position.
√
void enQueue(int);
void deQueue();
void display();
int queue[SIZE], front = -1, rear = -1;
void main()
{
int value, choice;
clrscr();
while(1)
{
cout<<"\n\n***** MENU *****\n";
cout<<"1. Insertion\n2. Deletion\n3. Display\n4. Exit";
cout<<"\nEnter your choice: ";
cin>>choice;
switch(choice)
{
case 1: cout<<”Enter the value to be insert: ";
cin>>value;
enQueue(value);
break;
case 2: deQueue();
break;
case 3: display();
break;
case 4: exit(0);
default:cout<<”\nWrong selection!!! Try again!!!";
}
}
}
void enQueue(int value)
{
if(rear == SIZE-1)
cout<<"\nQueue is Full!!! Insertion is not possible!!!";
else
{
if(front == -1)
front = 0;
rear++;
queue[rear] = value;
cout<<"\nInsertion success!!!";
}
}
void deQueue()
{
if(front == rear)
cout<<”\nQueue is Empty!!! Deletion is not possible!!!";
else{
cout<<"\nDeleted : ", queue[front];
front++;
if(front == rear)
front = rear = -1;
}
}
void display(){
if(rear == -1)
cout<<"\nQueue is Empty!!!";
else
{
int i;
cout<<”\nQueue elements are:\n";
for(i=front; i<=rear; i++)
cout<<"\t",queue[i];
}
}
OUTPUT
void main()
{
int choice, value;
clrscr();
cout<<"\n:: Queue Implementation using Linked List ::\n";
while(1)
{
cout<<"\n****** MENU ******\n";
cout<<"1. Insert\n2. Delete\n3. Display\n4. Exit\n";
cout<<"Enter your choice: ";
cin>>choice;
switch(choice)
{
case 1: cout<<"Enter the value to be insert: ";
cin>>value;
insert(value);
break;
case 2: delete(); break;
case 3: display(); break;
case 4: exit(0);
default: cout<<"\nWrong selection!!! Please try again!!!\n";
}
}
}
void insert(int value)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode -> next = NULL;
if(front == NULL)
front = rear = newNode;
else{
rear -> next = newNode;
rear = newNode;
}
cout<<"\nInsertion is Success!!!\n";
}
void delete()
{
if(front == NULL)
cout<<"\nQueue is Empty!!!\n";
else
{
struct Node *temp = front;
front = front -> next;
cout<<"\nDeleted element: %d\n", temp->data;
free(temp);
}
}
void display()
{
if(front == NULL)
cout<<"\nQueue is Empty!!!\n";
else{
struct Node *temp = front;
while(temp->next != NULL){
cout<< temp->data <<"--->";
temp = temp -> next;
}
cout<< temp->data <<"--->NULL\n",;
}
}
OUTPUT
Types of Queues
Queue data structure can be classified into 4 types:
1. Simple Queue: Simple Queue simply follows FIFO Structure. We can only
insert the element at the back and remove the element from the front of the
queue.
2. Double-Ended Queue (Deque): In a double-ended queue the insertion and
deletion operations, both can be performed from both ends. They are of two
types:
√ Input Restricted Queue: This is a simple queue. In this type of queue, the
input can be taken from only one end but deletion can be done from
any of the ends.
√ Output Restricted Queue: This is also a simple queue. In this type of
queue, the input can be taken from both ends but deletion can be done
from only one end.
3. Circular Queue: This is a special type of queue where the last position is
connected back to the first position. Here also the operations are performed
in FIFO order.
4. Priority Queue: A priority queue is a special queue where the elements are
accessed based on the priority assigned to them. They are of two types:
√ Ascending Priority Queue: In Ascending Priority Queue, the elements are
arranged in increasing order of their priority values. Element with
smallest priority value is popped first.
√ Descending Priority Queue: In Descending Priority Queue, the elements
are arranged in decreasing order of their priority values. Element with
largest priority is popped first.
Circular Queue
In a normal Queue Data Structure, we can insert elements until queue becomes
full. But once the queue becomes full, we cannot insert the next element until all
the elements are deleted from the queue. For example, consider the queue below...
The queue after inserting all the elements into it is as follows...
Now consider the following situation after deleting three elements from the queue...
This situation also says that Queue is Full and we cannot insert the new element because
'rear' is still at last position. In the above situation, even though we have empty positions in
the queue we can not make use of them to insert the new element. This is the major problem
in a normal queue data structure. To overcome this problem we use a circular queue data
structure.
What is Circular Queue?
A Circular Queue can be defined as follows...
A 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.
int main ()
{
CircularQueue q;
[Link] (10);
[Link] (20);
[Link] (30);
[Link] (40);
[Link] ();
[Link] ();
[Link] ();
[Link] ();
[Link] (50);
[Link] (60);
[Link] ();
return 0;
}
Output:
Enqueued element: 10
Enqueued element: 20
Enqueued element: 30
Enqueued element: 40
Elements in the queue: 10 20 30 40
Dequeued element: 10
Dequeued element: 20
Elements in the queue: 30 40
Enqueued element: 50
Enqueued element: 60
Elements in the queue: 30 40 50 60
Priority Queue:
What is a priority queue?
A priority queue is an abstract data type that behaves similarly to the normal queue
except that each element has some priority, i.e., the element with the highest priority would
come first in a priority queue. The priority of the elements in a priority queue will determine
the order in which elements are removed from the priority queue.
Characteristics of a Priority queue
A priority queue is an extension of a queue that contains the following characteristics:
√ Every element in a priority queue has some priority associated with it.
√ An element with the higher priority will be deleted before the deletion of the lesser
priority.
√ If two elements in a priority queue have the same priority, they will be arranged using
the FIFO principle.
Example:
The hospital emergency queue is an ideal real-life example of a priority queue. In this queue
of patients, the patient with the most critical situation is the first in a queue, and the patient
who doesn’t need immediate medical attention will be the last. In this queue, the priority
depends on the medical condition of the patients.
Operation Description
push_front() Inserts the element at the beginning.
push_back() Adds element at the end.
pop_front() Removes the first element from the deque.
pop_back() Removes the last element from the deque.
front() Gets the front element from the deque.
back() Gets the last element from the deque.
empty() Checks whether the deque is empty or not.
size() Determines the number of elements in the deque.
int Deque::getRear()
{
// check whether Deque is empty or not
if (isEmpty() || rear < 0) {
cout << " Underflow\n" << endl;
return -1;
}
return arr[rear];
}
int main()
{
Deque dq(5);
cout << "Insert element at rear end : 5 \n";
[Link](5);
cout << "insert element at rear end : 10 \n";
[Link](10);
cout << "get rear element "<< " " << [Link]() << endl;
[Link]();
cout << "After delete rear element new rear"<< " become " << [Link]() << endl;
cout << "inserting element at front end \n";
[Link](15);
cout << "get front element "<< " " << [Link]() << endl;
[Link]();
cout << "After delete front element new "<< "front become " << [Link]() <<
endl;
return 0;
}
Output
Insert element at rear end : 5
insert element at rear end : 10
get rear element 10
After delete rear element new rear become 5
inserting element at front end
get front element 15
After delete front element new front become 5
Applications of Queue
√Task Scheduling: Queues can be used to schedule tasks based on priority or the order in
which they were received.
√Resource Allocation: Queues can be used to manage and allocate resources, such as printers
or CPU processing time.
√Batch Processing: Queues can be used to handle batch processing jobs, such as data
analysis or image rendering.
√Message Buffering: Queues can be used to buffer messages in communication systems, such
as message queues in messaging systems or buffers in computer networks.
√Event Handling: Queues can be used to handle events in event-driven systems, such as GUI
applications or simulation systems.
√Traffic Management: Queues can be used to manage traffic flow in transportation systems,
such as airport control systems or road networks.
√Operating systems: Operating systems often use queues to manage processes and resources.
For example, a process scheduler might use a queue to manage the order in which
processes are executed.
√Printer queues :In printing systems, queues are used to manage the order in which print jobs
are processed. Jobs are added to the queue as they are submitted, and the printer processes
them in the order they were received.
Advantages of Queue:
√A large amount of data can be managed efficiently with ease.
√Operations such as insertion and deletion can be performed with ease as it follows the first in
first out rule.
√Queues are useful when a particular service is used by multiple consumers.
√Queues are fast in speed for data inter-process communication.
√Queues can be used in the implementation of other data structures.
Disadvantages of Queue:
√The operations such as insertion and deletion of elements from the middle are time
consuming.
√In a classical queue, a new element can only be inserted when the existing elements are
deleted from the queue.
√Searching an element takes O(N) time.
√Maximum size of a queue must be defined prior in case of array implementation.
Practice questions
Section – A
[Link]fine stack.
[Link] out the any two applications of Stack?
[Link]fine PUSH.
[Link]fine POP.
[Link]fine Infix Expression.
[Link]fine Prefix(or)Polish notation.
[Link]fine Postfix(or)Reverse Polish notation.
[Link] the postfix and prefix forms for the expression A+B*(C-D)/(P-R)?
[Link] are the types of Queue?
[Link]fine Queue.
[Link] are the operations on a Queue?
[Link] the application of Queue.
[Link] do you test for an empty Queue?
[Link]fine Circular Queue.
[Link] the advantage and disadvantages of Circular Queue.
[Link] the applications of circular Queue.
[Link] between a Stack and a Queue.
[Link] is Priority Queue?
[Link] the advantage and disadvantages of dequeue.
[Link] are the operations that can be performed on Double ended Queue?
[Link] between Queue and Double ended Queue.
[Link] is Double ended Queue?
Section – B AND C
Additional References
[Link]://[Link]/
[Link]://[Link]/
[Link]://[Link]/dsa
[Link]://[Link]/gate/difference-stack-and-queue-data-structures
MARUDHAR KESARI JAIN COLLEGE FOR WOMEN (AUTONOMOUS)
VANIYAMBADI
PG and Department of Computer Applications
st
1 B.C.A – Semester - II
E-Notes (Study Material)
Overview
- Tree ADT-
- Tree traversals
- Binary Tree ADT
- Expression trees
- applications of trees
- binary search tree ADT
- Heap
- Applications of heap.
Tree ADT
√ A tree is a non-linear data structure that consists of a root node and potentially many
levels of additional nodes that form a hierarchy. A tree can be empty with no nodes
called the null or empty tree or a tree is a structure consisting of one node called the
root and one or more subtrees.
√ Tree data structure is a hierarchical structure that is used to represent and organize
data in the form of parent child relationship.
√ The topmost node of the tree is called the root, and the nodes below it are called the
child nodes. Each node can have multiple child nodes, and these child nodes can also
have their own child nodes, forming a recursive structure.
Structu
re of Tree
Why Tree is considered a non-linear data structure?
The data in a tree are not stored in a sequential manner i.e., they are not stored linearly.
Instead, they are arranged on multiple levels or we can say it is a hierarchical structure. For
this reason, the tree is considered to be a non-linear data structure.
1. Root
√ In a tree data structure, the root is the first node of the tree. The root node is the initial
node of the tree in data structures.
√ In the tree data structure, there must be only one root node.
2. Edge
√ In a tree in data structures, the connecting link of any two nodes is called the edge of
the tree data structure.
√ In the tree data structure, N number of nodes connecting with N -1 number of edges.
3. Parent
In the tree in data structures, the node that is the predecessor of any node is known as a
parent node, or a node with a branch from itself to any other successive node is called the
parent node.
4. Child
√The node, a descendant of any node, is known as child nodes in data structures.
√In a tree, any number of parent nodes can have any number of child nodes.
√In a tree, every node except the root node is a child node.
5. Siblings
In trees in the data structure, nodes that belong to the same parent are called siblings.
6. Leaf
√ Trees in the data structure, the node with no child, is known as a leaf node.
√ In trees, leaf nodes are also called external nodes or terminal nodes.
7. Internal nodes
√ Trees in the data structure have at least one child node known as internal nodes.
√ In trees, nodes other than leaf nodes are internal nodes.
√ Sometimes root nodes are also called internal nodes if the tree has more than one
node.
8. Degree
√ In the tree data structure, the total number of children of a node is called the degree
of the node.
√ The highest degree of the node among all the nodes in a tree is called the Degree of
Tree.
9. Level
√ In tree data structures, the root node is said to be at level 0, and the root node's
children are at level 1, and the children of that node at level 1 will be level 2, and so on.
10. Height
√ In a tree data structure, the number of edges from the leaf node to the particular node
in the longest path is known as the height of that node.
√ In the tree, the height of the root node is called "Height of Tree".
√ The tree height of all leaf nodes is 0.
[Link]
√ In a tree, many edges from the root node to the particular node are called the depth of
the tree.
√ In the tree, the total number of edges from the root node to the leaf node in the
longest path is known as "Depth of Tree".
√ In the tree data structures, the depth of the root node is 0.
12. Path
√ In the tree in data structures, the sequence of nodes and edges from one node to
another node is called the path between those two nodes.
√
13. Subtree
√ In the tree in data structures, each child from a node shapes a sub-tree recursively
and every child in the tree will form a sub-tree on its parent node.
Types of Tree in Data Structures
1. Binary Tree
Properties
A binary search tree is a type of tree that is a more constricted extension of a binary tree data
structure.
Properties
√ Follows all properties of the tree data structure.
√ The binary search tree has a unique property known as the binary search property.
This states that the value of a left child node of the tree should be less than or equal
to the parent node value of the tree. And the value of the right child node should be
greater than or equal to the parent value.
3. Expression Tree
[Link]
The expression tree is a binary tree in which each internal node corresponds to the operator
and each leaf node corresponds to the operand so for example expression tree for 3 +
((5+9)*2) would be:
Binary Tree
struct Node
{
int data;
Node * left, * right;
};
A complete binary tree is a special type of binary tree where all the levels of the tree are filled
completely except the lowest level nodes which are filled from as left as possible.
h
For a given height h, the maximum number of nodes is 2 +1-1.
A complete binary tree of height h is a perfect binary tree up to height h-1, and in the last level
element are stored in left to right order.
Example 1:
The height of the given binary tree is 2 and the maximum number of nodes in that tree is
h+1
n= 2
2+1
n=2 -1
3
n= 2 -1 = 7.
A skewed binary tree is a type of binary tree in which all the nodes have only either one child
or no child.
A complete binary tree is another name for the strict binary tree. Only if each node has either
0 or 2 offspring can the tree be regarded as strict binary tree. The term "strict binary tree" can
also refer to a tree where all nodes, save the leaf nodes, must have two children.
Array Representation is another way to represent binary trees, especially useful when the tree
is complete (all levels are fully filled except possibly the last, which is filled from left to right).
In this method:
√ The tree is stored in an array.
√ For any node at index i:
● Left Child: Located at 2 * i + 1
● Right Child: Located at 2 * i + 2
√
Advantages:
● Easy to navigate parent and child nodes using index calculations, which is fast
● Easier to implement, especially for complete binary trees.
Disadvantages:
● You have to set a size in advance, which can lead to wasted space.
● If the tree is not complete binary tree then then many slots in the array might be
empty, this will result in wasting memory.
● Not as flexible as linked representations for dynamic trees.
Linked representation of a binary tree
This is the simplest way to represent a binary tree. Each node contains data and pointers to its left
and right children.
int data;
Node* left, * right;
Node(int val)
{
data = val;
left = nullptr;
right = nullptr;
}
};
A binary search tree follows some order to arrange the elements. In a Binary search tree, the value of
left node must be smaller than the parent node, and the value of right node must be greater than the
parent node. This rule is applied recursively to the left and right subtrees of the root.
Binary Search Trees (BSTs) have the following properties:
Otherwise, if the element is larger than the root node, then insert it as the root
of the right subtree.
Step 1 - Insert 45.
Now, the creation of binary search tree is completed. After that, let's move towards the
operations that can be performed on Binary search tree.
Deletion Operation
There are three cases for deleting a node from a binary search tree.
Case I
In the first case, the node to be deleted is the leaf node. In such a case,
simply delete the node from the tree. 4 is to be deleted
4 child deleted
Case II
In the second case, the node to be deleted lies has a single child node. In such a case follow the
steps below:
●Replace that node with its child node.
●
Traversal is a process to visit all the nodes of a tree and may print their values too. Because,
all nodes are connected via edges (links) we always start from the root (head) node. That is,
we cannot randomly access a node in a tree.
● Inorder Traversal
● Preorder Traversal
●
Postorder Traversal
1.1Inorder Traversal
1.2Preorder Traversal
1.3
Postorder Traversal
● Visit all the nodes in the left subtree
● Visit all the nodes in the right subtree
● Visit the root node
#include<bits/stdc++.h>
using namespace std;
class Tree
{
public:
int data;
Tree *left = NULL, *right = NULL;
// Constructor initialised
Tree (int x)
{
data = x;
left = NULL;
right = NULL;
}
};
void preorder_traversal (Tree * root)
{
if (root == NULL)
return;
// Print the data
cout << root->data << " ";
// Visit Left subtree
preorder_traversal (root->left);
// Visit right subtree
preorder_traversal (root->right);
}
void inorder_traversal (Tree * root)
{
if (root == NULL)
return;
// Visit Left subtree
inorder_traversal (root->left);
// Print the data
cout << root->data << " ";
// Visit right subtree
inorder_traversal (root->right);
}
void postorder_traversal (Tree * root)
{
if (root == NULL)
return;
// Visit Left subtree
postorder_traversal (root->left);
// Visit right subtree
postorder_traversal (root->right);
// Print the data
cout << root->data << " ";
}
int main ()
{
Tree *root = new Tree (17);
root->left = new Tree (10);
root->right = new Tree (11);
root->left->left = new Tree (7);
root->right->left = new Tree (27);
root->right->right = new Tree (9);
cout << "Preorder => ";
preorder_traversal (root);
cout << endl;
cout << "Inorder => ";
inorder_traversal (root);
cout << endl;
cout << "Postorder => ";
postorder_traversal (root);
cout << endl;
return 0;
}
Output:
Preorder => 17 10 7 11 27 9
Inorder => 7 10 17 27 11 9
Postorder => 7 10 27 9 11 17
Expression Tree
The expression tree is a binary tree with each internal node representing an operand, and
each leaf node representing an operator.
● The operands are always represented by the leaf nodes. These operands are always
used in the operations.
● The operator at the root of the tree is always given top priority.
● When compared to the operators at the bottom of the tree, the operator at the bottom
is always given the lowest priority.
● Because the operand is always present at a depth of the tree, it is given the highest
priority of all operators.
● The expression tree can be traversed to evaluate prefix expressions, postfix
expressions, and infix expressions.
The given expression can be evaluated using the expression tree in data structure.
a + (b * c) + d * (e + f)
For example, the expression (a - b) * (c+d) can be represented as an expression tree where:
● File System
Tree is prevalent used in file system of computer where thre and multiple files and
folder to locate a data and access it. The folder structure can be broken down to
understand in the form of an hierarchical tree data structure.
● Searching for Key
Speacial type of tree known as Binary Search Tree or BST can be used to find key
comparatively faster than other data structures like Array or Linked List.
● Finding Minimum Cost
Spanning trees are used to find the path in a tree with minimum cost such that all the
nodes are covered.
● Storage of elements
Unlike arrays, trees do not have an upper bound for the number of elements like linked
list as it stores the address to nodes are stored in heap that are allocated dynamically.
● XML/HTML Data
Data used in the website that is composed in the hierarchical form is a general
application of tree in data structure that has a usage in website architecture.
● Priority Queue
Priority queue data structure is implemented with the help of heap, a specialized form
of tree used to extract the highest priority element that is found at the root of the tree.
● Indexing in Databases
B Tree and B+ Tree, an advanced form of tree, are used to implement indexing in
MARUDHAR KESARI JAIN COLLEGE FOR WOMEN (AUTONOMOUS)
VANIYAMBADI
PG and Department of Computer Applications
st
1 B.C.A – Semester - II
E-Notes (Study Material)
Overview
- Graph: Definition
- Representation of Graph
- Types of graph
- Breadth first traversal
- Depth first traversal
- Applications of graphs.
Graph:
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(V, E).
Vertices (V): Represent the entities themselves, also called nodes or points.
Edges (E): Represent the relationships between the entities, also called links or lines.
1. Un-Directed Graph
An Undirected graph is one in which the edges do not have a direction is called Un-
directed graph.
For example, the edge between vertex A and vertex B doesn't have any direction,
so we cannot determine whether the edge between vertex A and vertex B starts
from vertex A or vertex B. Similarly, we can't determine the ending vertex of this
edge between these nodes.
2. Directed Graph
Another name for the directed graphs is digraphs. An directed graph is one in which
the edges do not have a direction is called Un-directed graph.
[Link]
structure#types_of_graphs_in_data_structures
[Link]
202003242118236659shruti_saxena_Data%[Link]
3. Symmetric Digraph
Graph in which every node has as an edge in the reverse direction also called as
symmetric digraph. For every edge (u,v) in the graph, there must be one edge with (v,u).
4. Weighted Graph
A graph G= (V, E) is called a labeled or weighted graph because each edge has a value
or weight representing the cost of traversing that edge.
Basic Terminologies to Graph:
Graph Representation
1. Adjacency Matrix
2. Adjacency List
[Link] Matrix
An adjacency matrix is a 2D array of V x V vertices. Each row and column represent a vertex.
If the value of any element a[i][j] is 1, it represents that there is an edge connecting vertex i
and vertex j.
Edge lookup(checking if an edge exists between vertex A and vertex B) is extremely fast in
adjacency matrix representation but we have to reserve space for every possible link between
all vertices(V x V), so it requires more space.
2. Adjacency List
The index of the array represents a vertex and each element in its linked list represents the
other vertices that form an edge with the vertex.
The adjacency list for the graph we made in the first example is as follows:
An adjacency list is efficient in terms of storage because we only need to store the values for
the edges. For a graph with millions of vertices, this can mean a lot of saved space.
Graph Traversal
Graph traversal is a technique used for searching a vertex in a graph. The graph
traversal is also used to decide the order of vertices is visited in the search process. A
graph traversal finds the edges to be used in the search process without creating
loops. That means using graph traversal we visit all the vertices of the graph without
getting into looping path.
Breadth First Search (BFS) algorithm traverses a graph in a breadthward motion to search a
graph data structure for a node that meets a set of criteria. It uses a queue to remember the
next vertex to start a search, when a dead end occurs in any iteration.
Breadth First Search (BFS) algorithm starts at the tree root and explores all nodes at the
present depth prior to moving on to the nodes at the next depth level.
As in the example given above, BFS algorithm traverses from A to B to E to F first then to C
and G lastly to D. It employs the following rules.
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Insert it in a queue.
Rule 2 − If no adjacent vertex is found, remove the first vertex from the queue.
Ste
Traversal Description
p
1 Initialize the queue.
We start from
2
visiting S (starting node),
and mark it as visited.
At this stage, we are left with no unmarked (unvisited) nodes. But as per the algorithm we
keep on dequeuing in order to get all unvisited nodes. When the queue gets emptied, the
program is over.
#include<iostream>
#include<stdlib.h>
using namespace std;
int cost[10][10],i,j,k,n,qu[10],front,rare,v,visit[10],visited[10];
int main()
{
int m;
cout <<"Enter no of vertices:";
cin >> n;
cout <<"Enter no of edges:";
cin >> m;
cout <<"\nEDGES \n";
for(k=1; k<=m; k++)
{
cin >>i>>j;
cost[i][j]=1;
}
cout <<"Enter initial vertex to traverse from:";
cin >>v;
cout <<"Visitied vertices:";
cout <<v<<" ";
visited[v]=1;
k=1;
while(k<n)
{
for(j=1; j<=n; j++)
if(cost[v][j]!=0 && visited[j]!=1 && visit[j]!=1)
{
visit[j]=1;
qu[rare++]=j;
}
v=qu[front++];
cout<<v <<" ";
k++;
visit[v]=0;
visited[v]=1;
}
return 0;
}
Output:
Enter no of vertices:4
Enter no of edges:4
EDGES
12
13
24
34
Enter initial vertex to traverse from:1
Visited vertices:1 2 3 4
12
Depth First Search (DFS) algorithm is a recursive algorithm for searching all the vertices of a
graph or tree data structure. This algorithm traverses a graph in a depthward motion and
uses a stack to remember to get the next vertex to start a search, when a dead end occurs in
any iteration.
.
As in the example given above, DFS algorithm traverses from S to A to D to G to E to B first,
then to F and lastly to C. It employs the following rules.
Rule 1 − Visit the adjacent unvisited vertex. Mark it as visited. Display it. Push it in a stack.
Rule 2 − If no adjacent vertex is found, pop up a vertex from the stack. (It will pop up all the
vertices from the stack, which do not have adjacent vertices.)
We choose B, mark it as
visited and put onto the
5 stack. Here B does not have
any unvisited adjacent node.
So, we pop B from the stack.
As C does not have any unvisited adjacent node so we keep popping the stack until we find a
node that has an unvisited adjacent node. In this case, there's none and we keep popping
until the stack is empty.
OUTPUT
Enter no of vertices:4
Enter no of edges:4
EDGES
12
13
24
34
Enter initial vertex to traverse from:1
DFS ORDER OF VISITED VERTICES:1 2 4 3
● The BFS algorithm helps in finding the shortest path in an unweighted graph.
● DFS is more memory efficient than BFS as it doesn't need to store all the nodes at the
current level.
● Both BFS and DFS can be used to explore all nodes of a graph systematically,
ensuring complete traversal.
● The BFS is not ideal for deep graph traversal or pathfinding in weighted graphs.
● DFS can get stuck in infinite loops if the graph contains cycles unless cycle detection
is implemented.
Here are some of the applications of how graph traversal is used in real-world scenarios:
● Graph traversal is used to analyze social networks, identify communities, and detect
influential users.
● Graph traversal is used in search engines to index and rank web pages.
● GPS navigation systems use graph traversal to find the shortest path between two
points.
● Graph traversal is used to optimize database queries and improve query performance.
● Amazon’s recommendation system uses graph traversal to suggest products based
on user interactions and preferences.
Practice Questions
SECTION-(A)
1. Define graph.
2. Define directed graph or digraph.
3. Define undirected graph.
4. Define a weighted graph.
5. Define adjacency matrix.
6. What does traversing a graph mean? State the different ways of traversing a graph.
7. What is a simple graph?
8. When a graph is said to be bi-connected?
9. What are the applications of graph?
10. How a graph is represented?
11. Define indegree and out degree of a graph.
12. What is breadth-first search?
13. Give the applications of DFS.
14. Differentiate BFS and DFS.
15. Define cut vertices.
16. Define Euler graph or Define Euler circuit.
SECTION-(B)
Additional Resources
1. [Link]
2. [Link]
3. [Link]
4. [Link]
structure
MARUDHAR KESARI JAIN COLLEGE FOR WOMEN (AUTONOMOUS)
VANIYAMBADI
PG and Department of Computer Applications
st
1 B.C.A – Semester - II
E-Notes (Study Material)
Overview
Searching
- Linear Search
- Binary Search
Sorting
- Bubble sort
- Selection sort
- Insertion sort
Hashing
- Hash functions
- Separate chaining
- Open Addressing
- Rehashing Extendible Hashing.
1
Searching
Searching algorithms are methods or procedures used to find a specific item or element
within a collection of data. These algorithms are widely used in computer science and are
crucial for tasks like searching for a particular record in a database, finding an element in a
sorted list, or locating a file on a computer.
There are two categories these searching techniques fall into. They are −
● Sequential Searching
● Interval Searching
Sequential Searching
As the name suggests, the sequential searching operation traverses through each element of
the data sequentially to look for the desired data. The data need not be in a sorted manner
for this type of search.
Example − Linear Search
Program:
#include <iostream>
#include <vector>
using namespace std;
2
int search(vector<int>& arr, int x) {
for (int i = 0; i < [Link](); i++)
if (arr[i] == x)
return i;
return -1;
}
int main() {
vector<int> arr = {2, 3, 4, 10, 40};
int x = 10;
int res = search(arr, x);
if (res == -1)
cout << "Not Present";
else
cout << "Present at Index " << res;
return 0;
}
Output
Present at Index 3
Advantages of Linear Search Algorithm:
● Linear search can be used irrespective of whether the array is sorted or not. It can be
used on arrays of any data type.
● Does not require any additional memory.
● It is a well-suited algorithm for small datasets.
Disadvantages of Linear Search Algorithm:
● Linear search has a time complexity of O(N), which in turn makes it slow for large
datasets.
● Not suitable for large arrays.
When to use Linear Search Algorithm?
● When we are dealing with a small dataset.
● When you are searching for a dataset stored in contiguous memory.
Interval Searching
Unlike sequential searching, the interval searching operation requires the data to be in a
sorted manner. This method usually searches the data in intervals; it could be done by either
dividing the data into multiple sub-parts or jumping through the indices to search for an
element.
3
What is Binary Search Algorithm?
Binary search is a search algorithm used to find the position of a target value within a sorted
array. It works by repeatedly dividing the search interval in half until the target value is found
or the interval is empty. The search interval is halved by comparing the target element with
the middle value of the search space.
4
● The data structure must be sorted.
● Access to any element of the data structure should take constant time.
1. Divide the search space into two halves by finding the middle index “mid”.
2. Compare the middle element of the search space with the key.
3. If the key is found at middle element, the process is terminated.
4. If the key is not found at middle element, choose which half will be used as the next
search space.
● If the key is smaller than the middle element, then the left side is used for
next search.
● If the key is larger than the middle element, then the right side is used for
next search.
5. This process is continued until the key is found or the total search space is
exhausted.
Program:
#include<iostream>
using namespace std;
int main()
{
int len, i, arr[50], num, j, temp, first, last, middle;
cout<<"Enter the Size: ";
cin>>len;
cout<<"Enter "<<len<<" Elements: ";
for(i=0; i<len; i++)
5
cin>>arr[i];
// sort the array first
for(i=0; i<(len-1); i++)
{
for(j=0; j<(len-i-1); j++)
{
if(arr[j]>arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
// print the sorted array
cout<<"\nThe New Sorted Array is:\n";
for(i=0; i<len; i++)
cout<<arr[i]<<" ";
// now back to binary search
cout<<"\n\nEnter Element to be Search: ";
cin>>num;
first = 0;
last = (len-1);
middle = (first+last)/2;
while(first <= last)
{
if(arr[middle]<num)
first = middle+1;
else if(arr[middle]==num)
{
cout<<"\nThe number, "<<num<<" found at Position "<<middle+1;
break;
}
else
last = middle-1;
middle = (first+last)/2;
}
if(first>last)
cout<<"\nThe number, "<<num<<" is not found in given Array";
cout<<endl;
return 0;
}
6
Sorting
7
The sorting algorithm is important in Computer Science because it reduces the complexity
of a problem. There is a wide range of applications for these algorithms, including searching
algorithms, database algorithms, divide and conquer methods, and data structure
algorithms.
● When you have hundreds of datasets you want to print, you might want to arrange
them in some way.
● Once we get the data sorted, we can get the k-th smallest and k-th largest item in
O(1) time.
● Searching any element in a huge data set becomes easy. We can use Binary search
method for search if we have sorted data. So, Sorting become important here.
● They can be used in software and in conceptual problems to solve more advanced
problems.
There are various sorting algorithms are used in data structures. The following two types of
sorting algorithms can be broadly classified:
8
Bubble Sort
Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the
adjacent elements if they are in the wrong order. This algorithm is not suitable for large data
sets as its average and worst-case time complexity are quite high.
● We sort the array using multiple passes. After the first pass, the maximum element
goes to end (its correct position). Same way, after second pass, the second largest
element goes to second last position and so on.
● In every pass, we process only those elements that have already not moved to
correct position. After k passes, the largest k elements must have been moved to the
last k positions.
● In a pass, we consider remaining elements and compare all adjacent and swap if
larger element is before a smaller element. If we keep doing this, we get the largest
(among the remaining elements) at its correct position.
9
Program:
10
#include<iostream>
using namespace std;
int main()
{
int n, i, arr[50], j, temp;
cout<<"Enter the Size (max. 50): ";
cin>>n;
cout<<"Enter "<<n<<" Numbers: ";
for(i=0; i<n; i++)
cin>>arr[i];
cout<<"\nSorting the Array using Bubble Sort Technique..\n";
for(i=0; i<(n-1); i++)
{
for(j=0; j<(n-i-1); j++)
{
if(arr[j]>arr[j+1])
{
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
cout<<"\nArray Sorted Successfully!\n";
cout<<"\nThe New Array is: \n";
for(i=0; i<n; i++)
cout<<arr[i]<<" ";
cout<<endl;
return 0;
}
Output:
11
Insertion Sort
Insertion sort is a simple sorting algorithm that works by iteratively inserting each element
of an unsorted list into its correct position in a sorted portion of the list. It is like sorting
playing cards in your hands. You split the cards into two groups: the sorted cards and the
unsorted cards. Then, you pick a card from the unsorted group and put it in the right place in
the sorted group.
● We start with second element of the array as first element in the array is assumed to
be sorted.
● Compare second element with the first element and check if the second element is
smaller then swap them.
● Move to the third element and compare it with the first two elements and put at its
correct position
● Repeat until the entire array is sorted.
12
Program:
#include<iostream>
using namespace std;
int main()
{
intarr[50], tot, i, j, k, elem, index;
cout<<"Enter the Size for Array: ";
cin>>tot;
cout<<"Enter "<<tot<<" Array Elements: ";
for(i=0; i<tot; i++)
cin>>arr[i];
for(i=1; i<tot; i++)
{
elem = arr[i];
if(elem<arr[i-1])
{
for(j=0; j<=i; j++)
{
if(elem<arr[j])
{
index = j;
for(k=i; k>j; k--)
arr[k] = arr[k-1];
break;
}
}
}
else
continue;
arr[index] = elem;
cout<<"\nStep "<<i<<": ";
for(j=0; j<tot; j++)
cout<<arr[j]<<" ";
}
cout<<"\n\nThe New Array (Sorted Array):\n";
for(i=0; i<tot; i++)
cout<<arr[i]<<" ";
cout<<endl;
return 0;
}
Output:
Enter the Size for Array: 10
13
Enter 10 Array Elements: 10 1 9 2 8 3 7 4 6 5
Step 1: 1 10 9 2 8 3 7 4 6 5
Step 2: 1 9 10 2 8 3 7 4 6 5
Step 3: 1 2 9 10 8 3 7 4 6 5
Step 4: 1 2 8 9 10 3 7 4 6 5
Step 5: 1 2 3 8 9 10 7 4 6 5
Step 6: 1 2 3 7 8 9 10 4 6 5
Step 7: 1 2 3 4 7 8 9 10 6 5
Step 8: 1 2 3 4 6 7 8 9 10 5
Step 9: 1 2 3 4 5 6 7 8 9 10
Quick Sort
QuickSort is a sorting algorithm based on the Divide and Conquer that picks an element as a
pivot and partitions the given array around the picked pivot by placing the pivot in its correct
position in the sorted array.
QuickSort works on the principle of divide and conquer, breaking down the problem into
smaller sub-problems.
14
15
Program:
#include<iostream>
using namespace std;
void swap(intarr[] , int pos1, int pos2)
{
int temp;
temp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = temp;
}
int partition(intarr[], int low, int high, int pivot)
{
int i = low;
int j = low;
while( i <= high)
{
if(arr[i] > pivot)
{
i++;
}
else
{
swap(arr,i,j);
i++;
j++;
}
}
return j-1;
}
void quickSort(intarr[], int low, int high)
{
if(low < high)
{
16
int pivot = arr[high];
intpos = partition(arr, low, high, pivot);
quickSort(arr, low, pos-1);
quickSort(arr, pos+1, high);
}
}
int main()
{
int n ;
cout<< " Enter the size of array:";
cin>>n;
intarr[n];
for(int i = 0 ; i < n; i++)
{
cin>>arr[i];
}
quickSort(arr, 0 , n-1);
cout<<"The sorted array is: ";
for(int i = 0 ; i < n; i++)
{
cout<<arr[i]<<"\t";
}
}
Output:
Enter the size of array:5
32
21
54
76
45
The sorted array is: 21 32 45 54 76
Selection Sort
1. First we find the smallest element and swap it with the first element. This way we
get the smallest element at its correct position.
2. Then we find the smallest among remaining elements (or second smallest) and
17
swap it with the second element.
3. We keep doing this until we get all elements moved to correct position.
18
19
Program:
#include<iostream>
using namespace std;
void swapping(int &a, int &b)
{ //swap the content of a and b
int temp;
temp = a;
a = b;
b = temp;
}
void selectionSort(int *array, int size)
{
int i, j, imin;
for(i = 0; i<size-1; i++)
{
imin = i; //get index of minimum data
for(j = i+1; j<size; j++)
if(array[j] < array[imin])
imin = j;
//placing in correct position
swap(array[i], array[imin]);
}
}
int main()
{
int n;
n = 5;
int arr[5] = {12, 19, 55, 2, 16}; // initialize the array
cout << "Array before Sorting: ";
for(int i = 0; i<n; i++)
cout << arr[i] << " ";
cout << endl;
selectionSort(arr, n);
cout << "Array after Sorting: ";
20
for(int i = 0; i<n; i++)
cout << arr[i] << " ";
cout << endl;
}
OUTPUT:
Hashing
Hashing is a technique used in data structures that efficiently stores and retrieves data in a
way that allows for quick access.
● Hashing involves mapping data to a specific index in a hash table (an array of
items) using a hash function that enables fast retrieval of information based on its
key.
● The great thing about hashing is, we can achieve all three operations (search, insert
and delete) in O(1) time on average.
● Hashing is mainly used to implement a set of distinct items and dictionaries (key
value pairs).
21
Key: A key can be any text or number provided as input to the hash function, which
determines an item’s index or storage position within a data structure. In this scenario, Jane
is the key.
Hash Table: It is a type of data structure that stores data in an array format. The table maps
keys to values using a hash function.
What is Collision?
Collision in hashing is when two or more elements are fighting for the same slot in the hash
table. In simple terms, we can say that If the hash function returns the same index for more
than one element then the collision will occur.
Hash Functions:
It performs the mathematical operation of accepting the key value as input and
producing the hash code or hash value as the output. Some of the characteristics of an ideal
hash function are as follows:
● It must produce the same hash value for the same hash key to be deterministic.
● Every input has a unique hash code. This feature is known as the hash property.
● It must be collision-friendly.
● A little bit of change leads to a drastic change in the output.
● The calculation must be quick
There are many hash functions that use numeric or alphanumeric keys. This article focuses
on discussing different hash functions:
1. Division Method.
2. Mid-Square Method
Division Method
The easiest and quickest way to create a hash value is through division. The k-value is
divided by M in this hash function, and the result is used.
Formula:
h(K) = k mod M
22
Advantages:
Disadvantages:
● Since the hash table maps consecutive keys to successive hash values, this
could result in poor performance.
● There are times when exercising extra caution while selecting M's value is
necessary.
23
The computation of the hash value requires two steps. With a (key, value) pair, a hash
function is computed by calculating the key’s square, or key*key. The code then selects some
digits from the center of the integer to generate the hash value.
Advantages:
● This technique works well because most or all of the digits in the key value affect the
result. All of the necessary digits participate in a process that results in the middle
digits of the squared result.
● The result is not dominated by the top or bottom digits of the initial key value.
Disadvantages:
● The size of the key is one of the limitations of this system; if the key is large, its
square will contain twice as many digits.
● Probability of collisions occurring repeatedly.
24
Separate chaining
Separate Chaining is a collision handling technique. Separate chaining is one of the most
popular and commonly used techniques in order to handle collisions. In this article, we will
discuss about what is Separate Chain collision handling technique, its advantages,
disadvantages, etc.
1. Separate Chaining
2. Open Addressing
1. Hash Table Structure: The hash table is an array of a fixed size. Each element of this
array is a head pointer to a linked list.
2. Hash Function: The hash function takes a key and computes an index in the array.
3. Insertion:
● Apply the hash function to the key to get the index.
● Insert the key-value pair at the head of the linked list at this index.
4. Searching:
● Compute the index for the key using the hash function.
● Search through the linked list at that index for the desired key.
5. Deletion:
● Find the index using the hash function.
● Search the linked list for the key and remove the corresponding node.
25
Algorithm 2
● Memory Overhead: Every element requires extra memory for the pointers in the linked
list.
● Cache Performance: Poor cache performance compared to open addressing due to
the use of linked lists.
● Variable Length: The length of the chains can vary, leading to potentially poor
performance if one chain becomes significantly longer than others.
Open addressing
Open addressing is a collision resolution technique used in hash tables. In open addressing,
all elements are stored directly in the hash table itself. When a collision occurs (i.e., two items
hash to the same slot), the method seeks to find another slot to accommodate one of the
items using a probing sequence.
26
Linear probing
Linear probing is a type of open addressing where the probing sequence is linear.
1. Hash Function: Like any hash table, linear probing starts with a hash function that
computes an initial index for a given key.
2. Insertion:
● Compute the hash for the key to find the initial index.
● If the slot at the computed index is empty, insert the item there.
● If the slot is occupied, check the next slot (i.e., move linearly forward) until an
empty slot is found.
● If the end of the table is reached, wrap around to the beginning.
3. Searching:
● Compute the hash for the key to find the initial index.
● If the slot is empty, the key is not in the table.
● If the slot contains the key, return the item.
● If the slot contains a different key, move linearly forward until the key is found or
an empty slot is encountered.
27
4. Deletion:
1. Memory Efficiency: All data is stored in the hash table, no extra space for pointers like
in separate chaining.
2. Cache-Friendly: Linear memory access pattern is good for cache performance.
Quadratic probing
Quadratic probing is another method of open addressing used in hash tables to resolve
collisions. Unlike linear probing, where the interval between probes is fixed, quadratic probing
28
uses a quadratic function to calculate the interval between probes. This approach helps to
reduce the clustering problem seen in linear probing.
● Use the hash function hash(key) to calculate the initial index for the given key.
● Check if the slot at the calculated initial index contains the key you're searching
for.
● If it does, return the associated value or indicate that the key is present.
● If the slot at the initial index does not contain the key (collision occurred), use
quadratic probing.
29
● Initialize a variable i to 1 (for the first iteration). Calculate the next probe index
using the quadratic probing formula:
● next_index = (initial_index + (i2)) % table_size
● Check if the slot at the next_index contains the key you're searching for.
● If it does, return the associated value or indicate that the key is present.
● If it doesn't, increment i and repeat the probing process until an empty slot is
found or the key is located.
● If the entire probing process completes without finding the key (i.e., an empty slot
is encountered), indicate that the key is not found in the table.
30
● Complexity in Calculation: The probe sequence calculation is more complex than in
linear probing.
● Table Size Constraints: For quadratic probing to work correctly, the table size should
be a prime number, and even then, it does not guarantee that all slots can be probed.
● Load Factor Sensitivity: Like other open addressing methods, as the load factor
increases, performance tends to degrade due to an increase in collisions.
Double hashing
Double hashing is a technique used in hash tables to resolve collisions through open
addressing. Unlike linear or quadratic probing, double hashing uses a second hash function
to calculate the probe sequence. This approach significantly reduces the clustering issues
seen in other probing methods.
Double hashing employs two hash functions. When a collision occurs (i.e., two keys hash to
the same index), the second hash function is used to determine the interval between probes.
1. Initial Hash Function: The first hash function computes an initial index. Let's denote
this as hash1(key).
2. Second Hash Function: A second, independent hash function computes another hash
value. Let's denote this as hash2(key). It's crucial that hash2(key) never evaluates to
zero to ensure that the probe sequence advances.
3. Collision Resolution:
a. Upon collision, the next index to probe is calculated using the formula:
(hash1(key) + i * hash2(key)) % table_size, where i is the ith probe (starting at 0).
b. This process repeats, incrementing i, until an empty slot is found or the table
is deemed full.
Advantages of Double Hashing
1. Complexity: Requires the computation of two hash functions, which might be more
computationally intensive.
2. Design of Hash Functions: It's crucial to design both hash functions carefully to
ensure they are independent and that the second hash function never produces zero.
31
3. Sensitivity to Hash Function Quality: The performance of double hashing is highly
reliant on the quality of both hash functions.
Rehashing
Rehashing is a concept primarily used in computer science and data structures, specifically
in the context of hash tables or hash maps. Hash tables are data structures that allow
efficient storage and retrieval of key-value pairs. They work by using a hash function to map
keys to specific locations (buckets) in an array, where the associated values are stored.
Rehashing is the process of resizing a hash table and redistributing its elements when the
current size of the table no longer efficiently accommodates the number of elements it
contains. The primary goal of rehashing is to maintain a low load factor, which is the ratio of
the number of stored elements to the total number of buckets in the hash table. A low load
factor helps ensure that the hash table remains efficient in terms of time complexity for
insertion, retrieval, and deletion operations.
1. Check the Load Factor: Periodically or after each insertion operation, the hash table
checks its load factor. If the load factor exceeds a predefined threshold (often around
0.7 or 0.8), it indicates that the table is becoming crowded, and rehashing is needed.
2. Create a New Hash Table: A new, larger hash table (usually with double the number
of buckets) is created. The number of buckets is increased to reduce the load factor
and make the table more efficient.
3. Rehashing Process: Each element in the old hash table is rehashed, meaning their
keys are mapped to new bucket positions in the larger table using the updated hash
function. This process redistributes the key-value pairs among the new buckets.
4. Transfer Elements: The key-value pairs are transferred from the old table to the new
table based on their new hash values. This involves copying the data from the old
table to the appropriate locations in the new table.
5. Update References: Any references or pointers to the old hash table are updated to
point to the new hash table.
6. Dispose of the Old Table: Once all elements have been transferred, the old hash table
can be deallocated or discarded.
Extendible hashing
32
Extendible hashing is a dynamic hashing technique used in computer science and database
systems to efficiently organize and search data. It is designed to provide a compromise
between static hashing (which requires a fixed number of buckets) and dynamic hashing
(which may involve frequent rehashing). Extendible hashing dynamically adapts the number
of buckets as data grows, minimizing the overhead associated with rehashing.
1. Initialization: Initially, there is a single directory that contains a fixed number of empty
buckets. The directory acts as an index or directory of the available buckets.
2. Hashing: When a new key-value pair needs to be inserted or searched for, a hash
function is applied to the key to determine the bucket in which the pair should be
stored or searched. The hash function generates an index into the directory.
3. Directory Structure: The directory contains entries, each of which corresponds to a
specific bucket. Initially, all entries point to the same bucket. These pointers are
usually represented as binary strings or integers.
4. Bucket Splitting: When a bucket becomes full (i.e., it reaches a predefined load
factor), it is split into two new buckets. This splitting operation is done by adding a
single bit to the binary string associated with the directory entry that pointed to the full
bucket. This effectively doubles the number of buckets available for storing data.
5. Directory Expansion: As buckets are split and new ones are created, the directory's
structure grows dynamically. The directory entries are updated to reflect the new
bucket locations as they are created.
6. Search and Insertion: When searching for a key or inserting a new key-value pair, the
hash function generates an index into the directory. The directory entry at that index is
used to determine the appropriate bucket to search or insert into.
7. Merging: Over time, if some buckets become empty due to deletions or if the directory
becomes sparsely populated, extendible hashing allows for merging. This involves
reducing the number of buckets by removing a bit from directory entries and merging
adjacent buckets to maintain a balanced structure.
33
Extendible hashing offers several advantages:
● It adapts dynamically to the data distribution, avoiding the need for frequent
rehashing.
● It provides a good balance between space usage and search efficiency.
● It offers predictable performance for search, insertion, and deletion operations.
Additional Resources
1. [Link]
2. [Link]
Practice Questions:
SECTION-(A)
[Link] is meant by sorting and searching?
[Link] the types of [Link] it.
[Link] are the types of sorting?
[Link]fine insertion sort.
[Link] is the time complexity of insertion sort?
[Link]fine selection sort.
[Link] are the advantages and disadvantages of selection sort?
[Link]fine shell sort.
[Link] is the time complexity of shell sort?
[Link]fine bubble sort.
[Link]fine radix sort.
[Link] are the types of searching?
[Link]fine Linear search.
[Link]fine binary search.
[Link] linear and binary search.
[Link] is the time complexity of binary search?
34
[Link] is hashing?List its techniques.
[Link] is hash function?
[Link]fine hash table.
[Link]fine separate chaining.
[Link] is open addressing?
[Link]fine probing.
[Link] is rehashing?
[Link] is the collision in hashing?
[Link]fine extendible hashing.
SECTION-(B)
[Link] an algorithm to sort n numbers using insertion sort.
[Link] an algorithm to sort n numbers using selection sort.
[Link] an algorithm to sort n numbers using shell sort.
[Link] an algorithm to sort n numbers using bubble sort.
[Link] short notes on hashing and the various types of hash function.
[Link] a short notes on hashing and the various collision resolution techniques.
[Link] the rehashing technique.
[Link] the extensible hashing.
SECTION-(C)
[Link] in detail about the sorting and their types.
[Link] in detail about linear search with example program.
[Link] in detail about binary search with example program.
[Link] to implement Separate chaining.
5. Explain Rehashing techniques.
6. Explain Extendible rehashing techniques.
35
MARUDHAR KESARI JAIN COLLEGE FOR WOMEN (AUTONOMOUS),
VANIYAMBADI
QUESTION BANK
REGULATION : 2024-2025
YEAR : I BCA
Prepared By Approved By
[Link]
[Link]
UNIT I
2-Marks Questions:
3. Define array?
18. Differentiate between singly linked list and doubly linked list?
19. Explain the various positions. Where a node can be inserting in singly linked list?
20. Differentiate between primitive data structure and Non-primitive data structure?
5-Marks Questions:
1. What are the differences between linear and non-linear data structure?
9. Define circular linked list. Write the routine for its operations.
10-Marks Questions:
8. Define circular double linked list. Write the routine for its operations.
UNIT II
2-Marks Questions:
1. Define stack.
2. List out the any two applications of Stack?
3. Define PUSH.
4. Define POP.
5. Define Infix Expression.
6. Define Prefix(or)Polish notation.
7. Define Postfix(or)Reverse Polish notation.
8. Write the postfix and prefix forms for the expression A+B*(C-D)/(P-R)?
9. What are the types of Queue?
10. Define Queue.
11. What are the operations on a Queue?
12. List the application of Queue.
13. How do you test for an empty Queue?
14. Define Circular Queue.
15. Give the advantage and disadvantages of Circular Queue.
16. List the applications of circular Queue.
17. Distinguish between a Stack and a Queue.
18. What is Priority Queue?
19. Give the advantage and disadvantages of dequeue.
20. What are the operations that can be performed on Double ended Queue?
21. Differentiate between Queue and Double ended Queue.
22. What is Double ended Queue?
UNIT III
2-Marks Questions:
1. Define tree.
2. Define Height of tree.
3. Define depth of tree.
4. Define sibling.
5. What are the different types of traversing?
6. What are the types of binary trees.
7. List the application of tree.
8. Define complete binary tree.
9. Define binary search tree.
10. What are the various operation performed in the binary search tree.
11. Define expression tree.
12. Define construction of expression trees.
13. Define heap.
14. What are the types of heap?
15. List the applications of heaps.
UNIT IV
2-Marks Questions:
1. Define graph.
2. Define directed graph or digraph.
3. Define undirected graph.
4. Define a weighted graph.
5. Define adjacency matrix.
6. What does traversing a graph mean? State the different ways of traversing a graph.
7. What is a simple graph?
8. When a graph is said to be bi-connected?
9. What are the applications of graph?
10. How a graph is represented?
11. Define indegree and out degree of a graph.
12. What is breadth-first search?
13. Give the applications of DFS.
14. Differentiate BFS and DFS.
15. Define cut vertices.
16. Define Euler graph or Define Euler circuit.
5 -Marks Questions:
10-Marks Questions:
UNIT IV
2-Marks Questions:
5-Marks Questions:
10-Marks Questions:
5-Mark Questions
1. Explain Abstract Data Type (ADT) with its advantages.
2. Explain singly linked list and its operations.
3. Write a note on doubly linked list.
4. Define circular linked list and write routines for its operations.
5. Define polynomial and explain how it is represented using linked list.
10-Mark Questions
1. Write a C++ program to implement singly linked list.
2. Write a C++ program to implement doubly linked list.
3. Write a C++ program to implement circular linked list.
4. Explain polynomial manipulation using linked list in detail.
5-Mark Questions
1. Write an algorithm to convert infix expression to postfix expression.
2. Explain the concept of Stack with illustrations and operations.
3. Explain the concept of Queue with illustrations and operations.
4. Briefly explain Circular Queue.
5. Briefly explain Priority Queue.
10-Mark Questions
1. Explain any two operations on Stack with algorithm.
2. Describe Queue and write algorithms to insert and delete elements.
3. How is an infix expression converted to postfix? Convert A+B*C+(D*E+F)*G to
postfix.
4. Explain all operations available on a Queue in detail.
10-Mark Questions
1. Explain binary tree data structure and its implementation in detail.
2. Discuss all tree traversal techniques with examples.
3. Explain binary search tree with operations in detail.
4. Explain heap data structure and its techniques in detail.
UNIT IV – Graphs
2-Mark Questions
1. Define graph. Differentiate directed and undirected graph.
2. Define weighted graph, adjacency matrix, and indegree/outdegree.
3. What is Breadth-First Search (BFS)?
4. Differentiate BFS and DFS.
5. Define cut vertex and Euler circuit.
6. List the applications of graphs.
5-Mark Questions
1. Explain various types of graphs in detail.
2. Explain the different ways of representing a graph.
3. Explain BFS algorithm with example.
4. Explain DFS algorithm with example.
10-Mark Questions
1. Explain graphs and their various types in detail.
2. Explain graph traversal methods (BFS and DFS) with examples.
UNIT V – Sorting, Searching & Hashing
2-Mark Questions
1. Define sorting and searching. Give types of each.
2. Define insertion sort and its time complexity.
3. Define selection sort. Give its advantages and disadvantages.
4. Define bubble sort.
5. Differentiate linear and binary search. Give time complexity of binary search.
6. What is hashing? List its techniques.
7. Define hash function, hash table, and collision.
8. Define separate chaining, open addressing, and probing.
9. What is rehashing and extendible hashing?
5-Mark Questions
1. Write an algorithm for insertion sort.
2. Write an algorithm for selection sort.
3. Write an algorithm for bubble sort.
4. Write short notes on hashing and various hash functions.
5. Explain collision resolution techniques in hashing.
6. Explain rehashing and extendible hashing.
10-Mark Questions
1. Explain sorting techniques (insertion, selection, bubble) in detail.
2. Explain linear search and binary search with example programs.
3. Explain separate chaining in hashing.
4. Explain rehashing and extendible hashing techniques in detail.