Data Structure and Types
What are Data Structures?
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.
Depending on your requirement and project, it is important to choose the right data structure for your project. For example, if you want to store data sequentially in the memory, then you can go for the Array data structure.
Note: Data structure and data types are slightly different. Data structure is the collection of data types arranged in a specific order.
Types of Data Structure
Basically, data structures are divided into two categories:
- Linear data structure
- Non-linear data structure
Let's learn about each type in detail.
Linear data structures
In linear data structures, the elements are arranged in sequence one after the other. Since elements are arranged in particular order, they are easy to implement.
However, when the complexity of the program increases, the linear data structures might not be the best choice because of operational complexities.
Popular linear data structures are:
1. Array Data Structure
In an array, elements in memory are arranged in continuous memory. All the elements of an array are of the same type. And, the type of elements that can be stored in the form of arrays is determined by the programming language.
To learn more, visit Java Array.
2. Stack Data Structure
In stack data structure, elements are stored in the LIFO principle. That is, the last element stored in a stack will be removed first.
It works just like a pile of plates where the last plate kept on the pile will be removed first. To learn more, visit Stack Data Structure.
3. Queue Data Structure
Unlike stack, the queue data structure works in the FIFO principle where first element stored in the queue will be removed first.
It works just like a queue of people in the ticket counter where first person on the queue will get the ticket first. To learn more, visit Queue Data Structure.
4. Linked List Data Structure
In linked list data structure, data elements are connected through a series of nodes. And, each node contains the data items and address to the next node.
To learn more, visit Linked List Data Structure.
Non linear data structures
Unlike linear data structures, elements in non-linear data structures are not in any sequence. Instead they are arranged in a hierarchical manner where one element will be connected to one or more elements.
Non-linear data structures are further divided into graph and tree based data structures.
1. Graph Data Structure
In graph data structure, each node is called vertex and each vertex is connected to other vertices through edges.
To learn more, visit Graph Data Structure.
Popular Graph Based Data Structures:
- Spanning Tree and Minimum Spanning Tree
- Strongly Connected Components
- Adjacency Matrix
- Adjacency List
2. Trees Data Structure
Similar to a graph, a tree is also a collection of vertices and edges. However, in tree data structure, there can only be one edge between two vertices.
To learn more, visit Tree Data Structure.
Popular Tree based Data Structure
Linear Vs Non-linear Data Structures
Now that we know about linear and non-linear data structures, let's see the major differences between them.
| Linear Data Structures | Non Linear Data Structures |
| The data items are arranged in sequential order, one after the other. | The data items are arranged in non-sequential order (hierarchical manner). |
| All the items are present on the single layer. | The data items are present at different layers. |
| It can be traversed on a single run. That is, if we start from the first element, we can traverse all the elements sequentially in a single pass. | It requires multiple runs. That is, if we start from the first element it might not be possible to traverse all the elements in a single pass. |
| The memory utilization is not efficient. | Different structures utilize memory in different efficient ways depending on the need. |
| The time complexity increase with the data size. | Time complexity remains the same. |
| Example: Arrays, Stack, Queue | Example: Tree, Graph, Map |
Why Data Structure?
Knowledge about data structures help you understand the working of each data structure. And, based on that you can select the right data structures for your project.
This helps you write memory and time efficient code.
To learn more about the importance of data structure, visit Why Learn Data Structure?
Circular Linked List
A circular linked list is a type of linked list in which the first and the last nodes are also connected to each other to form a circle.
There are basically two types of circular linked list:
1. Circular Singly Linked List
Here, the address of the last node consists of the address of the first node.
2. Circular Doubly Linked List
Here, in addition to the last node storing the address of the first node, the first node will also store the address of the last node.
Note: We will be using the singly circular linked list to represent the working of circular linked list.
Representation of Circular Linked List
Let's see how we can represent a circular linked list on an algorithm/code. Suppose we have a linked list:
Here, the single node is represented as
struct Node {
int data;
struct Node * next;
};
Each struct node has a data item and a pointer to the next struct node.
Now we will create a simple circular linked list with three items to understand how this works.
/* Initialize nodes */
struct node *last;
struct node *one = NULL;
struct node *two = NULL;
struct node *three = NULL;
/* Allocate memory */
one = malloc(sizeof(struct node));
two = malloc(sizeof(struct node));
three = malloc(sizeof(struct node));
/* Assign data values */
one->data = 1;
two->data = 2;
three->data = 3;
/* Connect nodes */
one->next = two;
two->next = three;
three->next = one;
/* Save address of third node in last */
last = three;
In the above code, one, two, and three are the nodes with data items 1, 2, and 3 respectively.
For node one
- next stores the address of two (there is no node before it)
For node two
- next stores the address of three
For node three
- next stores
NULL(there is no node after it) - next points to node one
Insertion on a Circular Linked List
We can insert elements at 3 different positions of a circular linked list:
Suppose we have a circular linked list with elements 1, 2, and 3.
Let's add a node with value 6 at different positions of the circular linked list we made above. The first step is to create a new node.
- allocate memory for
newNode - assign the data to
newNode
1. Insertion at the Beginning
- store the address of the current first node in the
newNode(i.e. pointing thenewNodeto the current first node) - point the last node to
newNode(i.e makingnewNodeas head)
2. Insertion in between two nodes
Let's insert newNode after the first node.
- travel to the node given (let this node be
p) - point the
nextofnewNodeto the node next top - store the address of
newNodeatnextofp
3. Insertion at the end
- store the address of the head node to
nextof newNode (makingnewNodethe last node) - point the current last node to
newNode - make
newNodeas the last node
Deletion on a Circular Linked List
Suppose we have a double-linked list with elements 1, 2, and 3.
1. If the node to be deleted is the only node
- free the memory occupied by the node
- store NULL in
last
2. If last node is to be deleted
- find the node before the last node (let it be
temp) - store the address of the node next to the last node in
temp - free the memory of last
- make
tempas the last node
3. If any other nodes are to be deleted
- travel to the node to be deleted (here we are deleting node 2)
- let the node before node 2 be
temp - store the address of the node next to 2 in
temp - free the memory of 2
Circular Linked List Code in Python, Java, C, and C++
# Python code to perform circular linked list operations
class Node:
def __init__(self, data):
self.data = data
self.next = None
class CircularLinkedList:
def __init__(self):
self.last = None
def addToEmpty(self, data):
if self.last != None:
return self.last
# allocate memory to the new node and add data to the node
newNode = Node(data)
# assign last to newNode
self.last = newNode
# create link to iteself
self.last.next = self.last
return self.last
# add node to the front
def addFront(self, data):
# check if the list is empty
if self.last == None:
return self.addToEmpty(data)
# allocate memory to the new node and add data to the node
newNode = Node(data)
# store the address of the current first node in the newNode
newNode.next = self.last.next
# make newNode as last
self.last.next = newNode
return self.last
# add node to the end
def addEnd(self, data):
# check if the node is empty
if self.last == None:
return self.addToEmpty(data)
# allocate memory to the new node and add data to the node
newNode = Node(data)
# store the address of the last node to next of newNode
newNode.next = self.last.next
# point the current last node to the newNode
self.last.next = newNode
# make newNode as the last node
self.last = newNode
return self.last
# insert node after a specific node
def addAfter(self, data, item):
# check if the list is empty
if self.last == None:
return None
newNode = Node(data)
p = self.last.next
while p:
# if the item is found, place newNode after it
if p.data == item:
# make the next of the current node as the next of newNode
newNode.next = p.next
# put newNode to the next of p
p.next = newNode
if p == self.last:
self.last = newNode
return self.last
else:
return self.last
p = p.next
if p == self.last.next:
print(item, "The given node is not present in the list")
break
# delete a node
def deleteNode(self, last, key):
# If linked list is empty
if last == None:
return
# If the list contains only a single node
if (last).data == key and (last).next == last:
last = None
temp = last
d = None
# if last node is to be deleted
if (last).data == key:
# find the node before the last node
while temp.next != last:
temp = temp.next
# point temp node to the next of last i.e. first node
temp.next = (last).next
last = temp.next
# travel to the node to be deleted
while temp.next != last and temp.next.data != key:
temp = temp.next
# if node to be deleted was found
if temp.next.data == key:
d = temp.next
temp.next = d.next
return last
def traverse(self):
if self.last == None:
print("The list is empty")
return
newNode = self.last.next
while newNode:
print(newNode.data, end=" ")
newNode = newNode.next
if newNode == self.last.next:
break
# Driver Code
if __name__ == "__main__":
cll = CircularLinkedList()
last = cll.addToEmpty(6)
last = cll.addEnd(8)
last = cll.addFront(2)
last = cll.addAfter(10, 2)
cll.traverse()
last = cll.deleteNode(last, 8)
print()
cll.traverse()
// Java code to perform circular linked list operations
class CircularLinkedList {
static class Node {
int data;
Node next;
};
static Node addToEmpty(Node last, int data) {
if (last != null)
return last;
// allocate memory to the new node
Node newNode = new Node();
// assign data to the new node
newNode.data = data;
// assign last to newNode
last = newNode;
// create link to iteself
newNode.next = last;
return last;
}
// add node to the front
static Node addFront(Node last, int data) {
if (last == null)
return addToEmpty(last, data);
// allocate memory to the new node
Node newNode = new Node();
// add data to the node
newNode.data = data;
// store the address of the current first node in the newNode
newNode.next = last.next;
// make newNode as head
last.next = newNode;
return last;
}
// add node to the end
static Node addEnd(Node last, int data) {
if (last == null)
return addToEmpty(last, data);
// allocate memory to the new node
Node newNode = new Node();
// add data to the node
newNode.data = data;
// store the address of the head node to next of newNode
newNode.next = last.next;
// point the current last node to the newNode
last.next = newNode;
// make newNode as the last node
last = newNode;
return last;
}
static Node addAfter(Node last, int data, int item) {
if (last == null)
return null;
Node newNode, p;
p = last.next;
do {
// if the item is found, place newNode after it
if (p.data == item) {
// allocate memory to the new node
newNode = new Node();
// add data to the node
newNode.data = data;
// make the next of the current node as the next of newNode
newNode.next = p.next;
// put newNode to the next of p
p.next = newNode;
// if p is the last node, make newNode as the last node
if (p == last)
last = newNode;
return last;
}
p = p.next;
} while (p != last.next);
System.out.println(item + "The given node is not present in the list");
return last;
}
// delete a node
static Node deleteNode(Node last, int key) {
// if linked list is empty
if (last == null)
return null;
// if the list contains only a single node
if (last.data == key && last.next == last) {
last = null;
return last;
}
Node temp = last, d = new Node();
// if last is to be deleted
if (last.data == key) {
// find the node before the last node
while (temp.next != last) {
temp = temp.next;
}
// point temp node to the next of last i.e. first node
temp.next = last.next;
last = temp.next;
}
// travel to the node to be deleted
while (temp.next != last && temp.next.data != key) {
temp = temp.next;
}
// if node to be deleted was found
if (temp.next.data == key) {
d = temp.next;
temp.next = d.next;
}
return last;
}
static void traverse(Node last) {
Node p;
if (last == null) {
System.out.println("List is empty.");
return;
}
p = last.next;
do {
System.out.print(p.data + " ");
p = p.next;
}
while (p != last.next);
}
public static void main(String[] args) {
Node last = null;
last = addToEmpty(last, 6);
last = addEnd(last, 8);
last = addFront(last, 2);
last = addAfter(last, 10, 2);
traverse(last);
deleteNode(last, 8);
traverse(last);
}
}
// C code to perform circular linked list operations
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* addToEmpty(struct Node* last, int data) {
if (last != NULL) return last;
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// assign data to the new node
newNode->data = data;
// assign last to newNode
last = newNode;
// create link to iteself
last->next = last;
return last;
}
// add node to the front
struct Node* addFront(struct Node* last, int data) {
// check if the list is empty
if (last == NULL) return addToEmpty(last, data);
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// store the address of the current first node in the newNode
newNode->next = last->next;
// make newNode as head
last->next = newNode;
return last;
}
// add node to the end
struct Node* addEnd(struct Node* last, int data) {
// check if the node is empty
if (last == NULL) return addToEmpty(last, data);
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// store the address of the head node to next of newNode
newNode->next = last->next;
// point the current last node to the newNode
last->next = newNode;
// make newNode as the last node
last = newNode;
return last;
}
// insert node after a specific node
struct Node* addAfter(struct Node* last, int data, int item) {
// check if the list is empty
if (last == NULL) return NULL;
struct Node *newNode, *p;
p = last->next;
do {
// if the item is found, place newNode after it
if (p->data == item) {
// allocate memory to the new node
newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// make the next of the current node as the next of newNode
newNode->next = p->next;
// put newNode to the next of p
p->next = newNode;
// if p is the last node, make newNode as the last node
if (p == last) last = newNode;
return last;
}
p = p->next;
} while (p != last->next);
printf("\nThe given node is not present in the list");
return last;
}
// delete a node
void deleteNode(struct Node** last, int key) {
// if linked list is empty
if (*last == NULL) return;
// if the list contains only a single node
if ((*last)->data == key && (*last)->next == *last) {
free(*last);
*last = NULL;
return;
}
struct Node *temp = *last, *d;
// if last is to be deleted
if ((*last)->data == key) {
// find the node before the last node
while (temp->next != *last) temp = temp->next;
// point temp node to the next of last i.e. first node
temp->next = (*last)->next;
free(*last);
*last = temp;
}
// travel to the node to be deleted
while (temp->next != *last && temp->next->data != key) {
temp = temp->next;
}
// if node to be deleted was found
if (temp->next->data == key) {
d = temp->next;
temp->next = d->next;
free(d);
}
}
void traverse(struct Node* last) {
struct Node* p;
if (last == NULL) {
printf("The list is empty");
return;
}
p = last->next;
do {
printf("%d ", p->data);
p = p->next;
} while (p != last->next);
}
int main() {
struct Node* last = NULL;
last = addToEmpty(last, 6);
last = addEnd(last, 8);
last = addFront(last, 2);
last = addAfter(last, 10, 2);
traverse(last);
deleteNode(&last, 8);
printf("\n");
traverse(last);
return 0;
}
// C++ code to perform circular linked list operations
#include <iostream>
using namespace std;
struct Node {
int data;
struct Node* next;
};
struct Node* addToEmpty(struct Node* last, int data) {
if (last != NULL) return last;
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// assign data to the new node
newNode->data = data;
// assign last to newNode
last = newNode;
// create link to iteself
last->next = last;
return last;
}
// add node to the front
struct Node* addFront(struct Node* last, int data) {
// check if the list is empty
if (last == NULL) return addToEmpty(last, data);
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// store the address of the current first node in the newNode
newNode->next = last->next;
// make newNode as head
last->next = newNode;
return last;
}
// add node to the end
struct Node* addEnd(struct Node* last, int data) {
// check if the node is empty
if (last == NULL) return addToEmpty(last, data);
// allocate memory to the new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// store the address of the head node to next of newNode
newNode->next = last->next;
// point the current last node to the newNode
last->next = newNode;
// make newNode as the last node
last = newNode;
return last;
}
// insert node after a specific node
struct Node* addAfter(struct Node* last, int data, int item) {
// check if the list is empty
if (last == NULL) return NULL;
struct Node *newNode, *p;
p = last->next;
do {
// if the item is found, place newNode after it
if (p->data == item) {
// allocate memory to the new node
newNode = (struct Node*)malloc(sizeof(struct Node));
// add data to the node
newNode->data = data;
// make the next of the current node as the next of newNode
newNode->next = p->next;
// put newNode to the next of p
p->next = newNode;
// if p is the last node, make newNode as the last node
if (p == last) last = newNode;
return last;
}
p = p->next;
} while (p != last->next);
cout << "\nThe given node is not present in the list" << endl;
return last;
}
// delete a node
void deleteNode(Node** last, int key) {
// if linked list is empty
if (*last == NULL) return;
// if the list contains only a single node
if ((*last)->data == key && (*last)->next == *last) {
free(*last);
*last = NULL;
return;
}
Node *temp = *last, *d;
// if last is to be deleted
if ((*last)->data == key) {
// find the node before the last node
while (temp->next != *last) temp = temp->next;
// point temp node to the next of last i.e. first node
temp->next = (*last)->next;
free(*last);
*last = temp->next;
}
// travel to the node to be deleted
while (temp->next != *last && temp->next->data != key) {
temp = temp->next;
}
// if node to be deleted was found
if (temp->next->data == key) {
d = temp->next;
temp->next = d->next;
free(d);
}
}
void traverse(struct Node* last) {
struct Node* p;
if (last == NULL) {
cout << "The list is empty" << endl;
return;
}
p = last->next;
do {
cout << p->data << " ";
p = p->next;
} while (p != last->next);
}
int main() {
struct Node* last = NULL;
last = addToEmpty(last, 6);
last = addEnd(last, 8);
last = addFront(last, 2);
last = addAfter(last, 10, 2);
traverse(last);
deleteNode(&last, 8);
cout << endl;
traverse(last);
return 0;
}
Circular Linked List Complexity
| Circular Linked List Complexity | Time Complexity | Space Complexity |
| Insertion Operation | O(1) or O(n) | O(1) |
| Deletion Operation | O(1) | O(1) |
1. Complexity of Insertion Operation
- The insertion operations that do not require traversal have the time complexity of
O(1). - And, an insertion that requires traversal has a time complexity of
O(n). - The space complexity is
O(1).
2. Complexity of Deletion Operation
- All deletion operations run with a time complexity of
O(1). - And, the space complexity is
O(1).
Why Circular Linked List?
- The NULL assignment is not required because a node always points to another node.
- The starting point can be set to any node.
- Traversal from the first node to the last node is quick.
Circular Linked List Applications
- It is used in multiplayer games to give a chance to each player to play the game.
- Multiple running applications can be placed in a circular linked list on an operating system. The os keeps on iterating over these applications.
- Definition
- Representation of Circular Linked List
- Insertion at the Beginning
- Insertion in between two nodes
- Insertion at the end
- Deletion on a Circular Linked List
- Circular Linked List Code in Python, Java, C, and C++
- Circular Linked List Complexity
- Why Circular Linked List?
- Circular Linked List Applications
Types of Queues
A queue is a useful data structure in programming. It is similar to the ticket queue outside a cinema hall, where the first person entering the queue is the first person who gets the ticket.
There are four different types of queues:
- Simple Queue
- Circular Queue
- Priority Queue
- Double Ended Queue
Simple Queue
In a simple queue, insertion takes place at the rear and removal occurs at the front. It strictly follows the FIFO (First in First out) rule.
To learn more, visit Queue Data Structure.
Circular Queue
In a circular queue, the last element points to the first element making a circular link.
The main advantage of a circular queue over a simple queue is better memory utilization. If the last position is full and the first position is empty, we can insert an element in the first position. This action is not possible in a simple queue.
To learn more, visit Circular Queue Data Structure.
Priority Queue
A priority queue is a special type of queue in which each element is associated with a priority and is served according to its priority. If elements with the same priority occur, they are served according to their order in the queue.
Insertion occurs based on the arrival of the values and removal occurs based on priority.
To learn more, visit Priority Queue Data Structure.
Deque (Double Ended Queue)
In a double ended queue, insertion and removal of elements can be performed from either from the front or rear. Thus, it does not follow the FIFO (First In First Out) rule.
To learn more, visit Deque Data Structure.
Deque Data Structure
Deque or Double Ended Queue is a type of queue in which insertion and removal of elements can either be performed from the front or the rear. Thus, it does not follow FIFO rule (First In First Out).
Types of Deque
- Input Restricted Deque
In this deque, input is restricted at a single end but allows deletion at both the ends. - Output Restricted Deque
In this deque, output is restricted at a single end but allows insertion at both the ends.
Operations on a Deque
Below is the circular array implementation of deque. In a circular array, if the array is full, we start from the beginning.
But in a linear array implementation, if the array is full, no more elements can be inserted. In each of the operations below, if the array is full, "overflow message" is thrown.
Before performing the following operations, these steps are followed.
- Take an array (deque) of size n.
- Set two pointers
front = -1andrear = 0.
1. Insert at the Front
This operation adds an element at the front.
- Check if the deque is full.
Check the position of front - If the deque is full (i.e.
(front == 0 && rear == n - 1) || (front == rear + 1)), insertion operation cannot be performed (overflow condition). - If the deque is empty, reinitialize
front = 0. And, add the new key intoarray[front]. - If
front = 0, reinitializefront = n-1(last index).
Shift front to the end - Else, decrease front by 1.
- Add the new key 5 into
array[front].
Insert the element at Front
2. Insert at the Rear
This operation adds an element to the rear.
- Check if the deque is full.
Check if deque is full - If the deque is full, insertion operation cannot be performed (overflow condition).
- If the deque is empty, reinitialize
rear = 0. And, add the new key intoarray[rear]. - If
rear = n - 1, reinitializereal = 0(first index). - Else, increase rear by 1.
Increase the rear - Add the new key 5 into
array[rear].
Insert the element at rear
3. Delete from the Front
The operation deletes an element from the front.
- Check if the deque is empty.
Check if deque is empty - If the deque is empty (i.e.
front = -1), deletion cannot be performed (underflow condition). - If the deque has only one element (i.e.
front = rear), setfront = -1andrear = -1. - Else if front is at the last index (i.e.
front = n - 1), setfront = 0. - Else,
front = front + 1.
Increase the front
4. Delete from the Rear
This operation deletes an element from the rear.
- Check if the deque is empty.
Check if deque is empty - If the deque is empty (i.e.
front = -1), deletion cannot be performed (underflow condition). - If the deque has only one element (i.e.
front = rear), setfront = -1andrear = -1, else follow the steps below. - If rear is at the first index (i.e.
rear = 0), reinitializerear = n - 1. - Else,
rear = rear - 1.
Decrease the rear
5. Check Empty
This operation checks if the deque is empty. If front = -1, the deque is empty.
6. Check Full
This operation checks if the deque is full. If front = 0 and rear = n - 1 OR front = rear + 1, the deque is full.
Deque Implementation in Python, Java, C, and C++
# Deque implementaion in python
class Deque:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def addRear(self, item):
self.items.append(item)
def addFront(self, item):
self.items.insert(0, item)
def removeFront(self):
return self.items.pop(0)
def removeRear(self):
return self.items.pop()
def size(self):
return len(self.items)
d = Deque()
print(d.isEmpty())
d.addRear(8)
d.addRear(5)
d.addFront(7)
d.addFront(10)
print(d.size())
print(d.isEmpty())
d.addRear(11)
print(d.removeRear())
print(d.removeFront())
d.addFront(55)
d.addRear(45)
print(d.items)
// Deque implementation in Java
class Deque {
static final int MAX = 100;
int arr[];
int front;
int rear;
int size;
public Deque(int size) {
arr = new int[MAX];
front = -1;
rear = 0;
this.size = size;
}
boolean isFull() {
return ((front == 0 && rear == size - 1) || front == rear + 1);
}
boolean isEmpty() {
return (front == -1);
}
void insertfront(int key) {
if (isFull()) {
System.out.println("Overflow");
return;
}
if (front == -1) {
front = 0;
rear = 0;
}
else if (front == 0)
front = size - 1;
else
front = front - 1;
arr[front] = key;
}
void insertrear(int key) {
if (isFull()) {
System.out.println(" Overflow ");
return;
}
if (front == -1) {
front = 0;
rear = 0;
}
else if (rear == size - 1)
rear = 0;
else
rear = rear + 1;
arr[rear] = key;
}
void deletefront() {
if (isEmpty()) {
System.out.println("Queue Underflow\n");
return;
}
// Deque has only one element
if (front == rear) {
front = -1;
rear = -1;
} else if (front == size - 1)
front = 0;
else
front = front + 1;
}
void deleterear() {
if (isEmpty()) {
System.out.println(" Underflow");
return;
}
if (front == rear) {
front = -1;
rear = -1;
} else if (rear == 0)
rear = size - 1;
else
rear = rear - 1;
}
int getFront() {
if (isEmpty()) {
System.out.println(" Underflow");
return -1;
}
return arr[front];
}
int getRear() {
if (isEmpty() || rear < 0) {
System.out.println(" Underflow\n");
return -1;
}
return arr[rear];
}
public static void main(String[] args) {
Deque dq = new Deque(4);
System.out.println("Insert element at rear end : 12 ");
dq.insertrear(12);
System.out.println("insert element at rear end : 14 ");
dq.insertrear(14);
System.out.println("get rear element : " + dq.getRear());
dq.deleterear();
System.out.println("After delete rear element new rear become : " + dq.getRear());
System.out.println("inserting element at front end");
dq.insertfront(13);
System.out.println("get front element: " + dq.getFront());
dq.deletefront();
System.out.println("After delete front element new front become : " + +dq.getFront());
}
}
// Deque implementation in C
#include <stdio.h>
#define MAX 10
void addFront(int *, int, int *, int *);
void addRear(int *, int, int *, int *);
int delFront(int *, int *, int *);
int delRear(int *, int *, int *);
void display(int *);
int count(int *);
int main() {
int arr[MAX];
int front, rear, i, n;
front = rear = -1;
for (i = 0; i < MAX; i++)
arr[i] = 0;
addRear(arr, 5, &front, &rear);
addFront(arr, 12, &front, &rear);
addRear(arr, 11, &front, &rear);
addFront(arr, 5, &front, &rear);
addRear(arr, 6, &front, &rear);
addFront(arr, 8, &front, &rear);
printf("\nElements in a deque: ");
display(arr);
i = delFront(arr, &front, &rear);
printf("\nremoved item: %d", i);
printf("\nElements in a deque after deletion: ");
display(arr);
addRear(arr, 16, &front, &rear);
addRear(arr, 7, &front, &rear);
printf("\nElements in a deque after addition: ");
display(arr);
i = delRear(arr, &front, &rear);
printf("\nremoved item: %d", i);
printf("\nElements in a deque after deletion: ");
display(arr);
n = count(arr);
printf("\nTotal number of elements in deque: %d", n);
}
void addFront(int *arr, int item, int *pfront, int *prear) {
int i, k, c;
if (*pfront == 0 && *prear == MAX - 1) {
printf("\nDeque is full.\n");
return;
}
if (*pfront == -1) {
*pfront = *prear = 0;
arr[*pfront] = item;
return;
}
if (*prear != MAX - 1) {
c = count(arr);
k = *prear + 1;
for (i = 1; i <= c; i++) {
arr[k] = arr[k - 1];
k--;
}
arr[k] = item;
*pfront = k;
(*prear)++;
} else {
(*pfront)--;
arr[*pfront] = item;
}
}
void addRear(int *arr, int item, int *pfront, int *prear) {
int i, k;
if (*pfront == 0 && *prear == MAX - 1) {
printf("\nDeque is full.\n");
return;
}
if (*pfront == -1) {
*prear = *pfront = 0;
arr[*prear] = item;
return;
}
if (*prear == MAX - 1) {
k = *pfront - 1;
for (i = *pfront - 1; i < *prear; i++) {
k = i;
if (k == MAX - 1)
arr[k] = 0;
else
arr[k] = arr[i + 1];
}
(*prear)--;
(*pfront)--;
}
(*prear)++;
arr[*prear] = item;
}
int delFront(int *arr, int *pfront, int *prear) {
int item;
if (*pfront == -1) {
printf("\nDeque is empty.\n");
return 0;
}
item = arr[*pfront];
arr[*pfront] = 0;
if (*pfront == *prear)
*pfront = *prear = -1;
else
(*pfront)++;
return item;
}
int delRear(int *arr, int *pfront, int *prear) {
int item;
if (*pfront == -1) {
printf("\nDeque is empty.\n");
return 0;
}
item = arr[*prear];
arr[*prear] = 0;
(*prear)--;
if (*prear == -1)
*pfront = -1;
return item;
}
void display(int *arr) {
int i;
printf("\n front: ");
for (i = 0; i < MAX; i++)
printf(" %d", arr[i]);
printf(" :rear");
}
int count(int *arr) {
int c = 0, i;
for (i = 0; i < MAX; i++) {
if (arr[i] != 0)
c++;
}
return c;
}
// Deque implementation in C++
#include <iostream>
using namespace std;
#define MAX 10
class Deque {
int arr[MAX];
int front;
int rear;
int size;
public:
Deque(int size) {
front = -1;
rear = 0;
this->size = size;
}
void insertfront(int key);
void insertrear(int key);
void deletefront();
void deleterear();
bool isFull();
bool isEmpty();
int getFront();
int getRear();
};
bool Deque::isFull() {
return ((front == 0 && rear == size - 1) ||
front == rear + 1);
}
bool Deque::isEmpty() {
return (front == -1);
}
void Deque::insertfront(int key) {
if (isFull()) {
cout << "Overflow\n"
<< endl;
return;
}
if (front == -1) {
front = 0;
rear = 0;
}
else if (front == 0)
front = size - 1;
else
front = front - 1;
arr[front] = key;
}
void Deque ::insertrear(int key) {
if (isFull()) {
cout << " Overflow\n " << endl;
return;
}
if (front == -1) {
front = 0;
rear = 0;
}
else if (rear == size - 1)
rear = 0;
else
rear = rear + 1;
arr[rear] = key;
}
void Deque ::deletefront() {
if (isEmpty()) {
cout << "Queue Underflow\n"
<< endl;
return;
}
if (front == rear) {
front = -1;
rear = -1;
} else if (front == size - 1)
front = 0;
else
front = front + 1;
}
void Deque::deleterear() {
if (isEmpty()) {
cout << " Underflow\n"
<< endl;
return;
}
if (front == rear) {
front = -1;
rear = -1;
} else if (rear == 0)
rear = size - 1;
else
rear = rear - 1;
}
int Deque::getFront() {
if (isEmpty()) {
cout << " Underflow\n"
<< endl;
return -1;
}
return arr[front];
}
int Deque::getRear() {
if (isEmpty() || rear < 0) {
cout << " Underflow\n"
<< endl;
return -1;
}
return arr[rear];
}
int main() {
Deque dq(4);
cout << "insert element at rear end \n";
dq.insertrear(5);
dq.insertrear(11);
cout << "rear element: "
<< dq.getRear() << endl;
dq.deleterear();
cout << "after deletion of the rear element, the new rear element: " << dq.getRear() << endl;
cout << "insert element at front end \n";
dq.insertfront(8);
cout << "front element: " << dq.getFront() << endl;
dq.deletefront();
cout << "after deletion of front element new front element: " << dq.getFront() << endl;
}
Time Complexity
The time complexity of all the above operations is constant i.e. O(1).
Applications of Deque Data Structure
Decrease Key and Delete Node Operations on a Fibonacci Heap
A fibonacci heap is a tree based data structure which consists of a collection of trees with min heap or max heap property. Its operations are more efficient in terms of time complexity than those of its similar data structures like binomial heap and binary heap.
Now, we will discuss two of its important operations.
- Decrease a key: decreases the value of a the key to any lower value
- Delete a node: deletes the given node
Decreasing a Key
In decreasing a key operation, the value of a key is decreased to a lower value.
Following functions are used for decreasing the key.
Decrease-Key
- Select the node to be decreased, x, and change its value to the new value k.
- If the parent of x, y, is not null and the key of parent is greater than that of the k then call
Cut(x)andCascading-Cut(y)subsequently. - If the key of x is smaller than the key of min, then mark x as min.
Cut
- Remove x from the current position and add it to the root list.
- If x is marked, then mark it as false.
Cascading-Cut
- If the parent of y is not null then follow the following steps.
- If y is unmarked, then mark y.
- Else, call
Cut(y)andCascading-Cut(parent of y).
Decrease Key Example
The above operations can be understood in the examples below.
Example: Decreasing 46 to 15.
- Decrease the value 46 to 15.
Decrease 46 to 15 - Cut part: Since
24 ≠ nilland15 < its parent, cut it and add it to the root list. Cascading-Cut part: mark 24.
Add 15 to root list and mark 24
Example: Decreasing 35 to 5
- Decrease the value 35 to 5.
Decrease 35 to 5 - Cut part: Since
26 ≠ nilland5<its parent, cut it and add it to the root list.
Cut 5 and add it to root list - Cascading-Cut part: Since 26 is marked, the flow goes to
CutandCascading-Cut.
Cut(26): Cut 26 and add it to the root list and mark it as false.
Cut 26 and add it to root list
Cascading-Cut(24):
Since the 24 is also marked, again callCut(24)andCascading-Cut(7). These operations result in the tree below.
Cut 24 and add it to root list - Since
5 < 7, mark 5 as min.
Mark 5 as min
Deleting a Node
This process makes use of decrease-key and extract-min operations. The following steps are followed for deleting a node.
- Let k be the node to be deleted.
- Apply decrease-key operation to decrease the value of k to the lowest possible value (i.e. -∞).
- Apply extract-min operation to remove this node.
Decrease key and Delete node operations in Python, Java and C/C++
# Fibonacci Heap in python
import math
class FibonacciTree:
def __init__(self, key):
self.key = key
self.children = []
self.parent = None
self.marked = False
self.order = 0
def add_at_end(self, t):
self.children.append(t)
t.parent = self
self.order = self.order + 1
class FibonacciHeap:
def __init__(self):
self.trees = []
self.least = None
self.count = 0
def insert(self, key):
new_tree = FibonacciTree(key)
self.trees.append(new_tree)
if self.least is None or key < self.least.key:
self.least = new_tree
self.count += 1
def get_min(self):
if self.least is None:
return None
return self.least.key
def extract_min(self):
smallest = self.least
if smallest is not None:
for child in smallest.children:
child.parent = None
self.trees.append(child)
self.trees.remove(smallest)
if not self.trees:
self.least = None
else:
self.least = self.trees[0]
self.consolidate()
self.count -= 1
return smallest.key
def consolidate(self):
aux = (floor_log2(self.count) + 1) * [None]
while self.trees:
x = self.trees[0]
order = x.order
self.trees.remove(x)
while aux[order] is not None:
y = aux[order]
if x.key > y.key:
x, y = y, x
x.add_at_end(y)
aux[order] = None
order += 1
aux[order] = x
self.least = None
for k in aux:
if k is not None:
self.trees.append(k)
if self.least is None or k.key < self.least.key:
self.least = k
def decrease_key(self, x, new_key):
if new_key > x.key:
raise ValueError("New key is greater than current key")
x.key = new_key
y = x.parent
if y is not None and x.key < y.key:
self.cut(x, y)
self.cascading_cut(y)
if x.key < self.least.key:
self.least = x
def cut(self, x, y):
y.children.remove(x)
y.order -= 1
x.parent = None
x.marked = False
self.trees.append(x)
def cascading_cut(self, y):
z = y.parent
if z is not None:
if not y.marked:
y.marked = True
else:
self.cut(y, z)
self.cascading_cut(z)
def delete(self, x):
self.decrease_key(x, float('-inf'))
self.extract_min()
def floor_log2(x):
return math.frexp(x)[1] - 1
fheap = FibonacciHeap()
fheap.insert(11)
fheap.insert(10)
fheap.insert(39)
fheap.insert(26)
fheap.insert(24)
print('Minimum value: {}'.format(fheap.get_min()))
print('Minimum value removed: {}'.format(fheap.extract_min()))
// Operations on Fibonacci Heap in Java
class node {
node parent;
node left;
node right;
node child;
int degree;
boolean mark;
int key;
public node() {
this.degree = 0;
this.mark = false;
this.parent = null;
this.left = this;
this.right = this;
this.child = null;
this.key = Integer.MAX_VALUE;
}
node(int x) {
this();
this.key = x;
}
void set_parent(node x) {
this.parent = x;
}
node get_parent() {
return this.parent;
}
void set_left(node x) {
this.left = x;
}
node get_left() {
return this.left;
}
void set_right(node x) {
this.right = x;
}
node get_right() {
return this.right;
}
void set_child(node x) {
this.child = x;
}
node get_child() {
return this.child;
}
void set_degree(int x) {
this.degree = x;
}
int get_degree() {
return this.degree;
}
void set_mark(boolean m) {
this.mark = m;
}
boolean get_mark() {
return this.mark;
}
void set_key(int x) {
this.key = x;
}
int get_key() {
return this.key;
}
}
public class fibHeap {
node min;
int n;
boolean trace;
node found;
public boolean get_trace() {
return trace;
}
public void set_trace(boolean t) {
this.trace = t;
}
public static fibHeap create_heap() {
return new fibHeap();
}
fibHeap() {
min = null;
n = 0;
trace = false;
}
private void insert(node x) {
if (min == null) {
min = x;
x.set_left(min);
x.set_right(min);
} else {
x.set_right(min);
x.set_left(min.get_left());
min.get_left().set_right(x);
min.set_left(x);
if (x.get_key() < min.get_key())
min = x;
}
n += 1;
}
public void insert(int key) {
insert(new node(key));
}
public void display() {
display(min);
System.out.println();
}
private void display(node c) {
System.out.print("(");
if (c == null) {
System.out.print(")");
return;
} else {
node temp = c;
do {
System.out.print(temp.get_key());
node k = temp.get_child();
display(k);
System.out.print("->");
temp = temp.get_right();
} while (temp != c);
System.out.print(")");
}
}
public static void merge_heap(fibHeap H1, fibHeap H2, fibHeap H3) {
H3.min = H1.min;
if (H1.min != null && H2.min != null) {
node t1 = H1.min.get_left();
node t2 = H2.min.get_left();
H1.min.set_left(t2);
t1.set_right(H2.min);
H2.min.set_left(t1);
t2.set_right(H1.min);
}
if (H1.min == null || (H2.min != null && H2.min.get_key() < H1.min.get_key()))
H3.min = H2.min;
H3.n = H1.n + H2.n;
}
public int find_min() {
return this.min.get_key();
}
private void display_node(node z) {
System.out.println("right: " + ((z.get_right() == null) ? "-1" : z.get_right().get_key()));
System.out.println("left: " + ((z.get_left() == null) ? "-1" : z.get_left().get_key()));
System.out.println("child: " + ((z.get_child() == null) ? "-1" : z.get_child().get_key()));
System.out.println("degree " + z.get_degree());
}
public int extract_min() {
node z = this.min;
if (z != null) {
node c = z.get_child();
node k = c, p;
if (c != null) {
do {
p = c.get_right();
insert(c);
c.set_parent(null);
c = p;
} while (c != null && c != k);
}
z.get_left().set_right(z.get_right());
z.get_right().set_left(z.get_left());
z.set_child(null);
if (z == z.get_right())
this.min = null;
else {
this.min = z.get_right();
this.consolidate();
}
this.n -= 1;
return z.get_key();
}
return Integer.MAX_VALUE;
}
public void consolidate() {
double phi = (1 + Math.sqrt(5)) / 2;
int Dofn = (int) (Math.log(this.n) / Math.log(phi));
node[] A = new node[Dofn + 1];
for (int i = 0; i <= Dofn; ++i)
A[i] = null;
node w = min;
if (w != null) {
node check = min;
do {
node x = w;
int d = x.get_degree();
while (A[d] != null) {
node y = A[d];
if (x.get_key() > y.get_key()) {
node temp = x;
x = y;
y = temp;
w = x;
}
fib_heap_link(y, x);
check = x;
A[d] = null;
d += 1;
}
A[d] = x;
w = w.get_right();
} while (w != null && w != check);
this.min = null;
for (int i = 0; i <= Dofn; ++i) {
if (A[i] != null) {
insert(A[i]);
}
}
}
}
private void fib_heap_link(node y, node x) {
y.get_left().set_right(y.get_right());
y.get_right().set_left(y.get_left());
node p = x.get_child();
if (p == null) {
y.set_right(y);
y.set_left(y);
} else {
y.set_right(p);
y.set_left(p.get_left());
p.get_left().set_right(y);
p.set_left(y);
}
y.set_parent(x);
x.set_child(y);
x.set_degree(x.get_degree() + 1);
y.set_mark(false);
}
private void find(int key, node c) {
if (found != null || c == null)
return;
else {
node temp = c;
do {
if (key == temp.get_key())
found = temp;
else {
node k = temp.get_child();
find(key, k);
temp = temp.get_right();
}
} while (temp != c && found == null);
}
}
public node find(int k) {
found = null;
find(k, this.min);
return found;
}
public void decrease_key(int key, int nval) {
node x = find(key);
decrease_key(x, nval);
}
private void decrease_key(node x, int k) {
if (k > x.get_key())
return;
x.set_key(k);
node y = x.get_parent();
if (y != null && x.get_key() < y.get_key()) {
cut(x, y);
cascading_cut(y);
}
if (x.get_key() < min.get_key())
min = x;
}
private void cut(node x, node y) {
x.get_right().set_left(x.get_left());
x.get_left().set_right(x.get_right());
y.set_degree(y.get_degree() - 1);
x.set_right(null);
x.set_left(null);
insert(x);
x.set_parent(null);
x.set_mark(false);
}
private void cascading_cut(node y) {
node z = y.get_parent();
if (z != null) {
if (y.get_mark() == false)
y.set_mark(true);
else {
cut(y, z);
cascading_cut(z);
}
}
}
public void delete(node x) {
decrease_key(x, Integer.MIN_VALUE);
int p = extract_min();
}
public static void main(String[] args) {
fibHeap obj = create_heap();
obj.insert(7);
obj.insert(26);
obj.insert(30);
obj.insert(39);
obj.insert(10);
obj.display();
System.out.println(obj.extract_min());
obj.display();
System.out.println(obj.extract_min());
obj.display();
System.out.println(obj.extract_min());
obj.display();
System.out.println(obj.extract_min());
obj.display();
System.out.println(obj.extract_min());
obj.display();
}
}
// Operations on a Fibonacci heap in C
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
typedef struct _NODE
{
int key;
int degree;
struct _NODE *left_sibling;
struct _NODE *right_sibling;
struct _NODE *parent;
struct _NODE *child;
bool mark;
bool visited;
} NODE;
typedef struct fibanocci_heap
{
int n;
NODE *min;
int phi;
int degree;
} FIB_HEAP;
FIB_HEAP *make_fib_heap();
void insertion(FIB_HEAP *H, NODE *new, int val);
NODE *extract_min(FIB_HEAP *H);
void consolidate(FIB_HEAP *H);
void fib_heap_link(FIB_HEAP *H, NODE *y, NODE *x);
NODE *find_min_node(FIB_HEAP *H);
void decrease_key(FIB_HEAP *H, NODE *node, int key);
void cut(FIB_HEAP *H, NODE *node_to_be_decrease, NODE *parent_node);
void cascading_cut(FIB_HEAP *H, NODE *parent_node);
void Delete_Node(FIB_HEAP *H, int dec_key);
FIB_HEAP *make_fib_heap()
{
FIB_HEAP *H;
H = (FIB_HEAP *)malloc(sizeof(FIB_HEAP));
H->n = 0;
H->min = NULL;
H->phi = 0;
H->degree = 0;
return H;
}
void new_print_heap(NODE *n)
{
NODE *x;
for (x = n;; x = x->right_sibling)
{
if (x->child == NULL)
{
printf("node with no child (%d) \n", x->key);
}
else
{
printf("NODE(%d) with child (%d)\n", x->key, x->child->key);
new_print_heap(x->child);
}
if (x->right_sibling == n)
{
break;
}
}
}
void insertion(FIB_HEAP *H, NODE *new, int val)
{
new = (NODE *)malloc(sizeof(NODE));
new->key = val;
new->degree = 0;
new->mark = false;
new->parent = NULL;
new->child = NULL;
new->visited = false;
new->left_sibling = new;
new->right_sibling = new;
if (H->min == NULL)
{
H->min = new;
}
else
{
H->min->left_sibling->right_sibling = new;
new->right_sibling = H->min;
new->left_sibling = H->min->left_sibling;
H->min->left_sibling = new;
if (new->key < H->min->key)
{
H->min = new;
}
}
(H->n)++;
}
NODE *find_min_node(FIB_HEAP *H)
{
if (H == NULL)
{
printf(" \n Fibonacci heap not yet created \n");
return NULL;
}
else
return H->min;
}
FIB_HEAP *unionHeap(FIB_HEAP *H1, FIB_HEAP *H2)
{
FIB_HEAP *Hnew;
Hnew = make_fib_heap();
Hnew->min = H1->min;
NODE *temp1, *temp2;
temp1 = Hnew->min->right_sibling;
temp2 = H2->min->left_sibling;
Hnew->min->right_sibling->left_sibling = H2->min->left_sibling;
Hnew->min->right_sibling = H2->min;
H2->min->left_sibling = Hnew->min;
temp2->right_sibling = temp1;
if ((H1->min == NULL) || (H2->min != NULL && H2->min->key < H1->min->key))
Hnew->min = H2->min;
Hnew->n = H1->n + H2->n;
return Hnew;
}
int cal_degree(int n)
{
int count = 0;
while (n > 0)
{
n = n / 2;
count++;
}
return count;
}
void consolidate(FIB_HEAP *H)
{
int degree, i, d;
degree = cal_degree(H->n);
NODE *A[degree], *x, *y, *z;
for (i = 0; i <= degree; i++)
{
A[i] = NULL;
}
x = H->min;
do
{
d = x->degree;
while (A[d] != NULL)
{
y = A[d];
if (x->key > y->key)
{
NODE *exchange_help;
exchange_help = x;
x = y;
y = exchange_help;
}
if (y == H->min)
H->min = x;
fib_heap_link(H, y, x);
if (y->right_sibling == x)
H->min = x;
A[d] = NULL;
d++;
}
A[d] = x;
x = x->right_sibling;
} while (x != H->min);
H->min = NULL;
for (i = 0; i < degree; i++)
{
if (A[i] != NULL)
{
A[i]->left_sibling = A[i];
A[i]->right_sibling = A[i];
if (H->min == NULL)
{
H->min = A[i];
}
else
{
H->min->left_sibling->right_sibling = A[i];
A[i]->right_sibling = H->min;
A[i]->left_sibling = H->min->left_sibling;
H->min->left_sibling = A[i];
if (A[i]->key < H->min->key)
{
H->min = A[i];
}
}
if (H->min == NULL)
{
H->min = A[i];
}
else if (A[i]->key < H->min->key)
{
H->min = A[i];
}
}
}
}
void fib_heap_link(FIB_HEAP *H, NODE *y, NODE *x)
{
y->right_sibling->left_sibling = y->left_sibling;
y->left_sibling->right_sibling = y->right_sibling;
if (x->right_sibling == x)
H->min = x;
y->left_sibling = y;
y->right_sibling = y;
y->parent = x;
if (x->child == NULL)
{
x->child = y;
}
y->right_sibling = x->child;
y->left_sibling = x->child->left_sibling;
x->child->left_sibling->right_sibling = y;
x->child->left_sibling = y;
if ((y->key) < (x->child->key))
x->child = y;
(x->degree)++;
}
NODE *extract_min(FIB_HEAP *H)
{
if (H->min == NULL)
printf("\n The heap is empty");
else
{
NODE *temp = H->min;
NODE *pntr;
pntr = temp;
NODE *x = NULL;
if (temp->child != NULL)
{
x = temp->child;
do
{
pntr = x->right_sibling;
(H->min->left_sibling)->right_sibling = x;
x->right_sibling = H->min;
x->left_sibling = H->min->left_sibling;
H->min->left_sibling = x;
if (x->key < H->min->key)
H->min = x;
x->parent = NULL;
x = pntr;
} while (pntr != temp->child);
}
(temp->left_sibling)->right_sibling = temp->right_sibling;
(temp->right_sibling)->left_sibling = temp->left_sibling;
H->min = temp->right_sibling;
if (temp == temp->right_sibling && temp->child == NULL)
H->min = NULL;
else
{
H->min = temp->right_sibling;
consolidate(H);
}
H->n = H->n - 1;
return temp;
}
return H->min;
}
void cut(FIB_HEAP *H, NODE *node_to_be_decrease, NODE *parent_node)
{
NODE *temp_parent_check;
if (node_to_be_decrease == node_to_be_decrease->right_sibling)
parent_node->child = NULL;
node_to_be_decrease->left_sibling->right_sibling = node_to_be_decrease->right_sibling;
node_to_be_decrease->right_sibling->left_sibling = node_to_be_decrease->left_sibling;
if (node_to_be_decrease == parent_node->child)
parent_node->child = node_to_be_decrease->right_sibling;
(parent_node->degree)--;
node_to_be_decrease->left_sibling = node_to_be_decrease;
node_to_be_decrease->right_sibling = node_to_be_decrease;
H->min->left_sibling->right_sibling = node_to_be_decrease;
node_to_be_decrease->right_sibling = H->min;
node_to_be_decrease->left_sibling = H->min->left_sibling;
H->min->left_sibling = node_to_be_decrease;
node_to_be_decrease->parent = NULL;
node_to_be_decrease->mark = false;
}
void cascading_cut(FIB_HEAP *H, NODE *parent_node)
{
NODE *aux;
aux = parent_node->parent;
if (aux != NULL)
{
if (parent_node->mark == false)
{
parent_node->mark = true;
}
else
{
cut(H, parent_node, aux);
cascading_cut(H, aux);
}
}
}
void decrease_key(FIB_HEAP *H, NODE *node_to_be_decrease, int new_key)
{
NODE *parent_node;
if (H == NULL)
{
printf("\n FIbonacci heap not created ");
return;
}
if (node_to_be_decrease == NULL)
{
printf("Node is not in the heap");
}
else
{
if (node_to_be_decrease->key < new_key)
{
printf("\n Invalid new key for decrease key operation \n ");
}
else
{
node_to_be_decrease->key = new_key;
parent_node = node_to_be_decrease->parent;
if ((parent_node != NULL) && (node_to_be_decrease->key < parent_node->key))
{
printf("\n cut called");
cut(H, node_to_be_decrease, parent_node);
printf("\n cascading cut called");
cascading_cut(H, parent_node);
}
if (node_to_be_decrease->key < H->min->key)
{
H->min = node_to_be_decrease;
}
}
}
}
void *find_node(FIB_HEAP *H, NODE *n, int key, int new_key)
{
NODE *find_use = n;
NODE *f = NULL;
find_use->visited = true;
if (find_use->key == key)
{
find_use->visited = false;
f = find_use;
decrease_key(H, f, new_key);
}
if (find_use->child != NULL)
{
find_node(H, find_use->child, key, new_key);
}
if ((find_use->right_sibling->visited != true))
{
find_node(H, find_use->right_sibling, key, new_key);
}
find_use->visited = false;
}
FIB_HEAP *insertion_procedure()
{
FIB_HEAP *temp;
int no_of_nodes, ele, i;
NODE *new_node;
temp = (FIB_HEAP *)malloc(sizeof(FIB_HEAP));
temp = NULL;
if (temp == NULL)
{
temp = make_fib_heap();
}
printf(" \n enter number of nodes to be insert = ");
scanf("%d", &no_of_nodes);
for (i = 1; i <= no_of_nodes; i++)
{
printf("\n node %d and its key value = ", i);
scanf("%d", &ele);
insertion(temp, new_node, ele);
}
return temp;
}
void Delete_Node(FIB_HEAP *H, int dec_key)
{
NODE *p = NULL;
find_node(H, H->min, dec_key, -5000);
p = extract_min(H);
if (p != NULL)
printf("\n Node deleted");
else
printf("\n Node not deleted:some error");
}
int main(int argc, char **argv)
{
NODE *new_node, *min_node, *extracted_min, *node_to_be_decrease, *find_use;
FIB_HEAP *heap, *h1, *h2;
int operation_no, new_key, dec_key, ele, i, no_of_nodes;
heap = (FIB_HEAP *)malloc(sizeof(FIB_HEAP));
heap = NULL;
while (1)
{
printf(" \n choose below operations \n 1. Create Fibonacci heap \n 2. Insert nodes into fibonacci heap \n 3. Find min \n 4. Union \n 5. Extract min \n 6. Decrease key \n 7.Delete node \n 8. print heap \n 9. exit \n enter operation_no = ");
scanf("%d", &operation_no);
switch (operation_no)
{
case 1:
heap = make_fib_heap();
break;
case 2:
if (heap == NULL)
{
heap = make_fib_heap();
}
printf(" enter number of nodes to be insert = ");
scanf("%d", &no_of_nodes);
for (i = 1; i <= no_of_nodes; i++)
{
printf("\n node %d and its key value = ", i);
scanf("%d", &ele);
insertion(heap, new_node, ele);
}
break;
case 3:
min_node = find_min_node(heap);
if (min_node == NULL)
printf("No minimum value");
else
printf("\n min value = %d", min_node->key);
break;
case 4:
if (heap == NULL)
{
printf("\n no FIbonacci heap is created please create fibonacci heap \n ");
break;
}
h1 = insertion_procedure();
heap = unionHeap(heap, h1);
printf("Unified Heap:\n");
new_print_heap(heap->min);
break;
case 5:
if (heap == NULL)
printf("Fibonacci heap is empty");
else
{
extracted_min = extract_min(heap);
printf("\n min value = %d", extracted_min->key);
printf("\n Updated heap: \n");
new_print_heap(heap->min);
}
break;
case 6:
if (heap == NULL)
printf("Fibonacci heap is empty");
else
{
printf(" \n node to be decreased = ");
scanf("%d", &dec_key);
printf(" \n enter the new key = ");
scanf("%d", &new_key);
find_use = heap->min;
find_node(heap, find_use, dec_key, new_key);
printf("\n Key decreased- Corresponding heap:\n");
new_print_heap(heap->min);
}
break;
case 7:
if (heap == NULL)
printf("Fibonacci heap is empty");
else
{
printf(" \n Enter node key to be deleted = ");
scanf("%d", &dec_key);
Delete_Node(heap, dec_key);
printf("\n Node Deleted- Corresponding heap:\n");
new_print_heap(heap->min);
break;
}
case 8:
new_print_heap(heap->min);
break;
case 9:
free(new_node);
free(heap);
exit(0);
default:
printf("Invalid choice ");
}
}
}
// Operations on a Fibonacci heap in C++
#include <iostream>
#include <cmath>
#include <cstdlib>
using namespace std;
struct node
{
int n;
int degree;
node *parent;
node *child;
node *left;
node *right;
char mark;
char C;
};
class FibonacciHeap
{
private:
int nH;
node *H;
public:
node *InitializeHeap();
int Fibonnaci_link(node *, node *, node *);
node *Create_node(int);
node *Insert(node *, node *);
node *Union(node *, node *);
node *Extract_Min(node *);
int Consolidate(node *);
int Display(node *);
node *Find(node *, int);
int Decrease_key(node *, int, int);
int Delete_key(node *, int);
int Cut(node *, node *, node *);
int Cascase_cut(node *, node *);
FibonacciHeap() { H = InitializeHeap(); }
};
node *FibonacciHeap::InitializeHeap()
{
node *np;
np = NULL;
return np;
}
node *FibonacciHeap::Create_node(int value)
{
node *x = new node;
x->n = value;
return x;
}
node *FibonacciHeap::Insert(node *H, node *x)
{
x->degree = 0;
x->parent = NULL;
x->child = NULL;
x->left = x;
x->right = x;
x->mark = 'F';
x->C = 'N';
if (H != NULL)
{
(H->left)->right = x;
x->right = H;
x->left = H->left;
H->left = x;
if (x->n < H->n)
H = x;
}
else
{
H = x;
}
nH = nH + 1;
return H;
}
int FibonacciHeap::Fibonnaci_link(node *H1, node *y, node *z)
{
(y->left)->right = y->right;
(y->right)->left = y->left;
if (z->right == z)
H1 = z;
y->left = y;
y->right = y;
y->parent = z;
if (z->child == NULL)
z->child = y;
y->right = z->child;
y->left = (z->child)->left;
((z->child)->left)->right = y;
(z->child)->left = y;
if (y->n < (z->child)->n)
z->child = y;
z->degree++;
}
node *FibonacciHeap::Union(node *H1, node *H2)
{
node *np;
node *H = InitializeHeap();
H = H1;
(H->left)->right = H2;
(H2->left)->right = H;
np = H->left;
H->left = H2->left;
H2->left = np;
return H;
}
int FibonacciHeap::Display(node *H)
{
node *p = H;
if (p == NULL)
{
cout << "The Heap is Empty" << endl;
return 0;
}
cout << "The root nodes of Heap are: " << endl;
do
{
cout << p->n;
p = p->right;
if (p != H)
{
cout << "-->";
}
} while (p != H && p->right != NULL);
cout << endl;
}
node *FibonacciHeap::Extract_Min(node *H1)
{
node *p;
node *ptr;
node *z = H1;
p = z;
ptr = z;
if (z == NULL)
return z;
node *x;
node *np;
x = NULL;
if (z->child != NULL)
x = z->child;
if (x != NULL)
{
ptr = x;
do
{
np = x->right;
(H1->left)->right = x;
x->right = H1;
x->left = H1->left;
H1->left = x;
if (x->n < H1->n)
H1 = x;
x->parent = NULL;
x = np;
} while (np != ptr);
}
(z->left)->right = z->right;
(z->right)->left = z->left;
H1 = z->right;
if (z == z->right && z->child == NULL)
H = NULL;
else
{
H1 = z->right;
Consolidate(H1);
}
nH = nH - 1;
return p;
}
int FibonacciHeap::Consolidate(node *H1)
{
int d, i;
float f = (log(nH)) / (log(2));
int D = f;
node *A[D];
for (i = 0; i <= D; i++)
A[i] = NULL;
node *x = H1;
node *y;
node *np;
node *pt = x;
do
{
pt = pt->right;
d = x->degree;
while (A[d] != NULL)
{
y = A[d];
if (x->n > y->n)
{
np = x;
x = y;
y = np;
}
if (y == H1)
H1 = x;
Fibonnaci_link(H1, y, x);
if (x->right == x)
H1 = x;
A[d] = NULL;
d = d + 1;
}
A[d] = x;
x = x->right;
}
while (x != H1);
H = NULL;
for (int j = 0; j <= D; j++)
{
if (A[j] != NULL)
{
A[j]->left = A[j];
A[j]->right = A[j];
if (H != NULL)
{
(H->left)->right = A[j];
A[j]->right = H;
A[j]->left = H->left;
H->left = A[j];
if (A[j]->n < H->n)
H = A[j];
}
else
{
H = A[j];
}
if (H == NULL)
H = A[j];
else if (A[j]->n < H->n)
H = A[j];
}
}
}
int FibonacciHeap::Decrease_key(node *H1, int x, int k)
{
node *y;
if (H1 == NULL)
{
cout << "The Heap is Empty" << endl;
return 0;
}
node *ptr = Find(H1, x);
if (ptr == NULL)
{
cout << "Node not found in the Heap" << endl;
return 1;
}
if (ptr->n < k)
{
cout << "Entered key greater than current key" << endl;
return 0;
}
ptr->n = k;
y = ptr->parent;
if (y != NULL && ptr->n < y->n)
{
Cut(H1, ptr, y);
Cascase_cut(H1, y);
}
if (ptr->n < H->n)
H = ptr;
return 0;
}
int FibonacciHeap::Cut(node *H1, node *x, node *y)
{
if (x == x->right)
y->child = NULL;
(x->left)->right = x->right;
(x->right)->left = x->left;
if (x == y->child)
y->child = x->right;
y->degree = y->degree - 1;
x->right = x;
x->left = x;
(H1->left)->right = x;
x->right = H1;
x->left = H1->left;
H1->left = x;
x->parent = NULL;
x->mark = 'F';
}
int FibonacciHeap::Cascase_cut(node *H1, node *y)
{
node *z = y->parent;
if (z != NULL)
{
if (y->mark == 'F')
{
y->mark = 'T';
}
else
{
Cut(H1, y, z);
Cascase_cut(H1, z);
}
}
}
node *FibonacciHeap::Find(node *H, int k)
{
node *x = H;
x->C = 'Y';
node *p = NULL;
if (x->n == k)
{
p = x;
x->C = 'N';
return p;
}
if (p == NULL)
{
if (x->child != NULL)
p = Find(x->child, k);
if ((x->right)->C != 'Y')
p = Find(x->right, k);
}
x->C = 'N';
return p;
}
int FibonacciHeap::Delete_key(node *H1, int k)
{
node *np = NULL;
int t;
t = Decrease_key(H1, k, -5000);
if (!t)
np = Extract_Min(H);
if (np != NULL)
cout << "Key Deleted" << endl;
else
cout << "Key not Deleted" << endl;
return 0;
}
int main()
{
int n, m, l;
FibonacciHeap fh;
node *p;
node *H;
H = fh.InitializeHeap();
p = fh.Create_node(7);
H = fh.Insert(H, p);
p = fh.Create_node(17);
H = fh.Insert(H, p);
p = fh.Create_node(26);
H = fh.Insert(H, p);
p = fh.Create_node(1);
H = fh.Insert(H, p);
fh.Display(H);
p = fh.Extract_Min(H);
if (p != NULL)
cout << "The node with minimum key: " << p->n << endl;
else
cout << "Heap is empty" << endl;
m = 26;
l = 16;
fh.Decrease_key(H, m, l);
m = 16;
fh.Delete_key(H, m);
}
Complexities
| Decrease Key | O(1) |
| Delete Node | O(log n) |
Priority Queue
A priority queue is a special type of queue in which each element is associated with a priority value. And, elements are served on the basis of their priority. That is, higher priority elements are served first.
However, if elements with the same priority occur, they are served according to their order in the queue.
Assigning Priority Value
Generally, the value of the element itself is considered for assigning the priority. For example,
The element with the highest value is considered the highest priority element. However, in other cases, we can assume the element with the lowest value as the highest priority element.
We can also set priorities according to our needs.
Difference between Priority Queue and Normal Queue
In a queue, the first-in-first-out rule is implemented whereas, in a priority queue, the values are removed on the basis of priority. The element with the highest priority is removed first.
Implementation of Priority Queue
Priority queue can be implemented using an array, a linked list, a heap data structure, or a binary search tree. Among these data structures, heap data structure provides an efficient implementation of priority queues.
Hence, we will be using the heap data structure to implement the priority queue in this tutorial. A max-heap is implemented in the following operations. If you want to learn more about it, please visit max-heap and min-heap.
A comparative analysis of different implementations of priority queue is given below.
| Operations | peek | insert | delete |
|---|---|---|---|
| Linked List | O(1) |
O(n) |
O(1) |
| Binary Heap | O(1) |
O(log n) |
O(log n) |
| Binary Search Tree | O(1) |
O(log n) |
O(log n) |
Priority Queue Operations
Basic operations of a priority queue are inserting, removing, and peeking elements.
Before studying the priority queue, please refer to the heap data structure for a better understanding of binary heap as it is used to implement the priority queue in this article.
1. Inserting an Element into the Priority Queue
Inserting an element into a priority queue (max-heap) is done by the following steps.
- Insert the new element at the end of the tree.
Insert an element at the end of the queue - Heapify the tree.
Heapify after insertion
Algorithm for insertion of an element into priority queue (max-heap)
If there is no node, create a newNode. else (a node is already present) insert the newNode at the end (last node from left to right.) heapify the array
For Min Heap, the above algorithm is modified so that parentNode is always smaller than newNode.
2. Deleting an Element from the Priority Queue
Deleting an element from a priority queue (max-heap) is done as follows:
- Select the element to be deleted.
Select the element to be deleted - Swap it with the last element.
Swap with the last leaf node element - Remove the last element.
Remove the last element leaf - Heapify the tree.
Heapify the priority queue
Algorithm for deletion of an element in the priority queue (max-heap)
If nodeToBeDeleted is the leafNode remove the node Else swap nodeToBeDeleted with the lastLeafNode remove noteToBeDeleted heapify the array
For Min Heap, the above algorithm is modified so that the both childNodes are smaller than currentNode.
3. Peeking from the Priority Queue (Find max/min)
Peek operation returns the maximum element from Max Heap or minimum element from Min Heap without deleting the node.
For both Max heap and Min Heap
return rootNode
4. Extract-Max/Min from the Priority Queue
Extract-Max returns the node with maximum value after removing it from a Max Heap whereas Extract-Min returns the node with minimum value after removing it from Min Heap.
Priority Queue Implementations in Python, Java, C, and C++
# Priority Queue implementation in Python
# Function to heapify the tree
def heapify(arr, n, i):
# Find the largest among root, left child, and right child
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
# Swap and continue heapifying if root is not the largest
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
# Function to insert an element into the tree
def insert(array, newNum):
size = len(array)
if size == 0:
array.append(newNum)
else:
array.append(newNum)
for i in range((size // 2) - 1, -1, -1):
heapify(array, size, i)
# Function to delete an element from the tree
def deleteNode(array, num):
size = len(array)
i = 0
for i in range(0, size):
if num == array[i]:
break
# Swap the element to delete with the last element
array[i], array[size - 1] = array[size - 1], array[i]
# Remove the last element (the one we want to delete)
array.pop()
# Rebuild the heap
for i in range((len(array) // 2) - 1, -1, -1):
heapify(array, len(array), i)
arr = []
insert(arr, 3)
insert(arr, 4)
insert(arr, 9)
insert(arr, 5)
insert(arr, 2)
print("Max-Heap array: " + str(arr))
deleteNode(arr, 4)
print("After deleting an element: " + str(arr))
// Priority Queue implementation in Java
import java.util.ArrayList;
class Heap {
// Function to heapify the tree
void heapify(ArrayList<Integer> hT, int i) {
int size = hT.size();
// Find the largest among root, left child and right child
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && hT.get(l) > hT.get(largest))
largest = l;
if (r < size && hT.get(r) > hT.get(largest))
largest = r;
// Swap and continue heapifying if root is not largest
if (largest != i) {
int temp = hT.get(largest);
hT.set(largest, hT.get(i));
hT.set(i, temp);
heapify(hT, largest);
}
}
// Function to insert an element into the tree
void insert(ArrayList<Integer> hT, int newNum) {
int size = hT.size();
if (size == 0) {
hT.add(newNum);
} else {
hT.add(newNum);
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(hT, i);
}
}
}
// Function to delete an element from the tree
void deleteNode(ArrayList<Integer> hT, int num) {
int size = hT.size();
int i;
for (i = 0; i < size; i++) {
if (num == hT.get(i))
break;
}
int temp = hT.get(i);
hT.set(i, hT.get(size - 1));
hT.set(size - 1, temp);
hT.remove(size - 1);
for (int j = size / 2 - 1; j >= 0; j--) {
heapify(hT, j);
}
}
// Print the tree
void printArray(ArrayList<Integer> array, int size) {
for (Integer i : array) {
System.out.print(i + " ");
}
System.out.println();
}
// Driver code
public static void main(String args[]) {
ArrayList<Integer> array = new ArrayList<Integer>();
int size = array.size();
Heap h = new Heap();
h.insert(array, 3);
h.insert(array, 4);
h.insert(array, 9);
h.insert(array, 5);
h.insert(array, 2);
System.out.println("Max-Heap array: ");
h.printArray(array, size);
h.deleteNode(array, 4);
System.out.println("After deleting an element: ");
h.printArray(array, size);
}
}
// Priority Queue implementation in C
#include <stdio.h>
int size = 0;
void swap(int *a, int *b) {
int temp = *b;
*b = *a;
*a = temp;
}
// Function to heapify the tree
void heapify(int array[], int size, int i) {
if (size == 1) {
printf("Single element in the heap");
} else {
// Find the largest among root, left child and right child
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && array[l] > array[largest])
largest = l;
if (r < size && array[r] > array[largest])
largest = r;
// Swap and continue heapifying if root is not largest
if (largest != i) {
swap(&array[i], &array[largest]);
heapify(array, size, largest);
}
}
}
// Function to insert an element into the tree
void insert(int array[], int newNum) {
if (size == 0) {
array[0] = newNum;
size += 1;
} else {
array[size] = newNum;
size += 1;
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(array, size, i);
}
}
}
// Function to delete an element from the tree
void deleteRoot(int array[], int num) {
int i;
for (i = 0; i < size; i++) {
if (num == array[i])
break;
}
swap(&array[i], &array[size - 1]);
size -= 1;
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(array, size, i);
}
}
// Print the array
void printArray(int array[], int size) {
for (int i = 0; i < size; ++i)
printf("%d ", array[i]);
printf("\n");
}
// Driver code
int main() {
int array[10];
insert(array, 3);
insert(array, 4);
insert(array, 9);
insert(array, 5);
insert(array, 2);
printf("Max-Heap array: ");
printArray(array, size);
deleteRoot(array, 4);
printf("After deleting an element: ");
printArray(array, size);
}
// Priority Queue implementation in C++
#include <iostream>
#include <vector>
using namespace std;
// Function to swap position of two elements
void swap(int *a, int *b) {
int temp = *b;
*b = *a;
*a = temp;
}
// Function to heapify the tree
void heapify(vector<int> &hT, int i) {
int size = hT.size();
// Find the largest among root, left child and right child
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && hT[l] > hT[largest])
largest = l;
if (r < size && hT[r] > hT[largest])
largest = r;
// Swap and continue heapifying if root is not largest
if (largest != i) {
swap(&hT[i], &hT[largest]);
heapify(hT, largest);
}
}
// Function to insert an element into the tree
void insert(vector<int> &hT, int newNum) {
int size = hT.size();
if (size == 0) {
hT.push_back(newNum);
} else {
hT.push_back(newNum);
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(hT, i);
}
}
}
// Function to delete an element from the tree
void deleteNode(vector<int> &hT, int num) {
int size = hT.size();
int i;
for (i = 0; i < size; i++) {
if (num == hT[i])
break;
}
swap(&hT[i], &hT[size - 1]);
hT.pop_back();
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(hT, i);
}
}
// Print the tree
void printArray(vector<int> &hT) {
for (int i = 0; i < hT.size(); ++i)
cout << hT[i] << " ";
cout << "\n";
}
// Driver code
int main() {
vector<int> heapTree;
insert(heapTree, 3);
insert(heapTree, 4);
insert(heapTree, 9);
insert(heapTree, 5);
insert(heapTree, 2);
cout << "Max-Heap array: ";
printArray(heapTree);
deleteNode(heapTree, 4);
cout << "After deleting an element: ";
printArray(heapTree);
}
Priority Queue Applications
Some of the applications of a priority queue are:
- Dijkstra's algorithm
- for implementing stack
- for load balancing and interrupt handling in an operating system
- for data compression in Huffman code
Heap Data Structure
Heap data structure is a complete binary tree that satisfies the heap property, where any given node is
- always greater than its child node/s and the key of the root node is the largest among all other nodes. This property is also called max heap property.
- always smaller than the child node/s and the key of the root node is the smallest among all other nodes. This property is also called min heap property.
This type of data structure is also called a binary heap.
Heap Operations
Some of the important operations performed on a heap are described below along with their algorithms.
Heapify
Heapify is the process of creating a heap data structure from a binary tree. It is used to create a Min-Heap or a Max-Heap.
- Let the input array be
Initial Array - Create a complete binary tree from the array
Complete binary tree - Start from the first index of non-leaf node whose index is given by
n/2 - 1.
Start from the first on leaf node - Set current element
iaslargest. - The index of left child is given by
2i + 1and the right child is given by2i + 2.
IfleftChildis greater thancurrentElement(i.e. element atithindex), setleftChildIndexas largest.
IfrightChildis greater than element inlargest, setrightChildIndexaslargest. - Swap
largestwithcurrentElement
Swap if necessary - Repeat steps 3-7 until the subtrees are also heapified.
Algorithm
Heapify(array, size, i)
set i as largest
leftChild = 2i + 1
rightChild = 2i + 2
if leftChild > array[largest]
set leftChildIndex as largest
if rightChild > array[largest]
set rightChildIndex as largest
swap array[i] and array[largest]
To create a Max-Heap:
MaxHeap(array, size)
loop from the first index of non-leaf node down to zero
call heapify
For Min-Heap, both leftChild and rightChild must be larger than the parent for all nodes.
Insert Element into Heap
Algorithm for insertion in Max Heap
If there is no node,
create a newNode.
else (a node is already present)
insert the newNode at the end (last node from left to right.)
heapify the array
- Insert the new element at the end of the tree.
Insert at the end - Heapify the tree.
For Min Heap, the above algorithm is modified so that parentNode is always smaller than newNode.
Delete Element from Heap
Algorithm for deletion in Max Heap
If nodeToBeDeleted is the leafNode
remove the node
Else swap nodeToBeDeleted with the lastLeafNode
remove noteToBeDeleted
heapify the array
- Select the element to be deleted.
Select the element to be deleted - Swap it with the last element.
Swap with the last element - Remove the last element.
Remove the last element - Heapify the tree.
For Min Heap, above algorithm is modified so that both childNodes are greater smaller than currentNode.
Peek (Find max/min)
Peek operation returns the maximum element from Max Heap or minimum element from Min Heap without deleting the node.
For both Max heap and Min Heap
return rootNode
Extract-Max/Min
Extract-Max returns the node with maximum value after removing it from a Max Heap whereas Extract-Min returns the node with minimum after removing it from Min Heap.
Python, Java, C/C++ Examples
# Max-Heap data structure in Python
def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[l] > arr[largest]:
largest = l
if r < n and arr[r] > arr[largest]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def insert(array, newNum):
array.append(newNum)
current = len(array) - 1
while current > 0:
parent = (current - 1) // 2
if array[current] > array[parent]:
array[current], array[parent] = array[parent], array[current]
current = parent
else:
break
def deleteNode(array, num):
size = len(array)
i = 0
for i in range(size):
if array[i] == num:
break
# Swap with the last element
array[i], array[-1] = array[-1], array[i]
array.pop() # Remove the last element which is now the number to be deleted
# Only run heapify if the deleted node was not the last node
if i < len(array):
heapify(array, len(array), i)
arr = []
insert(arr, 3)
insert(arr, 4)
insert(arr, 9)
insert(arr, 5)
insert(arr, 2)
print("Max-Heap array:", arr)
deleteNode(arr, 4)
print("After deleting an element:", arr)
// Max-Heap data structure in Java
import java.util.ArrayList;
class Heap {
void heapify(ArrayList<Integer> hT, int i) {
int size = hT.size();
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && hT.get(l) > hT.get(largest))
largest = l;
if (r < size && hT.get(r) > hT.get(largest))
largest = r;
if (largest != i) {
int temp = hT.get(largest);
hT.set(largest, hT.get(i));
hT.set(i, temp);
heapify(hT, largest);
}
}
void insert(ArrayList<Integer> hT, int newNum) {
int size = hT.size();
if (size == 0) {
hT.add(newNum);
} else {
hT.add(newNum);
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(hT, i);
}
}
}
void deleteNode(ArrayList<Integer> hT, int num)
{
int size = hT.size();
int i;
for (i = 0; i < size; i++)
{
if (num == hT.get(i))
break;
}
int temp = hT.get(i);
hT.set(i, hT.get(size-1));
hT.set(size-1, temp);
hT.remove(size-1);
for (int j = size / 2 - 1; j >= 0; j--)
{
heapify(hT, j);
}
}
void printArray(ArrayList<Integer> array, int size) {
for (Integer i : array) {
System.out.print(i + " ");
}
System.out.println();
}
public static void main(String args[]) {
ArrayList<Integer> array = new ArrayList<Integer>();
int size = array.size();
Heap h = new Heap();
h.insert(array, 3);
h.insert(array, 4);
h.insert(array, 9);
h.insert(array, 5);
h.insert(array, 2);
System.out.println("Max-Heap array: ");
h.printArray(array, size);
h.deleteNode(array, 4);
System.out.println("After deleting an element: ");
h.printArray(array, size);
}
}
// Max-Heap data structure in C
#include <stdio.h>
int size = 0;
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void heapify(int array[], int size, int i) {
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && array[l] > array[largest])
largest = l;
if (r < size && array[r] > array[largest])
largest = r;
if (largest != i) {
swap(&array[i], &array[largest]);
heapify(array, size, largest);
}
}
void insert(int array[], int newNum) {
array[size] = newNum;
size += 1;
int current = size - 1;
while (current != 0) {
int parent = (current - 1) / 2;
if (array[current] > array[parent]) {
swap(&array[current], &array[parent]);
current = parent;
} else {
break;
}
}
}
void deleteRoot(int array[], int num) {
int i;
for (i = 0; i < size; i++) {
if (array[i] == num) break;
}
swap(&array[i], &array[size - 1]);
// Reduce the size of the heap since the last element is now removed
size -= 1;
// Heapify from the current index to adjust the rest of the heap
if (i < size) {
heapify(array, size, i);
}
}
void printArray(int array[], int size) {
for (int i = 0; i < size; ++i)
printf("%d ", array[i]);
printf("\n");
}
int main() {
int array[10];
insert(array, 3);
insert(array, 4);
insert(array, 9);
insert(array, 5);
insert(array, 2);
printf("Max-Heap array: ");
printArray(array, size);
deleteRoot(array, 4);
printf("After deleting an element: ");
printArray(array, size);
return 0;
}
// Max-Heap data structure in C++
#include <iostream>
#include <vector>
using namespace std;
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void heapify(vector<int> &hT, int i) {
int size = hT.size();
int largest = i;
int l = 2 * i + 1;
int r = 2 * i + 2;
if (l < size && hT[l] > hT[largest])
largest = l;
if (r < size && hT[r] > hT[largest])
largest = r;
if (largest != i) {
swap(&hT[i], &hT[largest]);
heapify(hT, largest);
}
}
void insert(vector<int> &hT, int newNum) {
hT.push_back(newNum);
int current = hT.size() - 1;
// Bubble up
while (current > 0) {
int parent = (current - 1) / 2;
if (hT[current] > hT[parent]) {
swap(&hT[current], &hT[parent]);
current = parent;
} else {
break;
}
}
}
void deleteNode(vector<int> &hT, int num) {
int size = hT.size();
int i;
for (i = 0; i < size; i++) {
if (num == hT[i])
break;
}
swap(&hT[i], &hT[size - 1]);
hT.pop_back();
// Update size after popping
size = hT.size();
// Heapify from the current index to adjust the rest of the heap
if (i < size) {
heapify(hT, i);
}
}
void printArray(const vector<int> &hT) {
for (int num : hT)
cout << num << " ";
cout << "\n";
}
int main() {
vector<int> heapTree;
insert(heapTree, 3);
insert(heapTree, 4);
insert(heapTree, 9);
insert(heapTree, 5);
insert(heapTree, 2);
cout << "Max-Heap array: ";
printArray(heapTree);
deleteNode(heapTree, 4);
cout << "After deleting an element: ";
printArray(heapTree);
return 0;
}
Heap Data Structure Applications
- Heap is used while implementing a priority queue.
- Dijkstra's Algorithm
- Heap Sort
Hash Table
The Hash table data structure stores elements in key-value pairs where
- Key- unique integer that is used for indexing the values
- Value - data that are associated with keys.
Hashing (Hash Function)
In a hash table, a new index is processed using the keys. And, the element corresponding to that key is stored in the index. This process is called hashing.
Let k be a key and h(x) be a hash function.
Here, h(k) will give us a new index to store the element linked with k.
To learn more, visit Hashing.
Hash Collision
When the hash function generates the same index for multiple keys, there will be a conflict (what value to be stored in that index). This is called a hash collision.
We can resolve the hash collision using one of the following techniques.
- Collision resolution by chaining
- Open Addressing: Linear/Quadratic Probing and Double Hashing
1. Collision resolution by chaining
In chaining, if a hash function produces the same index for multiple elements, these elements are stored in the same index by using a doubly-linked list.
If j is the slot for multiple elements, it contains a pointer to the head of the list of elements. If no element is present, j contains NIL.
Pseudocode for operations
chainedHashSearch(T, k)
return T[h(k)]
chainedHashInsert(T, x)
T[h(x.key)] = x //insert at the head
chainedHashDelete(T, x)
T[h(x.key)] = NIL
2. Open Addressing
Unlike chaining, open addressing doesn't store multiple elements into the same slot. Here, each slot is either filled with a single key or left NIL.
Different techniques used in open addressing are:
i. Linear Probing
In linear probing, collision is resolved by checking the next slot.
h(k, i) = (h′(k) + i) mod m
where
i = {0, 1, ….}h'(k)is a new hash function
If a collision occurs at h(k, 0), then h(k, 1) is checked. In this way, the value of i is incremented linearly.
The problem with linear probing is that a cluster of adjacent slots is filled. When inserting a new element, the entire cluster must be traversed. This adds to the time required to perform operations on the hash table.
ii. Quadratic Probing
It works similar to linear probing but the spacing between the slots is increased (greater than one) by using the following relation.
h(k, i) = (h′(k) + c1i + c2i2) mod m
where,
c1andc2are positive auxiliary constants,i = {0, 1, ….}
iii. Double hashing
If a collision occurs after applying a hash function h(k), then another hash function is calculated for finding the next slot.
h(k, i) = (h1(k) + ih2(k)) mod m
Good Hash Functions
A good hash function may not prevent the collisions completely however it can reduce the number of collisions.
Here, we will look into different methods to find a good hash function
1. Division Method
If k is a key and m is the size of the hash table, the hash function h() is calculated as:
h(k) = k mod m
For example, If the size of a hash table is 10 and k = 112 then h(k) = 112 mod 10 = 2. The value of m must not be the powers of 2. This is because the powers of 2 in binary format are 10, 100, 1000, …. When we find k mod m, we will always get the lower order p-bits.
if m = 22, k = 17, then h(k) = 17 mod 22 = 10001 mod 100 = 01 if m = 23, k = 17, then h(k) = 17 mod 22 = 10001 mod 100 = 001 if m = 24, k = 17, then h(k) = 17 mod 22 = 10001 mod 100 = 0001 if m = 2p, then h(k) = p lower bits of m
2. Multiplication Method
h(k) = ⌊m(kA mod 1)⌋
where,
kA mod 1gives the fractional partkA,⌊ ⌋gives the floor valueAis any constant. The value ofAlies between 0 and 1. But, an optimal choice will be≈ (√5-1)/2suggested by Knuth.
3. Universal Hashing
In Universal hashing, the hash function is chosen at random independent of keys.
Python, Java and C/C++ Examples
# Python program to demonstrate working of HashTable
# Initialize the hash table with 10 empty lists (each index is a list to handle collisions)
hashTable = [[] for _ in range(10)]
def checkPrime(n):
if n == 1 or n == 0:
return 0
for i in range(2, n // 2):
if n % i == 0:
return 0
return 1
def getPrime(n):
if n % 2 == 0:
n = n + 1
while not checkPrime(n):
n += 2
return n
def hashFunction(key):
capacity = getPrime(10)
return key % capacity
def insertData(key, data):
index = hashFunction(key)
# Check if the key already exists in the list to update it, otherwise append
found = False
for i, kv in enumerate(hashTable[index]):
if kv[0] == key:
hashTable[index][i] = (key, data) # Update existing key-value pair
found = True
break
if not found:
hashTable[index].append((key, data)) # Add new key-value pair if not found
def removeData(key):
index = hashFunction(key)
# Remove the key-value pair from the list if it exists
for i, kv in enumerate(hashTable[index]):
if kv[0] == key:
del hashTable[index][i]
break
# Test the hash table
insertData(123, "apple")
insertData(432, "mango")
insertData(213, "banana")
insertData(654, "guava")
insertData(213, "orange") # This should update the value for key 213
print(hashTable)
removeData(123)
print(hashTable)
// Java program to demonstrate working of HashTable
import java.util.*;
class HashTable {
public static void main(String args[])
{
Hashtable<Integer, Integer>
ht = new Hashtable<Integer, Integer>();
ht.put(123, 432);
ht.put(12, 2345);
ht.put(15, 5643);
ht.put(3, 321);
ht.remove(12);
System.out.println(ht);
}
}
// Implementing hash table in C
#include <stdio.h>
#include <stdlib.h>
struct set
{
int key;
int data;
};
struct set *array;
int capacity = 10;
int size = 0;
int hashFunction(int key)
{
return (key % capacity);
}
int checkPrime(int n)
{
int i;
if (n == 1 || n == 0)
{
return 0;
}
for (i = 2; i < n / 2; i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
int getPrime(int n)
{
if (n % 2 == 0)
{
n++;
}
while (!checkPrime(n))
{
n += 2;
}
return n;
}
void init_array()
{
capacity = getPrime(capacity);
array = (struct set *)malloc(capacity * sizeof(struct set));
for (int i = 0; i < capacity; i++)
{
array[i].key = 0;
array[i].data = 0;
}
}
void insert(int key, int data)
{
int index = hashFunction(key);
if (array[index].data == 0)
{
array[index].key = key;
array[index].data = data;
size++;
printf("\n Key (%d) has been inserted \n", key);
}
else if (array[index].key == key)
{
array[index].data = data;
}
else
{
printf("\n Collision occured \n");
}
}
void remove_element(int key)
{
int index = hashFunction(key);
if (array[index].data == 0)
{
printf("\n This key does not exist \n");
}
else
{
array[index].key = 0;
array[index].data = 0;
size--;
printf("\n Key (%d) has been removed \n", key);
}
}
void display()
{
int i;
for (i = 0; i < capacity; i++)
{
if (array[i].data == 0)
{
printf("\n array[%d]: / ", i);
}
else
{
printf("\n key: %d array[%d]: %d \t", array[i].key, i, array[i].data);
}
}
}
int size_of_hashtable()
{
return size;
}
int main()
{
int choice, key, data, n;
int c = 0;
init_array();
do
{
printf("1.Insert item in the Hash Table"
"\n2.Remove item from the Hash Table"
"\n3.Check the size of Hash Table"
"\n4.Display a Hash Table"
"\n\n Please enter your choice: ");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("Enter key -:\t");
scanf("%d", &key);
printf("Enter data -:\t");
scanf("%d", &data);
insert(key, data);
break;
case 2:
printf("Enter the key to delete-:");
scanf("%d", &key);
remove_element(key);
break;
case 3:
n = size_of_hashtable();
printf("Size of Hash Table is-:%d\n", n);
break;
case 4:
display();
break;
default:
printf("Invalid Input\n");
}
printf("\nDo you want to continue (press 1 for yes): ");
scanf("%d", &c);
} while (c == 1);
}
// Implementing hash table in C++
#include <iostream>
#include <list>
using namespace std;
class HashTable
{
int capacity;
list<int> *table;
public:
HashTable(int V);
void insertItem(int key, int data);
void deleteItem(int key);
int checkPrime(int n)
{
int i;
if (n == 1 || n == 0)
{
return 0;
}
for (i = 2; i < n / 2; i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
int getPrime(int n)
{
if (n % 2 == 0)
{
n++;
}
while (!checkPrime(n))
{
n += 2;
}
return n;
}
int hashFunction(int key)
{
return (key % capacity);
}
void displayHash();
};
HashTable::HashTable(int c)
{
int size = getPrime(c);
this->capacity = size;
table = new list<int>[capacity];
}
void HashTable::insertItem(int key, int data)
{
int index = hashFunction(key);
table[index].push_back(data);
}
void HashTable::deleteItem(int key)
{
int index = hashFunction(key);
list<int>::iterator i;
for (i = table[index].begin();
i != table[index].end(); i++)
{
if (*i == key)
break;
}
if (i != table[index].end())
table[index].erase(i);
}
void HashTable::displayHash()
{
for (int i = 0; i < capacity; i++)
{
cout << "table[" << i << "]";
for (auto x : table[i])
cout << " --> " << x;
cout << endl;
}
}
int main()
{
int key[] = {231, 321, 212, 321, 433, 262};
int data[] = {123, 432, 523, 43, 423, 111};
int size = sizeof(key) / sizeof(key[0]);
HashTable h(size);
for (int i = 0; i < size; i++)
h.insertItem(key[i], data[i]);
h.deleteItem(12);
h.displayHash();
}
Applications of Hash Table
Hash tables are implemented where
- constant time lookup and insertion is required
- cryptographic applications
- indexing data is required
Bellman Ford's Algorithm
It is similar to Dijkstra's algorithm but it can work with graphs in which edges can have negative weights.
Why would one ever have edges with negative weights in real life?
Negative weight edges might seem useless at first but they can explain a lot of phenomena like cashflow, the heat released/absorbed in a chemical reaction, etc.
For instance, if there are different ways to reach from one chemical A to another chemical B, each method will have sub-reactions involving both heat dissipation and absorption.
If we want to find the set of reactions where minimum energy is required, then we will need to be able to factor in the heat absorption as negative weights and heat dissipation as positive weights.
Why do we need to be careful with negative weights?
Negative weight edges can create negative weight cycles i.e. a cycle that will reduce the total path distance by coming back to the same point.
Shortest path algorithms like Dijkstra's Algorithm that aren't able to detect such a cycle can give an incorrect result because they can go through a negative weight cycle and reduce the path length.
How Bellman Ford's algorithm works
Bellman Ford algorithm works by overestimating the length of the path from the starting vertex to all other vertices. Then it iteratively relaxes those estimates by finding new paths that are shorter than the previously overestimated paths.
By doing this repeatedly for all vertices, we can guarantee that the result is optimized.
Note: To relax the path, an edge(U, V),
if distance(U) + edge_weight(U,V) < distance(V),
assign distance(V) = distance(U) + edge_weight(U,V).
Bellman Ford Pseudocode
We need to maintain the path distance of every vertex. We can store that in an array of size v, where v is the number of vertices.
We also want to be able to get the shortest path, not only know the length of the shortest path. For this, we map each vertex to the vertex that last updated its path length.
Once the algorithm is over, we can backtrack from the destination vertex to the source vertex to find the path.
function bellmanFord(G, S)
for each vertex V in G
distance[V] <- infinite
previous[V] <- NULL
distance[S] <- 0
for each vertex V in G
for each edge (U,V) in G
tempDistance <- distance[U] + edge_weight(U, V)
if tempDistance < distance[V]
distance[V] <- tempDistance
previous[V] <- U
for each edge (U,V) in G
if distance[U] + edge_weight(U, V) < distance[V]
Error: Negative Cycle Exists
return distance[], previous[]
Bellman Ford vs Dijkstra
Bellman Ford's algorithm and Dijkstra's algorithm are very similar in structure. While Dijkstra looks only to the immediate neighbors of a vertex, Bellman goes through each edge in every iteration.
Python, Java and C/C++ Examples
# Bellman Ford Algorithm in Python
class Graph:
def __init__(self, vertices):
self.V = vertices # Total number of vertices in the graph
self.graph = [] # Array of edges
# Add edges
def add_edge(self, s, d, w):
self.graph.append([s, d, w])
# Print the solution
def print_solution(self, dist):
print("Vertex Distance from Source")
for i in range(self.V - 1):
print("{0}\t\t{1}".format(i, dist[i]))
def bellman_ford(self, src):
# Step 1: fill the distance array and predecessor array
dist = [float("Inf")] * self.V
# Mark the source vertex
dist[src] = 0
# Step 2: relax edges |V| - 1 times
for _ in range(self.V - 1):
for s, d, w in self.graph:
if dist[s] != float("Inf") and dist[s] + w < dist[d]:
dist[d] = dist[s] + w
# Step 3: detect negative cycle
# if value changes then we have a negative cycle in the graph
# and we cannot find the shortest distances
for s, d, w in self.graph:
if dist[s] != float("Inf") and dist[s] + w < dist[d]:
print("Graph contains negative weight cycle")
return
# No negative weight cycle found!
# Print the distance and predecessor array
self.print_solution(dist)
g = Graph(5)
g.add_edge(0, 1, 5)
g.add_edge(0, 2, 4)
g.add_edge(1, 3, 3)
g.add_edge(2, 1, 6)
g.add_edge(3, 2, 2)
g.bellman_ford(0)
// Bellman Ford Algorithm in Java
class CreateGraph {
// CreateGraph - it consists of edges
class CreateEdge {
int s, d, w;
CreateEdge() {
s = d = w = 0;
}
};
int V, E;
CreateEdge edge[];
// Creates a graph with V vertices and E edges
CreateGraph(int v, int e) {
V = v;
E = e;
edge = new CreateEdge[e];
for (int i = 0; i < e; ++i)
edge[i] = new CreateEdge();
}
void BellmanFord(CreateGraph graph, int s) {
int V = graph.V, E = graph.E;
int dist[] = new int[V];
// Step 1: fill the distance array and predecessor array
for (int i = 0; i < V; ++i)
dist[i] = Integer.MAX_VALUE;
// Mark the source vertex
dist[s] = 0;
// Step 2: relax edges |V| - 1 times
for (int i = 1; i < V; ++i) {
for (int j = 0; j < E; ++j) {
// Get the edge data
int u = graph.edge[j].s;
int v = graph.edge[j].d;
int w = graph.edge[j].w;
if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v])
dist[v] = dist[u] + w;
}
}
// Step 3: detect negative cycle
// if value changes then we have a negative cycle in the graph
// and we cannot find the shortest distances
for (int j = 0; j < E; ++j) {
int u = graph.edge[j].s;
int v = graph.edge[j].d;
int w = graph.edge[j].w;
if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) {
System.out.println("CreateGraph contains negative w cycle");
return;
}
}
// No negative w cycle found!
// Print the distance and predecessor array
printSolution(dist, V);
}
// Print the solution
void printSolution(int dist[], int V) {
System.out.println("Vertex Distance from Source");
for (int i = 0; i < V - 1; ++i)
System.out.println(i + "\t\t" + dist[i]);
}
public static void main(String[] args) {
int V = 5; // Total vertices
int E = 5; // Total Edges
CreateGraph graph = new CreateGraph(V, E);
// edge 0 --> 1
graph.edge[0].s = 0;
graph.edge[0].d = 1;
graph.edge[0].w = 5;
// edge 0 --> 2
graph.edge[1].s = 0;
graph.edge[1].d = 2;
graph.edge[1].w = 4;
// edge 1 --> 3
graph.edge[2].s = 1;
graph.edge[2].d = 3;
graph.edge[2].w = 3;
// edge 2 --> 1
graph.edge[3].s = 2;
graph.edge[3].d = 1;
graph.edge[3].w = 6;
// edge 3 --> 2
graph.edge[4].s = 3;
graph.edge[4].d = 2;
graph.edge[4].w = 2;
graph.BellmanFord(graph, 0); // 0 is the source vertex
}
}
// Bellman Ford Algorithm in C
#include <stdio.h>
#include <stdlib.h>
#define INFINITY 99999
//struct for the edges of the graph
struct Edge {
int u; //start vertex of the edge
int v; //end vertex of the edge
int w; //weight of the edge (u,v)
};
//Graph - it consists of edges
struct Graph {
int V; //total number of vertices in the graph
int E; //total number of edges in the graph
struct Edge *edge; //array of edges
};
void bellmanford(struct Graph *g, int source);
void display(int arr[], int size);
int main(void) {
//create graph
struct Graph *g = (struct Graph *)malloc(sizeof(struct Graph));
g->V = 4; //total vertices
g->E = 5; //total edges
//array of edges for graph
g->edge = (struct Edge *)malloc(g->E * sizeof(struct Edge));
//------- adding the edges of the graph
/*
edge(u, v)
where u = start vertex of the edge (u,v)
v = end vertex of the edge (u,v)
w is the weight of the edge (u,v)
*/
//edge 0 --> 1
g->edge[0].u = 0;
g->edge[0].v = 1;
g->edge[0].w = 5;
//edge 0 --> 2
g->edge[1].u = 0;
g->edge[1].v = 2;
g->edge[1].w = 4;
//edge 1 --> 3
g->edge[2].u = 1;
g->edge[2].v = 3;
g->edge[2].w = 3;
//edge 2 --> 1
g->edge[3].u = 2;
g->edge[3].v = 1;
g->edge[3].w = 6;
//edge 3 --> 2
g->edge[4].u = 3;
g->edge[4].v = 2;
g->edge[4].w = 2;
bellmanford(g, 0); //0 is the source vertex
return 0;
}
void bellmanford(struct Graph *g, int source) {
//variables
int i, j, u, v, w;
//total vertex in the graph g
int tV = g->V;
//total edge in the graph g
int tE = g->E;
//distance array
//size equal to the number of vertices of the graph g
int d[tV];
//predecessor array
//size equal to the number of vertices of the graph g
int p[tV];
//step 1: fill the distance array and predecessor array
for (i = 0; i < tV; i++) {
d[i] = INFINITY;
p[i] = 0;
}
//mark the source vertex
d[source] = 0;
//step 2: relax edges |V| - 1 times
for (i = 1; i <= tV - 1; i++) {
for (j = 0; j < tE; j++) {
//get the edge data
u = g->edge[j].u;
v = g->edge[j].v;
w = g->edge[j].w;
if (d[u] != INFINITY && d[v] > d[u] + w) {
d[v] = d[u] + w;
p[v] = u;
}
}
}
//step 3: detect negative cycle
//if value changes then we have a negative cycle in the graph
//and we cannot find the shortest distances
for (i = 0; i < tE; i++) {
u = g->edge[i].u;
v = g->edge[i].v;
w = g->edge[i].w;
if (d[u] != INFINITY && d[v] > d[u] + w) {
printf("Negative weight cycle detected!\n");
return;
}
}
//No negative weight cycle found!
//print the distance and predecessor array
printf("Distance array: ");
display(d, tV);
printf("Predecessor array: ");
display(p, tV);
}
void display(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Bellman Ford Algorithm in C++
#include <bits/stdc++.h>
// Struct for the edges of the graph
struct Edge {
int u; //start vertex of the edge
int v; //end vertex of the edge
int w; //w of the edge (u,v)
};
// Graph - it consists of edges
struct Graph {
int V; // Total number of vertices in the graph
int E; // Total number of edges in the graph
struct Edge* edge; // Array of edges
};
// Creates a graph with V vertices and E edges
struct Graph* createGraph(int V, int E) {
struct Graph* graph = new Graph;
graph->V = V; // Total Vertices
graph->E = E; // Total edges
// Array of edges for graph
graph->edge = new Edge[E];
return graph;
}
// Printing the solution
void printArr(int arr[], int size) {
int i;
for (i = 0; i < size - 1; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void BellmanFord(struct Graph* graph, int u) {
int V = graph->V;
int E = graph->E;
int dist[V];
// Step 1: fill the distance array and predecessor array
for (int i = 0; i < V; i++)
dist[i] = INT_MAX;
// Mark the source vertex
dist[u] = 0;
// Step 2: relax edges |V| - 1 times
for (int i = 1; i <= V - 1; i++) {
for (int j = 0; j < E; j++) {
// Get the edge data
int u = graph->edge[j].u;
int v = graph->edge[j].v;
int w = graph->edge[j].w;
if (dist[u] != INT_MAX && dist[u] + w < dist[v])
dist[v] = dist[u] + w;
}
}
// Step 3: detect negative cycle
// if value changes then we have a negative cycle in the graph
// and we cannot find the shortest distances
for (int i = 0; i < E; i++) {
int u = graph->edge[i].u;
int v = graph->edge[i].v;
int w = graph->edge[i].w;
if (dist[u] != INT_MAX && dist[u] + w < dist[v]) {
printf("Graph contains negative w cycle");
return;
}
}
// No negative weight cycle found!
// Print the distance and predecessor array
printArr(dist, V);
return;
}
int main() {
// Create a graph
int V = 5; // Total vertices
int E = 5; // Total edges
// Array of edges for graph
struct Graph* graph = createGraph(V, E);
//------- adding the edges of the graph
/*
edge(u, v)
where u = start vertex of the edge (u,v)
v = end vertex of the edge (u,v)
w is the weight of the edge (u,v)
*/
//edge 0 --> 1
graph->edge[0].u = 0;
graph->edge[0].v = 1;
graph->edge[0].w = 5;
//edge 0 --> 2
graph->edge[1].u = 0;
graph->edge[1].v = 2;
graph->edge[1].w = 4;
//edge 1 --> 3
graph->edge[2].u = 1;
graph->edge[2].v = 3;
graph->edge[2].w = 3;
//edge 2 --> 1
graph->edge[3].u = 2;
graph->edge[3].v = 1;
graph->edge[3].w = 6;
//edge 3 --> 2
graph->edge[4].u = 3;
graph->edge[4].v = 2;
graph->edge[4].w = 2;
BellmanFord(graph, 0); //0 is the source vertex
return 0;
}
Bellman Ford's Complexity
Time Complexity
| Best Case Complexity | O(E) |
| Average Case Complexity | O(VE) |
| Worst Case Complexity | O(VE) |
Space Complexity
And, the space complexity is O(V).
Bellman Ford's Algorithm Applications
- For calculating shortest paths in routing algorithms
- For finding the shortest path
Dijkstra's Algorithm
It differs from the minimum spanning tree because the shortest distance between two vertices might not include all the vertices of the graph.
How Dijkstra's Algorithm works
Dijkstra's Algorithm works on the basis that any subpath B -> D of the shortest path A -> D between vertices A and D is also the shortest path between vertices B and D.
Djikstra used this property in the opposite direction i.e we overestimate the distance of each vertex from the starting vertex. Then we visit each node and its neighbors to find the shortest subpath to those neighbors.
The algorithm uses a greedy approach in the sense that we find the next best solution hoping that the end result is the best solution for the whole problem.
Example of Dijkstra's algorithm
It is easier to start with an example and then think about the algorithm.
Djikstra's algorithm pseudocode
We need to maintain the path distance of every vertex. We can store that in an array of size v, where v is the number of vertices.
We also want to be able to get the shortest path, not only know the length of the shortest path. For this, we map each vertex to the vertex that last updated its path length.
Once the algorithm is over, we can backtrack from the destination vertex to the source vertex to find the path.
A minimum priority queue can be used to efficiently receive the vertex with least path distance.
function dijkstra(G, S)
for each vertex V in G
distance[V] <- infinite
previous[V] <- NULL
If V != S, add V to Priority Queue Q
distance[S] <- 0
while Q IS NOT EMPTY
U <- Extract MIN from Q
for each unvisited neighbour V of U
tempDistance <- distance[U] + edge_weight(U, V)
if tempDistance < distance[V]
distance[V] <- tempDistance
previous[V] <- U
return distance[], previous[]
Code for Dijkstra's Algorithm
The implementation of Dijkstra's Algorithm in Python, Java, C and C++ is given below. The complexity of the code can be improved, but the abstractions are convenient to relate the code with the algorithm.
# Dijkstra's Algorithm in Python
import sys
# Providing the graph
vertices = [[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 0, 0],
[1, 0, 1, 0, 0, 0, 1],
[0, 0, 1, 0, 0, 1, 0],
[0, 1, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 0, 1, 0]]
edges = [[0, 0, 1, 2, 0, 0, 0],
[0, 0, 2, 0, 0, 3, 0],
[1, 2, 0, 1, 3, 0, 0],
[2, 0, 1, 0, 0, 0, 1],
[0, 0, 3, 0, 0, 2, 0],
[0, 3, 0, 0, 2, 0, 1],
[0, 0, 0, 1, 0, 1, 0]]
# Find which vertex is to be visited next
def to_be_visited():
global visited_and_distance
v = -10
for index in range(num_of_vertices):
if visited_and_distance[index][0] == 0 \
and (v < 0 or visited_and_distance[index][1] <=
visited_and_distance[v][1]):
v = index
return v
num_of_vertices = len(vertices[0])
visited_and_distance = [[0, 0]]
for i in range(num_of_vertices-1):
visited_and_distance.append([0, sys.maxsize])
for vertex in range(num_of_vertices):
# Find next vertex to be visited
to_visit = to_be_visited()
for neighbor_index in range(num_of_vertices):
# Updating new distances
if vertices[to_visit][neighbor_index] == 1 and \
visited_and_distance[neighbor_index][0] == 0:
new_distance = visited_and_distance[to_visit][1] \
+ edges[to_visit][neighbor_index]
if visited_and_distance[neighbor_index][1] > new_distance:
visited_and_distance[neighbor_index][1] = new_distance
visited_and_distance[to_visit][0] = 1
i = 0
# Printing the distance
for distance in visited_and_distance:
print("Distance of ", chr(ord('a') + i),
" from source vertex: ", distance[1])
i = i + 1
// Dijkstra's Algorithm in Java
public class Dijkstra {
public static void dijkstra(int[][] graph, int source) {
int count = graph.length;
boolean[] visitedVertex = new boolean[count];
int[] distance = new int[count];
for (int i = 0; i < count; i++) {
visitedVertex[i] = false;
distance[i] = Integer.MAX_VALUE;
}
// Distance of self loop is zero
distance[source] = 0;
for (int i = 0; i < count; i++) {
// Update the distance between neighbouring vertex and source vertex
int u = findMinDistance(distance, visitedVertex);
visitedVertex[u] = true;
// Update all the neighbouring vertex distances
for (int v = 0; v < count; v++) {
if (!visitedVertex[v] && graph[u][v] != 0 && (distance[u] + graph[u][v] < distance[v])) {
distance[v] = distance[u] + graph[u][v];
}
}
}
for (int i = 0; i < distance.length; i++) {
System.out.println(String.format("Distance from %s to %s is %s", source, i, distance[i]));
}
}
// Finding the minimum distance
private static int findMinDistance(int[] distance, boolean[] visitedVertex) {
int minDistance = Integer.MAX_VALUE;
int minDistanceVertex = -1;
for (int i = 0; i < distance.length; i++) {
if (!visitedVertex[i] && distance[i] < minDistance) {
minDistance = distance[i];
minDistanceVertex = i;
}
}
return minDistanceVertex;
}
public static void main(String[] args) {
int graph[][] = new int[][] { { 0, 0, 1, 2, 0, 0, 0 }, { 0, 0, 2, 0, 0, 3, 0 }, { 1, 2, 0, 1, 3, 0, 0 },
{ 2, 0, 1, 0, 0, 0, 1 }, { 0, 0, 3, 0, 0, 2, 0 }, { 0, 3, 0, 0, 2, 0, 1 }, { 0, 0, 0, 1, 0, 1, 0 } };
Dijkstra T = new Dijkstra();
T.dijkstra(graph, 0);
}
}
// Dijkstra's Algorithm in C
#include <stdio.h>
#define INFINITY 9999
#define MAX 10
void Dijkstra(int Graph[MAX][MAX], int n, int start);
void Dijkstra(int Graph[MAX][MAX], int n, int start) {
int cost[MAX][MAX], distance[MAX], pred[MAX];
int visited[MAX], count, mindistance, nextnode, i, j;
// Creating cost matrix
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
if (Graph[i][j] == 0)
cost[i][j] = INFINITY;
else
cost[i][j] = Graph[i][j];
for (i = 0; i < n; i++) {
distance[i] = cost[start][i];
pred[i] = start;
visited[i] = 0;
}
distance[start] = 0;
visited[start] = 1;
count = 1;
while (count < n - 1) {
mindistance = INFINITY;
for (i = 0; i < n; i++)
if (distance[i] < mindistance && !visited[i]) {
mindistance = distance[i];
nextnode = i;
}
visited[nextnode] = 1;
for (i = 0; i < n; i++)
if (!visited[i])
if (mindistance + cost[nextnode][i] < distance[i]) {
distance[i] = mindistance + cost[nextnode][i];
pred[i] = nextnode;
}
count++;
}
// Printing the distance
for (i = 0; i < n; i++)
if (i != start) {
printf("\nDistance from source to %d: %d", i, distance[i]);
}
}
int main() {
int Graph[MAX][MAX], i, j, n, u;
n = 7;
Graph[0][0] = 0;
Graph[0][1] = 0;
Graph[0][2] = 1;
Graph[0][3] = 2;
Graph[0][4] = 0;
Graph[0][5] = 0;
Graph[0][6] = 0;
Graph[1][0] = 0;
Graph[1][1] = 0;
Graph[1][2] = 2;
Graph[1][3] = 0;
Graph[1][4] = 0;
Graph[1][5] = 3;
Graph[1][6] = 0;
Graph[2][0] = 1;
Graph[2][1] = 2;
Graph[2][2] = 0;
Graph[2][3] = 1;
Graph[2][4] = 3;
Graph[2][5] = 0;
Graph[2][6] = 0;
Graph[3][0] = 2;
Graph[3][1] = 0;
Graph[3][2] = 1;
Graph[3][3] = 0;
Graph[3][4] = 0;
Graph[3][5] = 0;
Graph[3][6] = 1;
Graph[4][0] = 0;
Graph[4][1] = 0;
Graph[4][2] = 3;
Graph[4][3] = 0;
Graph[4][4] = 0;
Graph[4][5] = 2;
Graph[4][6] = 0;
Graph[5][0] = 0;
Graph[5][1] = 3;
Graph[5][2] = 0;
Graph[5][3] = 0;
Graph[5][4] = 2;
Graph[5][5] = 0;
Graph[5][6] = 1;
Graph[6][0] = 0;
Graph[6][1] = 0;
Graph[6][2] = 0;
Graph[6][3] = 1;
Graph[6][4] = 0;
Graph[6][5] = 1;
Graph[6][6] = 0;
u = 0;
Dijkstra(Graph, n, u);
return 0;
}
// Dijkstra's Algorithm in C++
#include <iostream>
#include <vector>
#define INT_MAX 10000000
using namespace std;
void DijkstrasTest();
int main() {
DijkstrasTest();
return 0;
}
class Node;
class Edge;
void Dijkstras();
vector<Node*>* AdjacentRemainingNodes(Node* node);
Node* ExtractSmallest(vector<Node*>& nodes);
int Distance(Node* node1, Node* node2);
bool Contains(vector<Node*>& nodes, Node* node);
void PrintShortestRouteTo(Node* destination);
vector<Node*> nodes;
vector<Edge*> edges;
class Node {
public:
Node(char id)
: id(id), previous(NULL), distanceFromStart(INT_MAX) {
nodes.push_back(this);
}
public:
char id;
Node* previous;
int distanceFromStart;
};
class Edge {
public:
Edge(Node* node1, Node* node2, int distance)
: node1(node1), node2(node2), distance(distance) {
edges.push_back(this);
}
bool Connects(Node* node1, Node* node2) {
return (
(node1 == this->node1 &&
node2 == this->node2) ||
(node1 == this->node2 &&
node2 == this->node1));
}
public:
Node* node1;
Node* node2;
int distance;
};
///////////////////
void DijkstrasTest() {
Node* a = new Node('a');
Node* b = new Node('b');
Node* c = new Node('c');
Node* d = new Node('d');
Node* e = new Node('e');
Node* f = new Node('f');
Node* g = new Node('g');
Edge* e1 = new Edge(a, c, 1);
Edge* e2 = new Edge(a, d, 2);
Edge* e3 = new Edge(b, c, 2);
Edge* e4 = new Edge(c, d, 1);
Edge* e5 = new Edge(b, f, 3);
Edge* e6 = new Edge(c, e, 3);
Edge* e7 = new Edge(e, f, 2);
Edge* e8 = new Edge(d, g, 1);
Edge* e9 = new Edge(g, f, 1);
a->distanceFromStart = 0; // set start node
Dijkstras();
PrintShortestRouteTo(f);
}
///////////////////
void Dijkstras() {
while (nodes.size() > 0) {
Node* smallest = ExtractSmallest(nodes);
vector<Node*>* adjacentNodes =
AdjacentRemainingNodes(smallest);
const int size = adjacentNodes->size();
for (int i = 0; i < size; ++i) {
Node* adjacent = adjacentNodes->at(i);
int distance = Distance(smallest, adjacent) +
smallest->distanceFromStart;
if (distance < adjacent->distanceFromStart) {
adjacent->distanceFromStart = distance;
adjacent->previous = smallest;
}
}
delete adjacentNodes;
}
}
// Find the node with the smallest distance,
// remove it, and return it.
Node* ExtractSmallest(vector<Node*>& nodes) {
int size = nodes.size();
if (size == 0) return NULL;
int smallestPosition = 0;
Node* smallest = nodes.at(0);
for (int i = 1; i < size; ++i) {
Node* current = nodes.at(i);
if (current->distanceFromStart <
smallest->distanceFromStart) {
smallest = current;
smallestPosition = i;
}
}
nodes.erase(nodes.begin() + smallestPosition);
return smallest;
}
// Return all nodes adjacent to 'node' which are still
// in the 'nodes' collection.
vector<Node*>* AdjacentRemainingNodes(Node* node) {
vector<Node*>* adjacentNodes = new vector<Node*>();
const int size = edges.size();
for (int i = 0; i < size; ++i) {
Edge* edge = edges.at(i);
Node* adjacent = NULL;
if (edge->node1 == node) {
adjacent = edge->node2;
} else if (edge->node2 == node) {
adjacent = edge->node1;
}
if (adjacent && Contains(nodes, adjacent)) {
adjacentNodes->push_back(adjacent);
}
}
return adjacentNodes;
}
// Return distance between two connected nodes
int Distance(Node* node1, Node* node2) {
const int size = edges.size();
for (int i = 0; i < size; ++i) {
Edge* edge = edges.at(i);
if (edge->Connects(node1, node2)) {
return edge->distance;
}
}
return -1; // should never happen
}
// Does the 'nodes' vector contain 'node'
bool Contains(vector<Node*>& nodes, Node* node) {
const int size = nodes.size();
for (int i = 0; i < size; ++i) {
if (node == nodes.at(i)) {
return true;
}
}
return false;
}
///////////////////
void PrintShortestRouteTo(Node* destination) {
Node* previous = destination;
cout << "Distance from start: "
<< destination->distanceFromStart << endl;
while (previous) {
cout << previous->id << " ";
previous = previous->previous;
}
cout << endl;
}
// these two not needed
vector<Edge*>* AdjacentEdges(vector<Edge*>& Edges, Node* node);
void RemoveEdge(vector<Edge*>& Edges, Edge* edge);
vector<Edge*>* AdjacentEdges(vector<Edge*>& edges, Node* node) {
vector<Edge*>* adjacentEdges = new vector<Edge*>();
const int size = edges.size();
for (int i = 0; i < size; ++i) {
Edge* edge = edges.at(i);
if (edge->node1 == node) {
cout << "adjacent: " << edge->node2->id << endl;
adjacentEdges->push_back(edge);
} else if (edge->node2 == node) {
cout << "adjacent: " << edge->node1->id << endl;
adjacentEdges->push_back(edge);
}
}
return adjacentEdges;
}
void RemoveEdge(vector<Edge*>& edges, Edge* edge) {
vector<Edge*>::iterator it;
for (it = edges.begin(); it < edges.end(); ++it) {
if (*it == edge) {
edges.erase(it);
return;
}
}
}
Dijkstra's Algorithm Complexity
Time Complexity: O(E Log V)
where, E is the number of edges and V is the number of vertices.
Space Complexity: O(V)
Dijkstra's Algorithm Applications
- To find the shortest path
- In social networking applications
- In a telephone network
- To find the locations in the map