You are on page 1of 34

1)Multidimensional Arrays in C

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];

Three dimensional array: int three_d[10][20][30];

Size of Multidimensional Arrays:

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).

Graphical Representation of Two-Dimensional Array of Size 3 x 3

Declaration of Two-Dimensional Array in C

The basic form of declaring a 2D array with x rows and y columns in C is shown below.


Syntax:
data_type array_name[x][y];
where,
 data_type: Type of data to be stored in each element.
 array_name: name of the array
 x: Number of rows.
 y: Number of columns.
We can declare a two-dimensional integer array say ‘x’ with 10 rows and 20 columns as:
Example:
int x[10][20];
Note: In this type of declaration, the array is allocated memory in the stack and the size of the array should be
known at the compile time i.e. size of the array is fixed. We can also create an array dynamically in C by using
methods mentioned here.

Initialization of Two-Dimensional Arrays in C

The various ways in which a 2D array can be initialized are as follows:


1. Using Initializer List
2. Using Loops
1. Initialization of 2D array using Initializer List
We can initialize a 2D array in C by using an initializer list as shown in the example below.
First Method:
int x[3][4] = {0, 1 ,2 ,3 ,4 , 5 , 6 , 7 , 8 , 9 , 10 , 11}
The above array has 3 rows and 4 columns. The elements in the braces from left to right are stored in the table
also from left to right. The elements will be filled in the array in order: the first 4 elements from the left will be
filled in the first row, the next 4 elements in the second row, and so on.
Second Method (better):
int x[3][4] = {{0,1,2,3}, {4,5,6,7}, {8,9,10,11}};
This type of initialization makes use of nested braces. Each set of inner braces represents one row. In the above
example, there is a total of three rows so there are three sets of inner braces. The advantage of this method is
that it is easier to understand.
Note: The number of elements in initializer list should always be less than or equal to the total number of
elements in the array.

2. Initialization of 2D array using Loops


We can use any C loop to initialize each member of a 2D array one by one as shown in the below example.
Example:
int x[3][4];

for(int i = 0; i < 3; i++){


for(int j = 0; j < 4; j++){
x[i][j] = i + j;
}
}
This method is useful when the values of each element have some sequential relation.
Accessing Elements of Two-Dimensional Arrays in C
Elements in 2D arrays are accessed using row indexes and column indexes. Each element in a 2D array can be
referred to by:
Syntax:
array_name[i][j]
where,
 i: The row index.
 j: The column index.
Example: 
int x[2][1];
The above example represents the element present in the third row and second column.
Note: In arrays, if the size of an array is N. Its index will be from 0 to N-1. Therefore, for row index 2 row
number is 2+1 = 3. To output all the elements of a Two-Dimensional array we can use nested for loops. We
will require two ‘for‘ loops. One to traverse the rows and another to traverse columns.

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:

// C Program to print the elements of a


// Two-Dimensional array
 
#include <stdio.h>
 
int main(void)
{
    // an array with 3 rows and 2 columns.
    int x[3][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 } };
 
    // output each array element's value
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("Element at x[%i][%i]: ", i, j);
            printf("%d\n", x[i][j]);
        }
    }
 
    return (0);
}
 
// This code is contributed by sarajadhav12052009
Output
Element at x[0][0]: 0
Element at x[0][1]: 1
Element at x[1][0]: 2
Element at x[1][1]: 3
Element at x[2][0]: 4
Element at x[2][1]: 5
Time Complexity: O(N*M) ,where N(here 3) and M(here 2) are number of rows and columns repectively.
Space Complexity:O(1)
Three-Dimensional Array in C
A Three Dimensional Array or 3D array in C is a collection of two-dimensional arrays. It can be visualized
as multiple 2D arrays stacked on top of each other.

Graphical Representation of Three-Dimensional Array of Size 3 x 3 x 3


Declaration of Three-Dimensional Array in C

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 of Three-Dimensional Array in C

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];

for (int i=0; i<2; i++) {


for (int j=0; j<3; j++) {
for (int k=0; k<4; k++) {
x[i][j][k] = (some_value);
}
}
}

Accessing elements in Three-Dimensional Array in C

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

