Assignment DS
Assignment DS
Prerequisite: Arrays in C
A multi-dimensional array can be termed as an array of arrays that stores homogeneous data in tabular form.
Data in multidimensional arrays is generally stored in row-major order in the memory.
The general form of declaring N-dimensional arrays is shown below.
Syntax:
data_type array_name[size1][size2]....[sizeN];
data_type: Type of data to be stored in the array.
array_name: Name of the array.
size1, size2,…, sizeN: Size of each dimension.
Examples:
Two dimensional array: int two_d[10][20];
The total number of elements that can be stored in a multidimensional array can be calculated by multiplying
the size of all the dimensions.
For example:
The array int x[10][20] can store total (10*20) = 200 elements.
Similarly array int x[5][10][20] can store total (5*10*20) = 1000 elements.
To get the size of the array in bytes, we multiply the size of a single element with the total number of elements
in the array.
For example:
Size of array int x[10][20] = 10 * 20 * 4 = 800 bytes. (where int = 4 bytes)
Similarly, size of int x[5][10][20] = 5 * 10 * 20 * 4 = 4000 bytes. (where int = 4 bytes)
The most commonly used forms of the multidimensional array are:
1. Two Dimensional Array
2. Three Dimensional Array
Two-Dimensional Array in C
A two-dimensional array or 2D array in C is the simplest form of the multidimensional array. We can
visualize a two-dimensional array as an array of one-dimensional arrays arranged one over another forming a
table with ‘x’ rows and ‘y’ columns where the row number ranges from 0 to (x-1) and the column number
ranges from 0 to (y-1).
For printing the whole array, we access each element one by one using loops. The order of traversal can be
row-major order or column-major order depending upon the requirement. The below example demonstrates the
row-major traversal of a 2D array.
Example:
We can declare a 3D array with x 2D arrays each having y rows and z columns using the syntax shown below.
Syntax:
data_type array_name[x][y][z];
data_type: Type of data to be stored in each element.
array_name: name of the array
x: Number of 2D arrays.
y: Number of rows in each 2D array.
z: Number of columns in each 2D array.
Example:
int array[3][3][3];
Initialization in a 3D array is the same as that of 2D arrays. The difference is as the number of dimensions
increases so the number of nested braces will also increase.
A 3D array in C can be initialized by using:
1. Initializer List
2. Loops
Initialization of 3D Array using Initializer List
Method 1:
int x[2][3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23};
Method 2(Better):
int x[2][3][4] =
{
{ {0,1,2,3}, {4,5,6,7}, {8,9,10,11} },
{ {12,13,14,15}, {16,17,18,19}, {20,21,22,23} }
};
Initialization of 3D Array using Loops
It is also similar to that of 2D array with one more nested loop for accessing one more dimension.
int x[2][3][4];
Accessing elements in 3D Arrays is also similar to that of 3D Arrays. The difference is we have to use three
loops instead of two loops for one additional dimension in 3D Arrays.
Syntax:
array_name[x][y][z]
where,
x: Index of 2D array.
y: Index of that 2D array row.
z: Index of that 2D array column
To implement the stack, it is required to maintain the pointer to the top of the stack, which is the last element
to be inserted because we can access the elements only on the top of the stack.
LIFO( Last In First Out ):
This strategy states that the element that is inserted last will come out first. You can take a pile of plates kept
on top of each other as a real-life example. The plate which we put last is on the top and since we remove the
plate that is at the top, we can say that the plate that was put last comes out first.
Stack
Push:
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
Algorithm for push:
begin
if stack is full
return
endif
else
increment top
stack[top] assign value
end else
end procedure
Pop:
Removes an item from the stack. The items are popped in the reversed order in which they are pushed. If the
stack is empty, then it is said to be an Underflow condition.
Algorithm for pop:
begin
if stack is empty
return
endif
else
store value of stack[top]
decrement top
return value
end else
end procedure
Top:
Returns the top element of the stack.
Algorithm for Top:
begin
return stack[top]
end procedure
isEmpty:
Returns true if the stack is empty, else false.
Algorithm for isEmpty:
begin
if top < 1
return true
else
return false
end procedure
Understanding stack practically:
There are many real-life examples of a stack. Consider the simple example of plates stacked over one another
in a canteen. The plate which is at the top is the first one to be removed, i.e. the plate which has been placed at
the bottommost position remains in the stack for the longest period of time. So, it can be simply seen to follow
the LIFO/FILO order.
Complexity Analysis:
Time Complexity
Operations Complexity
push() O(1)
pop() O(1)
isEmpty() O(1)
size() O(1)
Types of Stacks:
Fixed Size Stack: As the name suggests, a fixed size stack has a fixed size and cannot grow or shrink
dynamically. If the stack is full and an attempt is made to add an element to it, an overflow error occurs. If
the stack is empty and an attempt is made to remove an element from it, an underflow error occurs.
Dynamic Size Stack: A dynamic size stack can grow or shrink dynamically. When the stack is full, it
automatically increases its size to accommodate the new element, and when the stack is empty, it decreases
its size. This type of stack is implemented using a linked list, as it allows for easy resizing of the stack.
In addition to these two main types, there are several other variations of Stacks, including:
1. Infix to Postfix Stack: This type of stack is used to convert infix expressions to postfix expressions.
2. Expression Evaluation Stack: This type of stack is used to evaluate postfix expressions.
3. Recursion Stack: This type of stack is used to keep track of function calls in a computer program and to
return control to the correct function when a function returns.
4. Memory Management Stack: This type of stack is used to store the values of the program counter and the
values of the registers in a computer program, allowing the program to return to the previous state when a
function returns.
5. Balanced Parenthesis Stack: This type of stack is used to check the balance of parentheses in an
expression.
6. Undo-Redo Stack: This type of stack is used in computer programs to allow users to undo and redo
actions.
Applications of the stack:
Infix to Postfix /Prefix conversion
Redo-undo features at many places like editors, photoshop.
Forward and backward features in web browsers
Used in many algorithms like Tower of Hanoi, tree traversals, stock span problems, and histogram
problems.
Backtracking is one of the algorithm designing techniques. Some examples of backtracking are the Knight-
Tour problem, N-Queen problem, find your way through a maze, and game-like chess or checkers in all
these problems we dive into someway if that way is not efficient we come back to the previous state and go
into some another path. To get back from a current state we need to store the previous state for that purpose
we need a stack.
In Graph Algorithms like Topological Sorting and Strongly Connected Components
In Memory management, any modern computer uses a stack as the primary management for a running
purpose. Each program that is running in a computer system has its own memory allocations
String reversal is also another application of stack. Here one by one each character gets inserted into the
stack. So the first character of the string is on the bottom of the stack and the last element of a string is on
the top of the stack. After Performing the pop operations on the stack we get a string in reverse order.
Stack also helps in implementing function call in computers. The last called function is always completed
first.
Stacks are also used to implement the undo/redo operation in text editor.
Implementation of Stack:
A stack can be implemented using an array or a linked list. In an array-based implementation, the push
operation is implemented by incrementing the index of the top element and storing the new element at that
index. The pop operation is implemented by decrementing the index of the top element and returning the value
stored at that index. In a linked list-based implementation, the push operation is implemented by creating a
new node with the new element and setting the next pointer of the current top node to the new node. The pop
operation is implemented by setting the next pointer of the current top node to the next node and returning the
value of the current top node.
Stacks are commonly used in computer science for a variety of applications, including the evaluation of
expressions, function calls, and memory management. In the evaluation of expressions, a stack can be used to
store operands and operators as they are processed. In function calls, a stack can be used to keep track of the
order in which functions are called and to return control to the correct function when a function returns. In
memory management, a stack can be used to store the values of the program counter and the values of the
registers in a computer program, allowing the program to return to the previous state when a function returns.
In conclusion, a Stack is a linear data structure that operates on the LIFO principle and can be implemented
using an array or a linked list. The basic operations that can be performed on a stack include push, pop, and
peek, and stacks are commonly used in computer science for a variety of applications, including the evaluation
of expressions, function calls, and memory management.There are two ways to implement a stack –
Using array
Using linked list
Implementing Stack using Arrays:
Recommended Problem
Implement Stack using Linked Lis
3) Linked List
o Linked List can be defined as collection of objects called nodes that are randomly stored in the
memory.
o A node contains two fields i.e. data stored at that particular address and the pointer which
contains the address of the next node in the memory.
o The last node of the list contains pointer to the null.
1. The size of array must be known in advance before using it in the program.
2. Increasing size of the array is a time taking process. It is almost impossible to expand the size of
the array at run time.
3. All the elements in the array need to be contiguously stored in the memory. Inserting any
element in the array needs shifting of all its predecessors.
Linked list is the data structure which can overcome all the limitations of an array. Using linked list is
useful because,
1. It allocates the memory dynamically. All the nodes of linked list are non-contiguously stored in
the memory and linked together with the help of pointers.
2. Sizing is no longer a problem since we do not need to define its size at the time of declaration.
List grows as per the program's demand and limited to the available memory space.
One way chain or singly linked list can be traversed only in one direction. In other words, we can say that
each node contains only next pointer, therefore we can not traverse the list in the reverse direction.
Consider an example where the marks obtained by the student in three subjects are stored in a linked list
as shown in the figure.
In the above figure, the arrow represents the links. The data part of every node contains the marks
obtained by the student in the different subject. The last node in the list is identified by the null pointer
which is present in the address part of the last node. We can have as many elements we require, in the
data part of the list.
Complexity
Node Creation
1. struct node
2. {
3. int data;
4. struct node *next;
5. };
6. struct node *head, *ptr;
7. ptr = (struct node *)malloc(sizeof(struct node *));
Insertion
The insertion into a singly linked list can be performed at different positions. Based on the position of the
new node being inserted, the insertion is categorized into the following categories.
S Operation Description
N
1 Insertion at It involves inserting any element at the front of the list. We just need to a
beginning few link adjustments to make the new node as the head of the list.
2 Insertion at end It involves insertion at the last of the linked list. The new node can be
of the list inserted as the only node in the list or it can be inserted as the last one.
Different logics are implemented in each scenario.
3 Insertion after It involves insertion after the specified node of the linked list. We need to
specified node skip the desired number of nodes in order to reach the node after which the
new node will be inserted. .
The Deletion of a node from a singly linked list can be performed at different positions. Based on the
position of the node being deleted, the operation is categorized into the following categories.
S Operation Description
N
1 Deletion at It involves deletion of a node from the beginning of the list. This is the
beginning simplest operation among all. It just need a few adjustments in the node
pointers.
2 Deletion at the It involves deleting the last node of the list. The list can either be empty or
end of the list full. Different logic is implemented for the different scenarios.
3 Deletion after It involves deleting the node after the specified node in the list. we need to
specified node skip the desired number of nodes to reach the node after which the node
will be deleted. This requires traversing through the list.
4 Traversing In traversing, we simply visit each node of the list at least once in order to
perform some specific operation on it, for example, printing data part of
each node present in the list.
5 Searching In searching, we match each element of the list with the given element. If the
element is found on any of the location then location of that element is
returned otherwise null is returned. .
1. #include<stdio.h>
2. #include<stdlib.h>
3. struct node
4. {
5. int data;
6. struct node *next;
7. };
8. struct node *head;
9.
10. void beginsert ();
11. void lastinsert ();
12. void randominsert();
13. void begin_delete();
14. void last_delete();
15. void random_delete();
16. void display();
17. void search();
18. void main ()
19. {
20. int choice =0;
21. while(choice != 9)
22. {
23. printf("\n\n*********Main Menu*********\n");
24. printf("\nChoose one option from the following list ...\n");
25. printf("\n===============================================\n");
26. printf("\n1.Insert in begining\n2.Insert at last\n3.Insert at any random location\n4.Delete from Beginn
ing\n
27. 5.Delete from last\n6.Delete node after specified location\n7.Search for an element\n8.Show\n9.Exit\
n");
28. printf("\nEnter your choice?\n");
29. scanf("\n%d",&choice);
30. switch(choice)
31. {
32. case 1:
33. beginsert();
34. break;
35. case 2:
36. lastinsert();
37. break;
38. case 3:
39. randominsert();
40. break;
41. case 4:
42. begin_delete();
43. break;
44. case 5:
45. last_delete();
46. break;
47. case 6:
48. random_delete();
49. break;
50. case 7:
51. search();
52. break;
53. case 8:
54. display();
55. break;
56. case 9:
57. exit(0);
58. break;
59. default:
60. printf("Please enter valid choice..");
61. }
62. }
63. }
64. void beginsert()
65. {
66. struct node *ptr;
67. int item;
68. ptr = (struct node *) malloc(sizeof(struct node *));
69. if(ptr == NULL)
70. {
71. printf("\nOVERFLOW");
72. }
73. else
74. {
75. printf("\nEnter value\n");
76. scanf("%d",&item);
77. ptr->data = item;
78. ptr->next = head;
79. head = ptr;
80. printf("\nNode inserted");
81. }
82.
83. }
84. void lastinsert()
85. {
86. struct node *ptr,*temp;
87. int item;
88. ptr = (struct node*)malloc(sizeof(struct node));
89. if(ptr == NULL)
90. {
91. printf("\nOVERFLOW");
92. }
93. else
94. {
95. printf("\nEnter value?\n");
96. scanf("%d",&item);
97. ptr->data = item;
98. if(head == NULL)
99. {
100. ptr -> next = NULL;
101. head = ptr;
102. printf("\nNode inserted");
103. }
104. else
105. {
106. temp = head;
107. while (temp -> next != NULL)
108. {
109. temp = temp -> next;
110. }
111. temp->next = ptr;
112. ptr->next = NULL;
113. printf("\nNode inserted");
114.
115. }
116. }
117. }
118. void randominsert()
119. {
120. int i,loc,item;
121. struct node *ptr, *temp;
122. ptr = (struct node *) malloc (sizeof(struct node));
123. if(ptr == NULL)
124. {
125. printf("\nOVERFLOW");
126. }
127. else
128. {
129. printf("\nEnter element value");
130. scanf("%d",&item);
131. ptr->data = item;
132. printf("\nEnter the location after which you want to insert ");
133. scanf("\n%d",&loc);
134. temp=head;
135. for(i=0;i<loc;i++)
136. {
137. temp = temp->next;
138. if(temp == NULL)
139. {
140. printf("\ncan't insert\n");
141. return;
142. }
143.
144. }
145. ptr ->next = temp ->next;
146. temp ->next = ptr;
147. printf("\nNode inserted");
148. }
149. }
150. void begin_delete()
151. {
152. struct node *ptr;
153. if(head == NULL)
154. {
155. printf("\nList is empty\n");
156. }
157. else
158. {
159. ptr = head;
160. head = ptr->next;
161. free(ptr);
162. printf("\nNode deleted from the begining ...\n");
163. }
164. }
165. void last_delete()
166. {
167. struct node *ptr,*ptr1;
168. if(head == NULL)
169. {
170. printf("\nlist is empty");
171. }
172. else if(head -> next == NULL)
173. {
174. head = NULL;
175. free(head);
176. printf("\nOnly node of the list deleted ...\n");
177. }
178.
179. else
180. {
181. ptr = head;
182. while(ptr->next != NULL)
183. {
184. ptr1 = ptr;
185. ptr = ptr ->next;
186. }
187. ptr1->next = NULL;
188. free(ptr);
189. printf("\nDeleted Node from the last ...\n");
190. }
191. }
192. void random_delete()
193. {
194. struct node *ptr,*ptr1;
195. int loc,i;
196. printf("\n Enter the location of the node after which you want to perform deletion \n");
197. scanf("%d",&loc);
198. ptr=head;
199. for(i=0;i<loc;i++)
200. {
201. ptr1 = ptr;
202. ptr = ptr->next;
203.
204. if(ptr == NULL)
205. {
206. printf("\nCan't delete");
207. return;
208. }
209. }
210. ptr1 ->next = ptr ->next;
211. free(ptr);
212. printf("\nDeleted node %d ",loc+1);
213. }
214. void search()
215. {
216. struct node *ptr;
217. int item,i=0,flag;
218. ptr = head;
219. if(ptr == NULL)
220. {
221. printf("\nEmpty List\n");
222. }
223. else
224. {
225. printf("\nEnter item which you want to search?\n");
226. scanf("%d",&item);
227. while (ptr!=NULL)
228. {
229. if(ptr->data == item)
230. {
231. printf("item found at location %d ",i+1);
232. flag=0;
233. }
234. else
235. {
236. flag=1;
237. }
238. i++;
239. ptr = ptr -> next;
240. }
241. if(flag==1)
242. {
243. printf("Item not found\n");
244. }
245. }
246.
247. }
248.
249. void display()
250. {
251. struct node *ptr;
252. ptr = head;
253. if(ptr == NULL)
254. {
255. printf("Nothing to print");
256. }
257. else
258. {
259. printf("\nprinting values . . . . .\n");
260. while (ptr!=NULL)
261. {
262. printf("\n%d",ptr->data);
263. ptr = ptr -> next;
264. }
265. }
266. }
267.
Output:
*********Main Menu*********
===============================================
1.Insert in begining
2.Insert at last
3.Insert at any random location
4.Delete from Beginning
5.Delete from last
6.Delete node after specified location
7.Search for an element
8.Show
9.Exit
Enter value
1
Node inserted
*********Main Menu*********
===============================================
1.Insert in begining
2.Insert at last
3.Insert at any random location
4.Delete from Beginning
5.Delete from last
6.Delete node after specified location
7.Search for an element
8.Show
9.Exit
Enter value?
2
1. struct node
2. {
3. struct node *prev;
4. int data;
5. struct node *next;
6. }
The prev part of the first node and the next part of the last node will always contain null indicating end in
each direction.
In a singly linked list, we could traverse only in one direction, because each node contains address of the
next node and it doesn't have any record of its previous nodes. However, doubly linked list overcome this
limitation of singly linked list. Due to the fact that, each node of the list contains the address of its
previous node, we can find all the details about the previous node as well by using the previous address
stored inside the previous part of each node.
In the following image, the first element of the list that is i.e. 13 stored at address 1. The head pointer
points to the starting address 1. Since this is the first element being added to the list therefore the prev of
the list contains null. The next node of the list resides at address 4 therefore the first node contains 4 in
its next pointer.
We can traverse the list in this way until we find any node containing null or -1 in its next part.
Operations on doubly linked list
Node Creation
1. struct node
2. {
3. struct node *prev;
4. int data;
5. struct node *next;
6. };
7. struct node *head;
All the remaining operations regarding doubly linked list are described in the following table.
S Operation Description
N
1 Insertion at beginning Adding the node into the linked list at beginning.
2 Insertion at end Adding the node into the linked list to the end.
3 Insertion after specified Adding the node into the linked list after the specified node.
node
4 Deletion at beginning Removing the node from beginning of the list
5 Deletion at the end Removing the node from end of the list.
6 Deletion of the node Removing the node which is present just after the node containing
having given data the given data.
7 Searching Comparing each node data with the item to be searched and return
the location of the item in the list if the item found else return null.
8 Traversing Visiting each node of the list at least once in order to perform some
specific operation like searching, sorting, display, etc.
We traverse a circular singly linked list until we reach the same node where we started. The circular singly
liked list has no beginning and no ending. There is no null value present in the next part of any of the
nodes.
Circular linked list are mostly used in task maintenance in operating systems. There are many examples
where circular linked list are being used in computer science including browser surfing where a record of
pages visited in the past by the user, is maintained in the form of circular linked lists and can be accessed
again on clicking the previous button.
PlayNext
Unmute
Current Time 0:00
/
Duration 18:10
Loaded: 0.37%
Â
Fullscreen
Backward Skip 10sPlay VideoForward Skip 10s
However, due to the fact that we are considering circular linked list in the memory therefore the last node
of the list contains the address of the first node of the list.
We can also have more than one number of linked list in the memory with the different start pointers
pointing to the different start nodes in the list. The last node is identified by its next part which contains
the address of the start node of the list. We must be able to identify the last node of any linked list so that
we can find out the number of iterations which need to be performed while traversing the list.
SN Operation Description
1 Insertion at beginning Adding a node into circular singly linked list at the beginning.
2 Insertion at the end Adding a node into circular singly linked list at the end.
**********************************&&&&&&&&&&&&&&&&&****************************
A group of edges that connects two sets of vertices in a graph is called cut in graph theory. So, at every step of
Prim’s algorithm, find a cut, pick the minimum weight edge from the cut, and include this vertex in MST Set
(the set that contains already included vertices).
How does Prim’s Algorithm Work?
The working of Prim’s algorithm can be described by using the following steps:
Step 1: Determine an arbitrary vertex as the starting vertex of the MST.
Step 2: Follow steps 3 to 5 till there are vertices that are not included in the MST (known as fringe vertex).
Step 3: Find edges connecting any tree vertex with the fringe vertices.
Step 4: Find the minimum among these edges.
Step 5: Add the chosen edge to the MST if it does not form any cycle.
Step 6: Return the MST and exit
Note: For determining a cycle, we can divide the vertices into two sets [one set contains the vertices included
in MST and the other contains the fringe vertices.]
Recommended Problem
Minimum Spanning Tree
Submission count: 81.7K
Illustration of Prim’s Algorithm:
Consider the following graph as an example for which we need to find the Minimum Spanning Tree (MST).
Example of a graph
Step 1: Firstly, we select an arbitrary vertex that acts as the starting vertex of the Minimum Spanning Tree.
Here we have selected vertex 0 as the starting vertex.
Step 2: All the edges connecting the incomplete MST and other vertices are the edges {0, 1} and {0, 7}.
Between these two the edge with minimum weight is {0, 1}. So include the edge and vertex 1 in the MST.
Step 3: The edges connecting the incomplete MST to other vertices are {0, 7}, {1, 7} and {1, 2}. Among these
edges the minimum weight is 8 which is of the edges {0, 7} and {1, 2}. Let us here include the edge {0, 7} and
the vertex 7 in the MST. [We could have also included edge {1, 2} and vertex 2 in the MST].
7 is added in the MST
Step 4: The edges that connect the incomplete MST with the fringe vertices are {1, 2}, {7, 6} and {7, 8}. Add
the edge {7, 6} and the vertex 6 in the MST as it has the least weight (i.e., 1).
Step 5: The connecting edges now are {7, 8}, {1, 2}, {6, 8} and {6, 5}. Include edge {6, 5} and vertex 5 in the
MST as the edge has the minimum weight (i.e., 2) among them.
Step 6: Among the current connecting edges, the edge {5, 2} has the minimum weight. So include that edge and
the vertex 2 in the MST.
Step 8: See here that the edges {7, 8} and {2, 3} both have same weight which are minimum. But 7 is already
part of MST. So we will consider the edge {2, 3} and include that edge and vertex 3 in the MST.
Step 9: Only the vertex 4 remains to be included. The minimum weighted edge from the incomplete MST to 4
is {3, 4}.
The final structure of the MST is as follows and the weight of the edges of the MST is (4 + 8 + 1 + 2 + 4 + 2 +
7 + 9) = 37.
The structure of the MST formed using the above method
Note: If we had selected the edge {1, 2} in the third step then the MST would look like the following.
Structure of the alternate MST if we had selected edge {1, 2} in the MST
Follow the given steps to utilize the Prim’s Algorithm mentioned above for finding MST of a graph:
Create a set mstSet that keeps track of vertices already included in MST.
Assign a key value to all vertices in the input graph. Initialize all key values as INFINITE. Assign the key
value as 0 for the first vertex so that it is picked first.
While mstSet doesn’t include all vertices
Pick a vertex u that is not there in mstSet and has a minimum key value.
Include u in the mstSet.
Update the key value of all adjacent vertices of u. To update the key values, iterate through all
adjacent vertices.
For every adjacent vertex v, if the weight of edge u-v is less than the previous key
value of v, update the key value as the weight of u-v.
The idea of using key values is to pick the minimum weight edge from the cut. The key values are used only
for vertices that are not yet included in MST, the key value for these vertices indicates the minimum weight
edges connecting them to the set of vertices included in MST.
Below is the implementation of the approach