0% found this document useful (0 votes)
8 views

Unit-2

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

Unit-2

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

UNIT-II

Heap Data Structure


A Heap is a complete binary tree data structure that satisfies the heap property: for
every node, the value of its children is greater than or equal to its own value. Heaps are
usually used to implement priority queues, where the smallest (or largest) element is always
at the root of the tree.

What is Heap Data Structure?


A heap is a binary tree-based data structure that follows the heap property. In a heap, the
value of each node is compared to the values of its children in a specific way:
 Max-Heap: The value of each node is greater than or equal to the values of its children,
ensuring that the root node contains the maximum value. As you move down the tree, the
values decrease.
 Min-Heap: The value of each node is less than or equal to the values of its children,
ensuring that the root node contains the minimum value. As you move down the tree, the
values increase.
This property guarantees that the largest (in a max-heap) or smallest (in a min-heap) value is
always at the root, and the tree maintains a specific order based on the type of heap.
Types of Heaps
There are two main types of heaps:
 Max Heap: The root node contains the maximum value, and the values decrease as you
move down the tree.
 Min Heap: The root node contains the minimum value, and the values increase as you
move down the tree.
Heap Operations
Common heap operations are:
 Insert: Adds a new element to the heap while maintaining the heap property.
 Extract Max/Min: Removes the maximum or minimum element from the heap and
returns it.
 Heapify: Converts an arbitrary binary tree into a heap.
Heap Data Structure Applications
Heaps have various applications, like:
 Heaps are commonly used to implement priority queues, where elements are retrieved
based on their priority (maximum or minimum value).
 Heapsort is a sorting algorithm that uses a heap to sort an array in ascending or
descending order.
 Heaps are used in graph algorithms like Dijkstra’s algorithm and Prim’s algorithm for
finding the shortest paths and minimum spanning trees.

// 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;
}

What is Graph Data Structure?


Graph is a non-linear data structure consisting of vertices and edges. The vertices are
sometimes also referred to as nodes and the edges are lines or arcs that connect any two
nodes in the graph. More formally a Graph is composed of a set of vertices ( V ) and a set
of edges( E ). The graph is denoted by G(V, E).
Imagine a game of football as a web of connections, where players are the nodes and their
interactions on the field are the edges. This web of connections is exactly what a graph data
structure represents, and it‟s the key to unlocking insights into team performance and player
dynamics in sports.
Components of Graph Data Structure
 Vertices: Vertices are the fundamental units of the graph. Sometimes, vertices are also
known as vertex or nodes. Every node/vertex can be labeled or unlabelled.
 Edges: Edges are drawn or used to connect two nodes of the graph. It can be ordered
pair of nodes in a directed graph. Edges can connect any two nodes in any possible way.
There are no rules. Sometimes, edges are also known as arcs. Every edge can be
labelled/unlabelled.
Representation of Graph Data Structure:

There are multiple ways to store a graph: The following are the most common
representations.
 Adjacency Matrix
 Adjacency List
Adjacency Matrix Representation of Graph Data Structure:
In this method, the graph is stored in the form of the 2D matrix where rows and
columns denote vertices. Each entry in the matrix represents the weight of the edge between
those vertices.

Adjacency List Representation of Graph:


This graph is represented as a collection of linked lists. There is an array of pointer
which points to the edges connected to that vertex.
Breadth First Search or BFS for a Graph
Breadth First Search (BFS) is a fundamental graph traversal algorithm. It begins
with a node, then first traverses all its adjacent. Once all adjacent are visited, then their
adjacent are traversed. This is different from DFS in a way that closest vertices are visited
before others. We mainly traverse vertices level by level. A lot of popular graph algorithms
like Dijkstra‟s shortest path, Kahn‟s Algorithm, and Prim‟s algorithm are based on BFS. BFS
itself can be used to detect cycle in a directed and undirected graph, find shortest path in an
unweghted graph and many more problems.
// C program for BFS of an undirected graph
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100
// Structure to represent a node in adjacency list
struct Node {
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int data) {
struct Node* newNode =
(struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to add an edge to the graph
void addEdge(struct Node* adj[], int u, int v) {
struct Node* newNode = createNode(v);
newNode->next = adj[u];
adj[u] = newNode;

// Undirected graph
newNode = createNode(u);
newNode->next = adj[v];
adj[v] = newNode;
}
// Function to perform Breadth First Search on a graph
// represented using adjacency list
void bfs(struct Node* adj[], int vertices,
int source, int visited[]) {
// Create a queue for BFS
int queue[MAX_VERTICES];
int front = 0, rear = 0;

// Mark the current node as visited and enqueue it


visited[source] = 1;
queue[rear++] = source;
// Iterate over the queue
while (front != rear) {
// Dequeue a vertex from queue and print it
int curr = queue[front++];
printf("%d ", curr);
// Get all adjacent vertices of the dequeued vertex
// curr If an adjacent has not been visited,
// then mark it visited and enqueue it
struct Node* temp = adj[curr];
while (temp != NULL) {
int neighbor = temp->data;
if (!visited[neighbor]) {
visited[neighbor] = 1;
queue[rear++] = neighbor;
}
temp = temp->next;
}
}
}
int main() {
// Number of vertices in the graph
int vertices = 5;

// Adjacency list representation of the graph


struct Node* adj[vertices];
for (int i = 0; i < vertices; ++i)
adj[i] = NULL;
// Add edges to the graph
addEdge(adj, 0, 1);
addEdge(adj, 0, 2);
addEdge(adj, 1, 3);
addEdge(adj, 1, 4);
addEdge(adj, 2, 4);

// Mark all the vertices as not visited


int visited[vertices];
for (int i = 0; i < vertices; ++i)
visited[i] = 0;
// Perform BFS traversal starting from vertex 0
printf("Breadth First Traversal "
"starting from vertex 0: ");
bfs(adj, vertices, 0, visited);
return 0;
}

Complexity Analysis of Breadth-First Search (BFS) Algorithm:


Time Complexity of BFS Algorithm: O(V + E)
 BFS explores all the vertices and edges in the graph. In the worst case, it visits every
vertex and edge once. Therefore, the time complexity of BFS is O(V + E), where V and
E are the number of vertices and edges in the given graph.
Applications of BFS in Graphs:
BFS has various applications in graph theory and computer science, including:
 Shortest Path Finding: BFS can be used to find the shortest path between two nodes in
an unweighted graph. By keeping track of the parent of each node during the traversal,
the shortest path can be reconstructed.
 Cycle Detection: BFS can be used to detect cycles in a graph. If a node is visited twice
during the traversal, it indicates the presence of a cycle.
 Connected Components: BFS can be used to identify connected components in a
graph. Each connected component is a set of nodes that can be reached from each other.
 Topological Sorting: BFS can be used to perform topological sorting on a directed
acyclic graph (DAG). Topological sorting arranges the nodes in a linear order such that
for any edge (u, v), u appears before v in the order.
 Level Order Traversal of Binary Trees: BFS can be used to perform a level order
traversal of a binary tree. This traversal visits all nodes at the same level before moving
to the next level.
 Network Routing: BFS can be used to find the shortest path between two nodes in a
network, making it useful for routing data packets in network protocols.

Depth First Search or DFS for a Graph


Depth First Traversal (or DFS) for a graph is similar to Depth First Traversal of a
tree. The only catch here is, that, unlike trees, graphs may contain cycles (a node may be
visited twice). To avoid processing a node more than once, use a boolean visited array. A
graph can have more than one DFS traversal.
#include <stdio.h>
#include <stdlib.h>

// Node structure for adjacency list


struct Node {
int dest;
struct Node* next;
};

// Structure to represent an adjacency list


struct AdjList {
struct Node* head;
};

// Function to create a new adjacency list node


struct Node* createNode(int dest) {
struct Node* newNode =
(struct Node*)malloc(sizeof(struct Node));
newNode->dest = dest;
newNode->next = NULL;
return newNode;
}
// Function to add an edge to the adjacency list
void addEdge(struct AdjList adj[], int s, int t) {
// Add edge from s to t
struct Node* newNode = createNode(t);
newNode->next = adj[s].head;
adj[s].head = newNode;
// Due to undirected Graph
newNode = createNode(s);
newNode->next = adj[t].head;
adj[t].head = newNode;
}

// Recursive function for DFS traversal


void DFSRec(struct AdjList adj[], int visited[], int s) {
// Mark the current vertex as visited
visited[s] = 1;

// Print the current vertex


printf("%d ", s);

// Traverse all adjacent vertices that are not visited yet


struct Node* current = adj[s].head;
while (current != NULL) {
int dest = current->dest;
if (!visited[dest]) {
DFSRec(adj, visited, dest);
}
current = current->next;
}
}

// Main DFS function that initializes the visited array


// and calls DFSRec
void DFS(struct AdjList adj[], int V, int s) {
// Initialize visited array to false
int visited[5] = {0};
DFSRec(adj, visited, s);
}

int main() {
int V = 5;

// Create an array of adjacency lists


struct AdjList adj[V];

// Initialize each adjacency list as empty


for (int i = 0; i < V; i++) {
adj[i].head = NULL;
}

int E = 5;
// Define the edges of the graph
int edges[][2] = {{1, 2}, {1, 0}, {2, 0}, {2, 3}, {2, 4}};

// Populate the adjacency list with edges


for (int i = 0; i < E; i++) {
addEdge(adj, edges[i][0], edges[i][1]);
}

int source = 1;
printf("DFS from source: %d\n", source);
DFS(adj, V, source);

return 0;
}

Connected component:
If G is connected undirected graph, then we can visit all the vertices of the graph in the first
call to BFS. The sub graph which we obtain after traversing the graph using BFS represents
the connected component of the graph.

Spanning tree of a graph: Consider the set of all edges (u, w) where all vertices w are
adjacent to u and are not visited. According to BFS algorithm it is established that this set of
edges give the spanning tree of G, if G is connected. We obtain depth first search spanning
tree similarly.
These are the BFS and DFS spanning trees of the graph G

Bi-connected components and DFS:


A connected undirected graph is said to be bi-connected if it remains connected after removal
of any one vertex and the edges that are incident upon that vertex.
In this we have two components.
i. Articulation point: Let G= (V, E) be a connected undirected graph. Then an
articulation point of graph „G‟ is a vertex whose articulation point of graph is a vertex
whose removal disconnects the graph „G‟. It is also known as “cut point”.
ii. Bi-connected graph: A graph „G‟ is said to be bi-connected if it contains no-
articulation point.

Applications of Graphs in Data Structures

In many fields, the quantitative discipline is crucial. Graphs are regarded as an excellent
modeling instrument that can be used to simulate various phases of relationships between all
physical circumstances. Graphs are a useful tool for illustrating a variety of real-world issues.
Some significant graph uses are listed below:
 Social Networks: Graphs are unique network configurations with just one kind of edge
separating each vertex.

 Web Graphs: There are many allusions to URLs on the internet. In other terms, the internet
is a great source of network data.

 Biological Networks: Biological networks or space are two important forms of graphs in the
actual world. Brain networks, protein signalling networks, and nutrition networks are a few
examples.

 Information Graphs: Geographical data is organized in a graph-based style, and information


A is connected to information B when A specifically represents B.

 Product Recommendations: A website like Amazon suggests acquiring comparable goods


when making a transaction. These suggested goods are dependent on what previous
customers have bought. For instance, Amazon suggests a book about Scrum if you purchase
one about Python. Large networks of bipartite are at the core of these systems.

 Neural Networks: Large diagrams that artificially link neurons with synapses create neural
networks. There are numerous varieties of neural networks, and the primary distinction
among them is how graphs are formed.

 Map Networks: All devices come pre-loaded with applications like Uber, Apple Maps,
Google Maps, and Maze. Models for navigation issues resemble those for graph issues.
Consider issues with moving merchants, issues with shortcuts, Hammington paths, etc.

 Blockchains: Each block‟s vertices can contain numerous deals, and the edges link the
blocks that follow. The present benchmark for historical transactions is the biggest branch
from the first block.

 Bitcoin Creation Graphs: Blockchain is a fascinating network that is frequently examined


in the bitcoin world. When Bitcoin accounts are treated as the vertices and transfers between
wallets as the edges, a new, insightful graph appears. The image that results displays the
transfer of funds between Bitcoin accounts. This graph is crucial for understanding trends of
worldwide cash movement.

Divide and Conquer Introduction


Divide and Conquer is an algorithmic pattern. In algorithmic methods, the design is to
take a dispute on a huge input, break the input into minor pieces, decide the problem on each
of the small pieces, and then merge the piecewise solutions into a global solution. This
mechanism of solving the problem is called the Divide & Conquer Strategy.

Divide and Conquer algorithm consists of a dispute using the following three steps.

1. Divide the original problem into a set of subproblems.


2. Conquer: Solve every subproblem individually, recursively.
3. Combine: Put together the solutions of the subproblems to get the solution to the
whole problem.
Quick Sort
Quicksort is a sorting algorithm based on the Divide and Conquer that picks an
element as a pivot and partitions the given array around the picked pivot by placing the pivot
in its correct position in the sorted array.
How does QuickSort Algorithm work
There are mainly three steps in the algorithm.
1. Choose a pivot
2. Partition the array around pivot. After partition, it is ensured that all elements are smaller
than all right and we get index of the end point of smaller elements. The left and right may
not be sorted individually.
3. Recursively call for the two partitioned left and right subarrays.
4. We stop recursion when there is only one element is left.

#include <stdio.h>
#include <stdlib.h>

// Function to swap two elements


void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}

int partition(int arr[], int low, int high) {

// Choose the pivot


int pivot = arr[high];

// Index of smaller element and indicates


// the right position of pivot found so far
int i = low - 1;

// Traverse arr[low..high] and move all smaller


// elements on the left side. Elements from low to
// i are smaller after every iteration
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}

// Move pivot after smaller elements and


// return its position
swap(&arr[i + 1], &arr[high]);
return i + 1;
}
// The QuickSort function implementation
void quickSort(int arr[], int low, int high) {

if (low < high) {

// pi is the partition return index of pivot


int pi = partition(arr, low, high);

// Recursion calls for smaller elements


// and greater or equals elements
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}

// Function to print an array


void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}