// C program to print elements of Three-Dimensional Array


 
#include <stdio.h>
 
int main(void)
{
    // initializing the 3-dimensional array
    int x[2][3][2] = { { { 0, 1 }, { 2, 3 }, { 4, 5 } },
                       { { 6, 7 }, { 8, 9 }, { 10, 11 } } };
 
    // output each element's value
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            for (int k = 0; k < 2; ++k) {
                printf("Element at x[%i][%i][%i] = %d\n", i,
                       j, k, x[i][j][k]);
            }
        }
    }
    return (0);
}
Output
Element at x[0][0][0] = 0
Element at x[0][0][1] = 1
Element at x[0][1][0] = 2
Element at x[0][1][1] = 3
Element at x[0][2][0] = 4
Element at x[0][2][1] = 5
Element at x[1][0][0] = 6
Element at x[1][0][1] = 7
Element at x[1][1][0] = 8
Element at x[1][1][1] = 9
Element at x[1][2][0] = 10
Element at x[1][2][1] = 11
In similar ways, we can create arrays with any number of dimensions. However, the complexity also increases
as the number of dimensions increases. The most used multidimensional array is the Two-Dimensional Array.
********************&&&&&&&&&&&&&&&*************************************

2) Introduction to Stack – Data Structure and


Algorithm Tutorials
 Read
 Discuss(70+)
 Courses
 Practice
 Video
What is Stack?
A stack is a linear data structure in which the insertion of a new element and removal of an existing element
takes place at the same end represented as the top of the stack.

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.

Basic Operations on Stack


In order to make manipulations in a stack, there are certain operations provided to us.
 push() to insert an element into the stack
 pop() to remove an element from the stack
 top() Returns the top element of the stack.
 isEmpty() returns true if stack is empty else false.
 size() returns the size of stack.

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

/* C++ program to implement basic stack


   operations */
#include <bits/stdc++.h>
  
using namespace std;
  
#define MAX 1000
  
class Stack {
    int top;
  
public:
    int a[MAX]; // Maximum size of Stack
  
    Stack() { top = -1; }
    bool push(int x);
    int pop();
    int peek();
    bool isEmpty();
};
  
bool Stack::push(int x)
{
    if (top >= (MAX - 1)) {
        cout << "Stack Overflow";
        return false;
    }
    else {
        a[++top] = x;
        cout << x << " pushed into stack\n";
        return true;
    }
}
  
int Stack::pop()
{
    if (top < 0) {
        cout << "Stack Underflow";
        return 0;
    }
    else {
        int x = a[top--];
        return x;
    }
}
int Stack::peek()
{
    if (top < 0) {
        cout << "Stack is Empty";
        return 0;
    }
    else {
        int x = a[top];
        return x;
    }
}
  
bool Stack::isEmpty()
{
    return (top < 0);
}
  
// Driver program to test above functions
int main()
{
    class Stack s;
    s.push(10);
    s.push(20);
    s.push(30);
    cout << s.pop() << " Popped from stack\n";
    
    //print top element of stack after popping
    cout << "Top element is : " << s.peek() << endl;
    
    //print all elements in stack :
    cout <<"Elements present in stack : ";
    while(!s.isEmpty())
    {
        // print top element in stack
        cout << s.peek() <<" ";
        // remove top element in stack
        s.pop();
    }
  
    return 0;
}
Output
10 pushed into stack
20 pushed into stack
30 pushed into stack
30 Popped from stack
Top element is : 20
Elements present in stack : 20 10
Advantages of array implementation:
 Easy to implement.
 Memory is saved as pointers are not involved.
Disadvantages of array implementation:
 It is not dynamic i.e., it doesn’t grow and shrink depending on needs at runtime. [But in case of dynamic
sized arrays like vector in C++, list in Python, ArrayList in Java, stacks can grow and shrink with array
implementation as well].
 The total size of the stack must be defined beforehand.
********************************&&&&&&&&&&&&&&&&****************************

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.

Uses of Linked List


o The list is not required to be contiguously present in the memory. The node can reside any where
in the memory and linked together to make a list. This achieves optimized utilization of space.
o list size is limited to the memory size and doesn't need to be declared in advance.
o Empty node can not be present in the linked list.
o We can store values of primitive types or objects in the singly linked list.

