Data Structures Class Notes
Data Structures Class Notes
1.Array
Array is a kind of data structure that can store a fixed-size sequential
collection of elements of the same type. An array is used to store a collection
of data, but it is often more useful to think of an array as a collection of
variables of the same type.
Assume a[n]
Print “enter no of elements”
Read n
for(i=0;i<n;i++)
{
Print “ enter the element”
Read a[i]
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
return 0;
}
Output:
Enter number of elements: 5
Memory successfully allocated using malloc.
The elements of the array are: 1, 2, 3, 4, 5,
To know more:-
https://www.geeksforgeeks.org/dynamic-memory-allocation-in-c-using-mall
oc-calloc-free-and-realloc/
1 2 3 4
5 6 7 8
9 10 11 12
#include <stdio.h>
#include <stdlib.h>
int main() {
int r = 3, c = 4;
int * arr = (int * ) malloc(r * c * sizeof(int));
int i, j, count = 0;
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
*(arr + i * c + j) = ++count;
#include <stdio.h>
#include <stdlib.h>
int main()
{
int r = 3, c = 4, i, j, count;
int *arr[r];
for (i=0; i<r; i++)
arr[i] = (int *)malloc(c * sizeof(int));
return 0;
}
Output:
1 2 3 4 5 6 7 8 9 10 11 12
filter_none
edit
play_arrow
Brightness_4
#include <stdio.h>
#include <stdlib.h>
int main()
{
int r = 3, c = 4, i, j, count;
return 0;
}
Output:
1 2 3 4 5 6 7 8 9 10 11 12
filter_none
edit
play_arrow
Brightness_4
#include<stdio.h>
#include<stdlib.h>
int main()
{
int r=3, c=4, len=0;
int *ptr, **arr;
int count = 0,i,j;
return 0;
}
Output:
1 2 3 4 5 6 7 8 9 10 11 12
2. Linked list
while (choice!=0)
{
printf("Enter data?(1/0)=");
scanf("%d",&choice);
newnode=(struct node*)malloc(sizeof(struct node));
if(head==0)
{
head=temp=newnode;
}
else
{
temp->next=newnode;
temp=newnode;
}
}
//printing(traversal)
temp=head;
while(temp->next!=0)
{
printf("\ndata=%d",temp->data);
temp=temp->next;
}
return 0;
}
struct node
{
int data.
struct node *ptr.
}*start == NULL;
struct node
{
int data.
struct node *ptr.
}*start = NULL.
read pos.
if (pos == 1)
{
t = start.
start = start->ptr.
free (t).
}
else
{
t = t1 = start.
{
t = (struct node*) malloc( sizeof (struct node) ) .
read t -> data.
if (start == NULL)
{
start = t.
}
else
{
last -> ptr = t.
}
t -> ptr = start.
last = start.
}
struct node
{
int data.
struct node *ptr.
}*start = NULL.
struct node
{
int data.
struct node *ptr.
}*start = NULL.
1. assume *start, *last.
2. declare struct node *t, *t1.
3. if (start != NULL)
4. {
5. read pos.
6. if (pos == 1)
7. {
8. t = start.
9. start = start -> ptr.
10. free (t).
11. last -> ptr = start.
12. }
13. else
14. {
15. t = start.
16. for (i=1; i < pos; i++)
17. {
18. t1 = t.
19. t = t -> ptr.
20. }
21. t1 -> ptr = t -> ptr.
22. free (t).
23. }
struct node
{
int data;
struct node *pre, *next;
}*start = NULL.
declare struct node *t, *last.
read n.
for (i = 1 to n)
{
t = (struct node*) malloc( sizeof (struct node) )
read t -> data.
t -> next = NULL.
if ( start == NULL)
{
start = t.
t -> pre = NULL.
}
else
{
last -> next = t.
t -> pre = last.
}
last = t. }
3. Stack
A Stack is an abstract data structure which is used to store data in a particular order.
It is either implemented with the help of an array or a linked list. Two operations that
can be performed on a Stack are: Push operation which inserts an element into the
stack. Pop operation which removes the last element that was added into the stack.
It follows the Last In First Out(LIFO) Order.
int pop()
{
if(top==-1)
print(overflow)
else
return stack[top--]
}
int pop()
{
if(top!=NULL)
{
t=top.
top=top->ptr
ele =t->data
free(t)
return ele
}
else
print underflow
}
char pop()
{ if(top==-1)
return '#'
else
return stack[top--] }
ch1=pop();
if(ch1=='#')
Break;
}
}
if(ch=='#' || top!=-1)
print("incorrect sequence")
Else
print("Correct sequence")
{
if(ele=='+'|| ele=='-')
return 2.
else if(ele=='/'||ele=='*')
return 3.
Else
return 1. }
[ [
( [(
3 [( 3
+ [(+ 3
5 [(+ 35
* [(+ * 35
2 [( + * 352
) [( 352*+
+ [+ 352*+
8 [+ 352*+8
/ [ +/ 352*+8
2 [+/ 352*+82
] 352*+82/+
2 2
2, 3 2, 3
2, 3, * 2, 15 3*5=15
2, 3, *, + 17 2+15=17
2, 3, *, +,/ 17, 1
2, 3, *, +, / , - 16 17-1=16
} //end of for
ch = pop()
while (ch != '#') {
q[ind++] = ch.
ch = pop()
}
) #)
f #) f
- #)- f
e #)- fe
( # fe-
+ #+ fe-
d #+ fe-d
* #+* fe-d
c #+* fe-dc
- #+- fe-dc*
b #+- fe–dc*b
+ #+-+ fe–dc*b
A #+-+ fe–dc*ba
fe–dc*ba+-+
Reverse the expression +, -, +, a, b, *, c, d, –, e, f
3 3
2 3, 2
8 3, 2, 8
/ 3, 4 8/2=4
* 12 4*3=12
3 12, 3
2 12, 3, 2
+ 12, 5 3+2=5
- -7 5-12=-7
TOH(n, A, B, C) // ( S, M, D)
{
if(n==1)
{
Print("move %c disk from %c to %c", n, A, C)
Return
}
TOH( n-1, A, C, B) // (S, D, M)
Print( "move %c disk from %c to %c", n, A, C)
TOH(n-1, B, A, C) //( M, S, D)
}
4.Queue
insertionQA(int ele)
{
if(rear==size-1)
{
print(Overflow)
}
else if(rear==-1)
{
rear=front=0
queue[rear]=ele
}
else
{
rear=rear+1;
queue[rear]=ele
}
}//end of insertionQA
int deletionQA()
{
if(front==-1)
{
print(overflow)
}
else if(front==rear)
{
ele=queue[front]
front=rear=-1
return ele
}
else
{
ele=queue[front]
front=front+1
return ele
}}
printQA()
{
if(front==-1)
{
print(underflow)
}
else
{
for(i=front;i<=rear;i++)
Print(queue[i])
}
}
cq[rear]=ele
}
else
{
rear=(rear+1)%size
cq[rear]=ele
}
}
DeletionQA()
{
if(front==-1)
print(overflow)
else if (front==rear)
front=rear=-1
else
front=(front+1)%size
}
print()
{
for(i=front;front!=-1;i=(i+1)%size)
{
print(cq[i])
if(i==rear)
break;
}
}
print()
{
if(front<=rear)
{
for(i=front;i<=rear;i++)
print(cq[i])
}
else
{
for(i=front;i<=size-1;i++)
print(cq[i])
for(i=0;i<=rear;i++)
print(cq[i])
}
}
insertionQLL(int ele)
{
struct node *t
t=malloc()
if(t!=NULL)
{
t->data=ele
t->ptr=NULL
if(rear==NULL)
front=rear=t
else
{
rear->ptr=t
rear=t
}
}
deletionQLL()
{
if(front==NULL)
print(underflow)
else if(front==rear)
{
free (front)
front=rear=NULL
}
else
{
t=front
front=front->ptr
free(t)
}
}
printQLL
{
for(t=front;t!=NULL;t=t->ptr)
print(t->data)
}
if(front==NULL)
{
front=t;
t->ptr=NULL;
}
else
{
if(t->pri < front->pri)
{
t->ptr=front
front=t
}
else
{
for(t2=front; t->pri >= t2->pri && t2!=NULL; )
{
t1=t2
t2=t2->ptr
}
t->ptr=t1->ptr
t1->ptr=t;
}//end of inner else
}// end of outer else
}// end of if
}
deletionPQA()
{
for(i=0;i<rsize;i++)
{
if(fr[i][0]!=-1)
{
if(fr[i][0]==fr[i][1])
{
print(mq[priority][fr[priority][0]])
fr[priority][0]=fr[priority][1]=-1
break;
}
else
{
print(mq[i][fr[i][0]])
fr[i][0]=(fr[i][0]+1)%csize
break;
}
}
if(i==rsize)
print(“underflow”);
}
5.Trees
{
t=malloc()
t->data=data;
main()
{
root=binarytreecreation();
preorder(root);
}
Print(ptr->data)
If(ptr->rc!=NULL)
push(ptr->rc)
else if(ptr->lc!=NULL)
ptr=ptr->lc;
else
ptr=pop()
}
}
5.1.3 Inorder:
//using recursion
inorder(struct node * t)
{
if(t!=NULL)
{
inorder(t->lc);
Print:- t->data;
inorder(t->rc);
}
}
// using stack
ptr=pop()
}
5.1.4 Postorder
postorder(struct node * t) //using recursion
{
if(t!=NULL)
{
postorder(t->lc);
postorder(t->rc);
Print:- t->data;
}
}
1.
return root1 }
main()
{
struct node *root =NULL
read ele
root=insertion(root,ele)
}
free root1
return temp
}
If(root1->rc==NULL)
struct node *temp=root1->lc
free root1
return temp
}
struct node *temp=minvalue(root1->rc)
root1->data=temp->data
root1->rc=deletion(root1->rc,temp->data)
} //end of else
return root1
}
struct node *minvalue(struct node *t)
{
while(t->lc!=NULL)
{
t=t->lc }
return root1
}
if(t==NULL)
return 0;
else
{
int lh, rh;
lh=height(t->lc);
rh=height(t->rc);
if(lh>rh)
return (lh+1);
else
return (rh+1);
}
}
if(t->lc==NULL)
lh=0
else
lh=t->lc->ht
if(t->rc==NULL)
rh=0
else
rh=t->rc->ht
return (lh-rh)
}
t = malloc()
t - > data = ele
t - > lc = t - > rc = NULL
}
else if (ele > t - > data)
{
t - > rc = insertion(t - > rc, ele)
if (balfact(t) == -2)
{
if(ele > t - > rc - > data)
t = RRrotation(t)
else
t = RLrotation(t)
}
}
else if (ele < t - > data)
{
t - > lc = insertion(t - > lc, ele)
if (balfact(t) == 2)
{
if(ele < t - > lc - > data)
t = LLrotation(t)
else
t = LRrotation(t)
}
t - > ht = height(t)
return t
}
}
LeftRotate(*a)
{
struct node *b=a->rc
a->rc=b->lc
b->lc=a
a->ht=height(a)
b->ht=height(b)
return b
}
RightRotate(*a)
{
struct node *b=a->lc
a->lc=b->rc
b->rc=a
a->ht=height(a)
b->ht=height(b)
return b
}
if (t - > rc != NULL)
{
* temp = t - > rc;
while (temp - > lc = NULL)
temp = temp - > lc;
t - > data = temp - > data;
t - > rc = deletion(t - > rc, temp - > data);
if (balfact(t) == 2)
{
if (balfact(t - > lc) >= 0)
t = LLrotation(t);
else
t = LRrotation(t);
}
else
{
return (t - > lc)
}
t - > ht = height(t - > lc)
return t;
}
}
Max heap
1. Assume a[max] \\n=last index of max heap tree
2. n=n+1, ptr=n, ele=a[n]
3. While (ptr>0)
4. {
5. par=(ptr-1)/2
6. if(ele=<a[par])
7. {
8. a[ptr]=ele;
9. Return;
10. }
11. a[ptr]=a[par]
12. ptr=par;
13. }
14. a[ptr]=ele;
15. exit
Max heap
1. Assume a[max]
2. ele=a[n],n=n-1, ptr=0, left=1, right=2
3. While (right<=n)
4. {
5. if(ele>=a[left] and ele>=a[right] )
6. {
7. a[ptr]=ele;
8. Return;
9. }
10. else if (a[left]<a[right])
11. {
12. a[ptr]=a[right]
13. ptr=right;
14. }
15. else if (a[left]>a[right])
16. {
17. a[ptr]=a[left]
18. ptr=left;
19. }
20. left=(2*ptr)+1;
21. right=left+1;
22. }
23. if(left==n and ele<=a[left])
24. a[ptr]=ele;
25. else
26. {
27. a[ptr]=a[left]
28. a[left]=ele;
29. }
30. exit
6. Graph
Source:- https://www.geeksforgeeks.org/graph-and-its-representations/
A Graph is a non-linear data structure consisting of nodes and edges. The nodes are
sometimes also referred to as vertices and edges are lines or arcs that connect any
two nodes in the graph. More formally a Graph can be defined as,
A Graph consists of a finite set of vertices(or nodes) and set of Edges which connect
a pair of nodes.
In the above Graph, the set of vertices V = {0,1,2,3,4} and the set of edges E = {01, 12,
Graphs are used to solve many real-life problems. Graphs are used to represent
networks. The networks may include paths in a city or telephone network or circuit
network. Graphs are also used in social networks like linkedIn, Facebook. For
is a structure and contains information like person id, name, gender, locale etc.
2. A finite set of ordered pair of the form (u, v) called as edge. The pair is ordered
because (u, v) is not the same as (v, u) in the case of a directed graph(di-graph). The
pair of the form (u, v) indicates that there is an edge from vertex u to vertex v. The
Graphs are used to represent many real-life applications: Graphs are used to
represent networks. The networks may include paths in a city or telephone network
or circuit network. Graphs are also used in social networks like linkedIn, Facebook.
node is a structure and contains information like person id, name, gender and locale.
1. Adjacency Matrix
2. Adjacency List
There are other representations also like, Incidence Matrix and Incidence List. The
choice of the graph representation is situation specific. It totally depends on the type
graph. Let the 2D array be adj[][], a slot adj[i][j] = 1 indicates that there is an edge from
Adjacency Matrix is also used to represent weighted graphs. If adj[i][j] = w, then there
O(1) time. Queries like whether there is an edge from vertex ‘u’ to vertex ‘v’ are
Cons: Consumes more space O(V^2). Even if the graph is sparse(contains less
number of edges), it consumes the same space. Adding a vertex is O(V^2) time.
An array of lists is used. Size of the array is equal to the number of vertices. Let the
array be array[]. An entry array[i] represents the list of vertices adjacent to the ith
vertex. This representation can also be used to represent a weighted graph. The
C++/ArrayList in Java) to represent adjacency lists instead of linked lists. The vector
// Driver code
int main()
{
int V = 5;
vector<int> adj[V];
addEdge(adj, 0, 1);
addEdge(adj, 0, 4);
addEdge(adj, 1, 2);
addEdge(adj, 1, 3);
addEdge(adj, 1, 4);
addEdge(adj, 2, 3);
addEdge(adj, 3, 4);
printGraph(adj, V);
return 0;
}
a tree (See method 2 of this post). The only catch here is, unlike trees, graphs may
contain cycles, so we may come to the same node again. To avoid processing a
node more than once, we use a boolean visited array. For simplicity, it is assumed
For example, in the following graph, we start traversal from vertex 2. When we come
to vertex 0, we look for all adjacent vertices of it. 2 is also an adjacent vertex of 0. If
we don’t mark visited vertices, then 2 will be processed again and it will become a
Following are the implementations of simple Breadth First Traversal from a given
source.
container is used to store lists of adjacent nodes and queue of nodes needed for
BFS traversal.
BFS Algorithm:-
BFS (G, s) //Where G is a graph and s is the source node
let Q be queue.
Q.enqueue( s ) //Inserting s in queue until all its neighbour vertices
are marked.
mark s as visited.
while ( Q is not empty)
//Removing that vertex from queue,whose neighbour will be visited now
v = Q.dequeue( )
//processing all the neighbours of v
for all neighbours w of v in Graph G
if w is not visited
Q.enqueue( w )//Stores w in Q to further visit its neighbour
mark w as visited.
vis[ v[ p ][ i ] ] = true;
}
}
}
}
The DFS algorithm is a recursive algorithm that uses the idea of backtracking. It involves
exhaustive searches of all the nodes by going ahead, if possible, else by backtracking.
Here, the word backtrack means that when you are moving forward and there are no more nodes
along the current path, you move backwards on the same path to find nodes to traverse. All the
nodes will be visited on the current path till all the unvisited nodes have been traversed after
This recursive nature of DFS can be implemented using stacks. The basic idea is as follows:
Pick a starting node and push all its adjacent nodes into a stack.
Pop a node from stack to select the next node to visit and push all its adjacent nodes into a
stack.
Repeat this process until the stack is empty. However, ensure that the nodes that are visited are
marked. This will prevent you from visiting the same node more than once. If you do not mark the
nodes that are visited and you visit the same node more than once, you may end up in an infinite
loop.
Pseudocode
DFS-iterative (G, s): //Where G is a graph and s is source vertex
let S be stack
S.push( s ) //Inserting s in stack
mark s as visited.
while ( S is not empty):
//Pop a vertex from stack to visit next
v = S.top( )
S.pop( )
DFS-recursive(G, s):
mark s as visited
for all neighbours w of s in Graph G:
if w is not visited:
DFS-recursive(G, w)
Prim's algorithm is a minimum spanning tree algorithm that takes a graph as input
and finds the subset of the edges of that graph which
It falls under a class of algorithms called greedy algorithms which find the local
optimum in the hopes of finding a global optimum.
We start from one vertex and keep adding edges with the lowest weight until we
reach our goal.
The pseudocode for prim's algorithm shows how we create two sets of vertices U
and V-U. U contains the list of vertices that have been visited and V-U the list of
vertices that haven't. One by one, we move vertices from set V-U to set U by
connecting the least weight edge.
1. T = ∅;
2. U = { 1 };
3. while (U ≠ V)
4. let (u, v) be the lowest cost edge such that u ∈ U and v ∈ V -
U;
5. T = T ∪ {(u, v)}
6. U = U ∪ {v}
The program below implements Prim's algorithm in C++. Although adjacency matrix
representation of graph is used, this algorithm can also be implemented using
Adjacency List to improve its efficiency.
1. #include <iostream>
2. #include <cstring>
3. using namespace std;
4.
5. #define INF 9999999
6.
7. // number of vertices in graph
8. #define V 5
9.
10. // create a 2d array of size 5x5
11. //for adjacency matrix to represent graph
12.
13. int G[V][V] = {
14. {0, 9, 75, 0, 0},
15. {9, 0, 95, 19, 42},
16. {75, 95, 0, 51, 66},
17. {0, 19, 51, 0, 31},
18. {0, 42, 66, 31, 0}
19. };
20.
21. int main () {
22.
23. int no_edge; // number of edge
24.
25. // create a array to track selected vertex
Edge : Weight
0 - 1 : 9
1 - 3 : 19
3 - 4 : 31
3 - 2 : 51
Kruskal's algorithm is another popular minimum spanning tree algorithm that uses a
different logic to find the MST of a graph. Instead of starting from a vertex, Kruskal's
algorithm sorts all the edges from low weight to high and keeps adding the lowest
edges, ignoring those edges that create a cycle.
Kruskal's algorithm is a minimum spanning tree algorithm that takes a graph as input
and finds the subset of the edges of that graph which
It falls under a class of algorithms called greedy algorithms which find the local
optimum in the hopes of finding a global optimum.
We start from the edges with the lowest weight and keep adding edges until we
reach our goal.
Any minimum spanning tree algorithm revolves around checking if adding an edge
creates a loop or not.
The most common way to find this out is an algorithm called Union FInd. The
Union-Find algorithm divides the vertices into clusters and allows us to check if two
vertices belong to the same cluster or not and hence decide whether adding an edge
creates a cycle.
1. KRUSKAL(G):
2. A = ∅
3. For each vertex v ∈ G.V:
4. MAKE-SET(v)
5. For each edge (u, v) ∈ G.E ordered by increasing order by weight(u,
v):
6. if FIND-SET(u) ≠ FIND-SET(v):
7. A = A ∪ {(u, v)}
8. UNION(u, v)
9. return A
Here is the code for C++ implementation in C++. We use standard template libraries
to make our work easier and code cleaner.
1. #include <iostream>
2. #include <vector>
3. #include <algorithm>
4. using namespace std;
5.
6. #define edge pair<int,int>
7.
8. class Graph {
9. private:
10. vector<pair<int, edge>> G; // graph
11. vector<pair<int, edge>> T; // mst
12. int *parent;
13. int V; // number of vertices/nodes in graph
14. public:
15. Graph(int V);
16. void AddWeightedEdge(int u, int v, int w);
17. int find_set(int i);
18. void union_set(int u, int v);
19. void kruskal();
68. }
69. }
70. int main() {
71. Graph g(6);
72. g.AddWeightedEdge(0, 1, 4);
73. g.AddWeightedEdge(0, 2, 4);
74. g.AddWeightedEdge(1, 2, 2);
75. g.AddWeightedEdge(1, 0, 4);
76. g.AddWeightedEdge(2, 0, 4);
77. g.AddWeightedEdge(2, 1, 2);
78. g.AddWeightedEdge(2, 3, 3);
79. g.AddWeightedEdge(2, 5, 2);
80. g.AddWeightedEdge(2, 4, 4);
81. g.AddWeightedEdge(3, 2, 3);
82. g.AddWeightedEdge(3, 4, 3);
83. g.AddWeightedEdge(4, 2, 4);
84. g.AddWeightedEdge(4, 3, 3);
85. g.AddWeightedEdge(5, 2, 2);
86. g.AddWeightedEdge(5, 4, 3);
87. g.kruskal();
88. g.print();
89. return 0;
90. }
Edge : Weight
1 - 2 : 2
2 - 5 : 2
2 - 3 : 3
3 - 4 : 3
0 - 1 : 4
Prim's algorithm is another popular minimum spanning tree algorithm that uses a
different logic to find the MST of a graph. Instead of starting from an edge, Prim's
algorithm starts from a vertex and keeps adding lowest-weight edges which aren't in
the tree, until all vertices have been covered.
Like Merge Sort, QuickSort is a Divide and Conquer algorithm. It picks an element as
pivot and partitions the given array around the picked pivot. There are many different
The key process in quickSort is partition(). Target of partitions is, given an array and
an element x of array as pivot, put x at its correct position in sorted array and put all
smaller elements (smaller than x) before x, and put all greater elements (greater than
x) after x. All this should be done in linear time.
Partition Algorithm
There can be many ways to do partition, following pseudo code adopts the method
given in CLRS book. The logic is simple, we start from the leftmost element and keep
track of index of smaller (or equal to) elements as i. While traversing, if we find a
smaller element, we swap current element with arr[i]. Otherwise we ignore current
element.
Illustration of partition() :
Like QuickSort, Merge Sort is a Divide and Conquer algorithm. It divides input array in
two halves, calls itself for the two halves and then merges the two sorted halves. The
merge() function is used for merging two halves. The merge(arr, l, m, r) is a key
process that assumes that arr[l..m] and arr[m+1..r] are sorted and merges the two
MergeSort(arr[], l, r)
If r > l
1. Find the middle point to divide the array into two halves:
middle m = (l+r)/2
2. Call mergeSort for first half:
Call mergeSort(arr, l, m)
3. Call mergeSort for second half:
Call mergeSort(arr, m+1, r)
4. Merge the two halves sorted in step 2 and 3:
Call merge(arr, l, m, r)
The following diagram from wikipedia shows the complete merge sort process for
an example array {38, 27, 43, 3, 9, 82, 10}. If we take a closer look at the diagram, we
can see that the array is recursively divided into two halves till the size becomes 1.
Once the size becomes 1, the merge processes comes into action and starts
The Standard Template Library (STL) is a set of C++ template classes to provide
common programming data structures and functions such as lists, stacks, arrays,
library and so, its components are parameterized. A working knowledge of template
● Algorithms
● Containers
● Functions
● Iterators
Algorithms
○ valarray class
Containers
Containers or container classes store objects and data. There are in total seven
standard “first-class” container classes and three container adaptor classes and only
seven header files that provide access to these containers or container adaptors.
Functions
The STL includes classes that overload the function call operator. Instances of such
classes are called function objects or functors. Functors allow the working of the
● Functors
Iterators
As the name suggests, iterators are used for working upon a sequence of values.
● Iterators
Utility Library
● pair