int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);

quickSort(arr, 0, n - 1);

printf("Sorted Array\n");
printArray(arr, n);
return 0;
}
Merge sort :
Merge sort is a sorting algorithm that follows the divide-and-conquer approach. It
works by recursively dividing the input array into smaller sub arrays and sorting those sub
arrays then merging them back together to obtain the sorted array.
In simple terms, we can say that the process of merge sort is to divide the array into two
halves, sort each half, and then merge the sorted halves back together. This process is
repeated until the entire array is sorted.
How does Merge Sort work?
Merge sort is a popular sorting algorithm known for its efficiency and stability. It follows
the divide-and-conquer approach to sort a given array of elements.
Here‟s a step-by-step explanation of how merge sort works:

1. Divide: Divide the list or array recursively into two halves until it can no more be
divided.
2. Conquer: Each subarray is sorted individually using the merge sort algorithm.
3. Merge: The sorted subarrays are merged back together in sorted order. The process
continues until all elements from both subarrays have been merged.
#include <stdio.h>
#include <stdlib.h>
// Merges two subarrays of arr[].
// First subarray is arr[l..m]
// Second subarray is arr[m+1..r]
void merge(int arr[], int l, int m, int r)
{
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;

// Create temp arrays


int L[n1], R[n2];

// Copy data to temp arrays L[] and R[]


for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];