Why use linked list over array?


Till now, we were using array data structure to organize the group of elements that are to be stored
individually in the memory. However, Array has several advantages and disadvantages which must be
known in order to decide the data structure which will be used throughout the program.
Array contains following limitations:

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.

Singly linked list or One way chain


Singly linked list can be defined as the collection of ordered set of elements. The number of elements may
vary according to need of the program. A node in the singly linked list consist of two parts: data part and
link part. Data part of the node stores actual information that is to be represented by the node while the
link part of the node stores the address of its immediate successor.

Backward Skip 10sPlay VideoForward Skip 10s

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

Data Time Complexity Space


Structure Compleity

Average Worst Worst

Access Search Insertion Deletio Access Search Insertion Deletion


n
Singly θ(n) θ(n) θ(1) θ(1) O(n) O(n) O(1) O(1) O(n)
Linked List

Operations on Singly Linked List


There are various operations which can be performed on singly linked list. A list of all such operations is
given below.

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. .

Deletion and Traversing

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. .

Linked List in C: Menu Driven Program

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*********

Choose one option from the following list ...

===============================================
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 your choice?


1

Enter value
1

Node inserted

*********Main Menu*********

Choose one option from the following list ...

===============================================

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 your choice?


2

Enter value?
2

Doubly linked list


Doubly linked list is a complex type of linked list in which a node contains a pointer to the previous as well
as the next node in the sequence. Therefore, in a doubly linked list, a node consists of three parts: node
data, pointer to the next node in sequence (next pointer) , pointer to the previous node (previous pointer).
A sample node in a doubly linked list is shown in the figure.
A doubly linked list containing three nodes having numbers from 1 to 3 in their data part, is shown in the
following image.

In C, structure of a node in doubly linked list can be given as :

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.

Backward Skip 10sPlay VideoForward Skip 10s

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.

Memory Representation of a doubly linked list


Memory Representation of a doubly linked list is shown in the following image. Generally, doubly linked
list consumes more space for every node and therefore, causes more expansive basic operations such as
insertion and deletion. However, we can easily manipulate the elements of the list since the list maintains
pointers in both the directions (forward and backward).

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.

Circular Singly Linked List


In a circular Singly linked list, the last node of the list contains a pointer to the first node of the list. We
can have circular singly linked list as well as circular doubly linked list.

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.

The following image shows a circular singly linked list.

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

Memory Representation of circular linked list:


In the following image, memory representation of a circular linked list containing marks of a student in 4
subjects. However, the image shows a glimpse of how the circular list is being stored in the memory. The
start or head of the list is pointing to the element with the index 1 and containing 13 marks in the data
part and 4 in the next part. Which means that it is linked with the node that is being stored at 4th index of
the list.

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.

Operations on Circular Singly linked list:


Insertion

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.
**********************************&&&&&&&&&&&&&&&&&****************************

4) Binary Search – Data Structure and Algorithm


Tutorials
 Read
 Discuss(210+)
 Courses
 Practice
Binary Search is defined as a searching algorithm used in a sorted array by repeatedly dividing the search
interval in half. The idea of binary search is to use the information that the array is sorted and reduce the time
complexity to O(log N). 

Example of Binary Search Algorithm

Conditions for when to apply Binary Search in a Data Structure:


To apply Binary Search algorithm:
 The data structure must be sorted.
 Access to any element of the data structure takes constant time.
Binary Search Algorithm:
In this algorithm, 
 Divide the search space into two halves by finding the middle index “mid”. 

Finding the middle index “mid” in Binary Search Algorithm


 Compare the middle element of the search space with the key. 
 If the key is found at middle element, the process is terminated.
 If the key is not found at middle element, choose which half will be used as the next search space.
 If the key is smaller than the middle element, then the left side is used for next search.
 If the key is larger than the middle element, then the right side is used for next search.
 This process is continued until the key is found or the total search space is exhausted.

How does Binary Search work?


To understand the working of binary search, consider the following illustration:
Consider an array arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91}, and the target = 23.
First Step: Calculate the mid and compare the mid element with the key. If the key is less than mid element,
move to left and if it is greater than the mid then move search space to the right.
How to Implement Binary Search?
The Binary Search Algorithm can be implemented in the following two ways
 Iterative Binary Search Algorithm
 Recursive Binary Search Algorithm
Given below are the pseudocodes for the approaches.

1. Iterative  Binary Search Algorithm:


Here we use a while loop to continue the process of comparing the key and splitting the search space in two
halves.

Implementation of Iterative  Binary Search Algorithm: 

// C program to implement iterative Binary Search


#include <stdio.h>
 
// An iterative binary search function.
int binarySearch(int arr[], int l, int r, int x)
{
    while (l <= r) {
        int m = l + (r - l) / 2;
 
        // Check if x is present at mid
        if (arr[m] == x)
            return m;
 
        // If x greater, ignore left half
        if (arr[m] < x)
            l = m + 1;
 
        // If x is smaller, ignore right half
        else
            r = m - 1;
    }
 
    // If we reach here, then element was not present
    return -1;
}
 
// Driver code
int main(void)
{
    int arr[] = { 2, 3, 4, 10, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int x = 10;
    int result = binarySearch(arr, 0, n - 1, x);
    (result == -1) ? printf("Element is not present"
                            " in array")
                   : printf("Element is present at "
                            "index %d",
                            result);
    return 0;
}
Output
Element is present at index 3
Time Complexity: O(log N)
Auxiliary Space: O(1)
2. Recursive  Binary Search Algorithm:
Create a recursive function and compare the mid of the search space with the key. And based on the result
either return the index where the key is found or call the recursive function for the next search space.

Implementation of Recursive  Binary Search Algorithm:

// C program to implement recursive Binary Search


#include <stdio.h>
 
// A recursive binary search function. It returns
// location of x in given array arr[l..r] is present,
// otherwise -1
int binarySearch(int arr[], int l, int r, int x)
{
    if (r >= l) {
        int mid = l + (r - l) / 2;
 
        // If the element is present at the middle
        // itself
        if (arr[mid] == x)
            return mid;
 
        // If element is smaller than mid, then
        // it can only be present in left subarray
        if (arr[mid] > x)
            return binarySearch(arr, l, mid - 1, x);
 
        // Else the element can only be present
        // in right subarray
        return binarySearch(arr, mid + 1, r, x);
    }
 
    // We reach here when element is not
    // present in array
    return -1;
}
 
// Driver code
int main()
{
    int arr[] = { 2, 3, 4, 10, 40 };
    int n = sizeof(arr) / sizeof(arr[0]);
    int x = 10;
    int result = binarySearch(arr, 0, n - 1, x);
    (result == -1)
        ? printf("Element is not present in array")
        : printf("Element is present at index %d", result);
    return 0;
}
Output
Element is present at index 3
Complexity Analysis of Binary Search:
 Time Complexity: 
 Best Case: O(1)
 Average Case: O(log N)
 Worst Case: O(log N)
 Auxiliary Space: O(1), If the recursive call stack is considered then the auxiliary space will be O(logN).
Advantages of Binary Search:
 Binary search is faster than linear search, especially for large arrays.
 More efficient than other searching algorithms with a similar time complexity, such as interpolation search
or exponential search.
 Binary search is well-suited for searching large datasets that are stored in external memory, such as on a
hard drive or in the cloud.
Drawbacks of Binary Search:
 The array should be sorted.
 Binary search requires that the data structure being searched be stored in contiguous memory locations. 
 Binary search requires that the elements of the array be comparable, meaning that they must be able to be
ordered.
Applications of Binary Search:
 Binary search can be used as a building block for more complex algorithms used in machine learning, such
as algorithms for training neural networks or finding the optimal hyperparameters for a model.
 It can be used for searching in computer graphics such as algorithms for ray tracing or texture mapping.
 It can be used for searching a database.
******************************&&&&&&&&&&&&&&&&&&&&&&&**********************

5)Prim’s Algorithm for Minimum Spanning Tree