// Merge the temp arrays back into arr[l..r


i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy the remaining elements of L[],
// if there are any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// Copy the remaining elements of R[],
// if there are any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}

// l is for left index and r is right index of the


// sub-array of arr to be sorted
void mergeSort(int arr[], int l, int r)
{
if (l < r) {
int m = l + (r - l) / 2;

// Sort first and second halves


mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);

merge(arr, l, m, r);
}
}

// Function to print an array


void printArray(int A[], int size)
{
int i;
for (i = 0; i < size; i++)
printf("%d ", A[i]);
printf("\n");
}

// Driver code
int main()
{
int arr[] = { 12, 11, 13, 5, 6, 7 };
int arr_size = sizeof(arr) / sizeof(arr[0]);

printf("Given array is \n");


printArray(arr, arr_size);

mergeSort(arr, 0, arr_size - 1);

printf("\nSorted array is \n");


printArray(arr, arr_size);
return 0;
}
Strassen’s Matrix Multiplication

Given two square matrices A and B of size n x n each, find their multiplication matrix.

Naive Method: Following is a simple way to multiply two matrices.

void multiply(int A[][N], int B[][N], int C[][N])


{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
C[i][j] = 0;
for (int k = 0; k < N; k++)
{
C[i][j] += A[i][k]*B[k][j];
}
}
}
}

Convex Hull Algorithm;


The Convex Hull Algorithm is used to find the convex hull of a set of points in
computational geometry. The convex hull is the smallest convex set that encloses all the
points, forming a convex polygon. This algorithm is important in various applications such as
image processing, route planning, and object modeling.
What is Convex Hull?
The convex hull of a set of points in a Euclidean space is the smallest convex polygon
that encloses all of the points. In two dimensions (2D), the convex hull is a convex polygon,
and in three dimensions (3D), it is a convex polyhedron.
The below image shows a 2-D convex polygon:

Convex Hull using Divide and Conquer Algorithm:


Pre-requisite: Tangents between two convex polygons
Algorithm: Given the set of points for which we have to find the convex hull. Suppose we
know the convex hull of the left half points and the right half points, then the problem now
is to merge these two convex hulls and determine the convex hull for the complete set.
 This can be done by finding the upper and lower tangent to the right and left convex
hulls. This is illustrated here Tangents between two convex polygons Let the left
convex hull be a and the right convex hull be b.
 Then the lower and upper tangents are named as 1 and 2 respectively, as shown in the
figure. Then the red outline shows the final convex hull.

 Now the problem remains, how to find the convex hull for the left and right half.
Now recursion comes into the picture, we divide the set of points until the number of
points in the set is very small, say 5, and we can find the convex hull for these points by
the brute algorithm.
 The merging of these halves would result in the convex hull for the complete set of
points. Note: We have used the brute algorithm to find the convex hull for a small
number of points and it has a time complexity of O(N^3).
 But some people suggest the following; the convex hull for 3 or fewer points is the
complete set of points. This is correct but the problem comes when we try to merge a
left convex hull of 2 points and right convex hull of 3 points, then the program gets
trapped in an infinite loop in some special cases.
 So, to get rid of this problem I directly found the convex hull for 5 or fewer points
by O(N^3) algorithm, which is somewhat greater but does not affect the overall
complexity of the algorithm.

You might also like