Introduction to Prim’s algorithm:
We have discussed Kruskal’s algorithm for Minimum Spanning Tree. Like Kruskal’s algorithm, Prim’s
algorithm is also a Greedy algorithm. This algorithm always starts with a single node and moves through
several adjacent nodes, in order to explore all of the connected edges along the way.
The algorithm starts with an empty spanning tree. The idea is to maintain two sets of vertices. The first set
contains the vertices already included in the MST, and the other set contains the vertices not yet included. At
every step, it considers all the edges that connect the two sets and picks the minimum weight edge from these
edges. After picking the edge, it moves the other endpoint of the edge to the set containing MST. 

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.

0 is selected as 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.

1 is added to 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).

6 is added in the MST

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.

Include vertex 5 in the MST

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.

Include vertex 2 in the MST


Step 7: The connecting edges between the incomplete MST and the other edges are {2, 8}, {2, 3}, {5, 3} and {5,
4}. The edge with minimum weight is edge {2, 8} which has weight 2. So include this edge and the vertex 8 in
the MST.

Add vertex 8 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.

Include vertex 3 in MST

Step 9: Only  the vertex 4 remains to be included. The minimum weighted edge from the incomplete MST to 4
is {3, 4}.

Include vertex 4 in the MST

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

How to implement Prim’s Algorithm?

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

// A C program for Prim's Minimum


// Spanning Tree (MST) algorithm. The program is
// for adjacency matrix representation of the graph
 
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
 
// Number of vertices in the graph
#define V 5
 
// A utility function to find the vertex with
// minimum key value, from the set of vertices
// not yet included in MST
int minKey(int key[], bool mstSet[])
{
    // Initialize min value
    int min = INT_MAX, min_index;
 
    for (int v = 0; v < V; v++)
        if (mstSet[v] == false && key[v] < min)
            min = key[v], min_index = v;
 
    return min_index;
}
 
// A utility function to print the
// constructed MST stored in parent[]
int printMST(int parent[], int graph[V][V])
{
    printf("Edge \tWeight\n");
    for (int i = 1; i < V; i++)
        printf("%d - %d \t%d \n", parent[i], i,
               graph[i][parent[i]]);
}
 
// Function to construct and print MST for
// a graph represented using adjacency
// matrix representation
void primMST(int graph[V][V])
{
    // Array to store constructed MST
    int parent[V];
    // Key values used to pick minimum weight edge in cut
    int key[V];
    // To represent set of vertices included in MST
    bool mstSet[V];
 
    // Initialize all keys as INFINITE
    for (int i = 0; i < V; i++)
        key[i] = INT_MAX, mstSet[i] = false;
 
    // Always include first 1st vertex in MST.
    // Make key 0 so that this vertex is picked as first
    // vertex.
    key[0] = 0;
   
    // First node is always root of MST
    parent[0] = -1;
 
    // The MST will have V vertices
    for (int count = 0; count < V - 1; count++) {
         
        // Pick the minimum key vertex from the
        // set of vertices not yet included in MST
        int u = minKey(key, mstSet);
 
        // Add the picked vertex to the MST Set
        mstSet[u] = true;
 
        // Update key value and parent index of
        // the adjacent vertices of the picked vertex.
        // Consider only those vertices which are not
        // yet included in MST
        for (int v = 0; v < V; v++)
 
            // graph[u][v] is non zero only for adjacent
            // vertices of m mstSet[v] is false for vertices
            // not yet included in MST Update the key only
            // if graph[u][v] is smaller than key[v]
            if (graph[u][v] && mstSet[v] == false
                && graph[u][v] < key[v])
                parent[v] = u, key[v] = graph[u][v];
    }
 
    // print the constructed MST
    printMST(parent, graph);
}
 
// Driver's code
int main()
{
    int graph[V][V] = { { 0, 2, 0, 6, 0 },
                        { 2, 0, 3, 8, 5 },
                        { 0, 3, 0, 0, 7 },
                        { 6, 8, 0, 0, 9 },
                        { 0, 5, 7, 9, 0 } };
 
    // Print the solution
    primMST(graph);
 
    return 0;
}
Output
Edge Weight
0 - 1 2
1 - 2 3
0 - 3 6
1 - 4 5

You might also like