Programming in C with Data Structure
December 2023
Q – What is the process to create increment and decrement statement in C ?
Ans - In C programming, the *increment* and *decrement* operations are used
to increase or decrease the value of a variable by 1. These operations are
performed using special operators:
### Increment Operator (++)
The increment operator increases the value of a variable by 1. It can be used in
two forms:
1. *Post-increment*: variable++
- Increases the value of the variable after it is used.
- Example:
int x = 5;
printf("%d", x++); // prints 5, then x becomes 6
printf("%d", x); // prints 6
2. *Pre-increment*: ++variable
- Increases the value of the variable before it is used.
- Example:
int x = 5;
printf("%d", ++x); // x becomes 6, then prints 6
### Decrement Operator (--)
The decrement operator decreases the value of a variable by 1. It can also be
used in two forms:
1. *Post-decrement*: variable--
- Decreases the value of the variable after it is used.
- Example:
int x = 5;
printf("%d", x--); // prints 5, then x becomes 4
printf("%d", x); // prints 4
2. *Pre-decrement*: --variable
- Decreases the value of the variable before it is used.
- Example:
int x = 5;
printf("%d", --x); // x becomes 4, then prints 4
### Example of Using Increment and Decrement in a Program:
#include <stdio.h>
int main() {
int a = 5;
int b = 10;
printf("a before increment: %d\n", a);
printf("a after increment: %d\n", ++a); // pre-increment
printf("b before decrement: %d\n", b);
printf("b after decrement: %d\n", b--); // post-decrement
printf("b after post-decrement: %d\n", b);
return 0;
### Output:
a before increment: 5
a after increment: 6
b before decrement: 10
b after decrement: 10
b after post-decrement: 9
In this example:
- The variable a is pre-incremented, meaning it is incremented before it is
printed.
- The variable b is post-decremented, meaning it is printed first, then
decremented.
These operators are commonly used in loops and conditions to make the code
more concise and readable.
Q - What is the explanation for the dangling pointer in C ?
Ans - Explanation of Dangling Pointer
A dangling pointer occurs when a pointer continues to reference a memory
location that has been deallocated or freed. This situation often arises in
languages like C or C++, which provide manual memory management. Once the
memory a pointer references is released, the pointer still holds the address of
the now-invalid memory location, leading to undefined behavior if accessed.
Causes of Dangling Pointers
1. Memory Deallocation: Using free() or delete to release memory without
nullifying the pointer.
2. Returning Local Variables: Returning the address of a local variable from
a function, as the variable goes out of scope after the function ends.
3. Object Destruction: When an object is destroyed, pointers referring to
the object become dangling.
Example of Dangling Pointer
#include <stdio.h>
#include <stdlib.h>
int* createDanglingPointer() {
int a = 42;
return &a; // Returning address of a local variable
}
int main() {
// Example 1: Local variable
int* ptr = createDanglingPointer();
printf("Dangling pointer value: %d\n", *ptr); // Undefined behavior
// Example 2: Memory deallocation
int* num = (int*)malloc(sizeof(int));
*num = 10;
free(num); // Memory freed
printf("Dangling pointer after free: %d\n", *num); // Undefined behavior
return 0;
Problems Caused by Dangling Pointers
Undefined Behaviour: Can cause crashes or erratic program behaviour.
Data Corruption: Writing to a dangling pointer can overwrite unintended
memory locations.
Security Risks: Exploited by attackers to inject malicious code or steal
sensitive data.
Prevention
1. Set Pointers to NULL: After freeing memory, assign NULL to the
pointer.
2. Avoid Returning Local Variables: Use dynamically allocated memory if a
function needs to return data.
Q – what is the difference between local and global variable in C language?
Ans – A local variable in C is a variable declared inside a function or block. It can only
be accessed within the function or block where it is defined. Local variables are
created when the function is called and destroyed when the function ends. They are
used for tasks that are specific to a function.
A global variable is a variable declared outside of all functions. It can be
accessed and modified by any function in the program. Global variables exist for
the entire duration of the program and are often used to share data between
functions.
Difference Between Local and Global Variables in C
Local and global variables are two types of variables in C, differentiated by
their scope and lifetime. Below is a concise explanation in tabular form:
Aspect Local Variable Global Variable
Definition Declared inside a function or block. Declared outside all functions.
Accessible only within the function Accessible throughout the
Scope
or block where defined. entire program.
Created when the function is called Exists throughout the
Lifetime
and destroyed when it ends. program's execution.
Storage Stored in the stack memory. Stored in the data segment.
Default Garbage value (uninitialized
Zero (if uninitialized).
Value variables).
Used for temporary tasks and Used to store data shared
Usage
specific calculations. across multiple functions.
Example in C
#include <stdio.h>
// Global variable
int globalVar = 10;
void demonstrateVariables() {
// Local variable
int localVar = 5;
printf("Local Variable: %d\n", localVar); // Accessible here
printf("Global Variable: %d\n", globalVar); // Accessible here
int main() {
demonstrateVariables();
// printf("Local Variable: %d\n", localVar); // Error: not accessible here
printf("Global Variable: %d\n", globalVar); // Accessible here
return 0;
Key Points
Local variables are temporary and only accessible within the function
they are declared in.
Global variables persist for the entire program and are accessible to all
functions.
Use local variables for function-specific tasks to avoid conflicts and
improve readability, while global variables are ideal for shared data but
should be used cautiously to prevent unintended modifications.
Q – what is the difference between macros and function in C Programming
language ?
Ans – Macro : A macro in C is a piece of code that is replaced by its definition
before the program is compiled. Macros are defined using the #define keyword
in the preprocessor. They are often used to create constants or write small,
reusable code snippets.
Function : A function in C is a block of code that performs a specific task and
can be reused multiple times. It is executed when it is called in the program.
Functions allow you to pass input values (arguments) and return a result.
Difference between Macro and Function
Aspect Macro Function
A macro is a preprocessor A function is a block of code
Definition directive that replaces code that performs a specific task
before compilation. and is executed at runtime.
Declared with a return type,
Declaration Declared using #define.
name, and parameter list.
Code is replaced inline during The function is called and
Execution
preprocessing. executed at runtime.
Faster because it avoids the Slightly slower due to
Speed
overhead of a function call. function call overhead.
Type Type checking is enforced by
No type checking is performed.
Checking the compiler.
Harder to debug, as macros are Easier to debug since they
Debugging
replaced before compilation. exist as functions in the code.
Compact code; reduces
Code Inline replacement; may increase
redundancy and improves
Reusability code size if used multiple times.
readability.
Difficult to handle complex Suitable for both simple and
Complexity
operations. complex operations.
Global; no concept of local Local or global depending on
Scope
variables. the function's definition.
Example of Macro
#include <stdio.h>
#define SQUARE(x) ((x) * (x)) // Macro
int main() {
printf("Square of 5: %d\n", SQUARE(5)); // Output: 25
return 0;
}
Example of Function
#include <stdio.h>
int square(int x) { // Function
return x * x;
}
int main() {
printf("Square of 5: %d\n", square(5)); // Output: 25
return 0;
}
Q – What is a Function prototypes ? Explain with the help of example.
Ans – Function : A function in C is a block of code that performs a specific task
and can be reused multiple times. It is executed when it is called in the
program. Functions allow you to pass input values (arguments) and return a
result.
Function Prototype in C
A function prototype in C is a declaration of a function that provides
information about the function’s name, return type, and parameters before the
function is actually defined. It acts as a promise that the function will exist
with the specified characteristics, helping the compiler understand how to
handle calls to that function.
The function prototype is important because it allows the function to be called
before its actual definition in the code, making the program more flexible and
organized.
Structure of a Function Prototype
A function prototype consists of:
1. Return Type: The data type the function will return (e.g., int, float).
2. Function Name: The name of the function.
3. Parameters: The types of inputs the function accepts (e.g., int, float),
including their order.
The general format for a function prototype is:
return_type function_name(parameter_type1, parameter_type2, ...);
Example of a Function Prototype
Consider the following example where we declare and define a function:
#include <stdio.h>
// Function prototype (declaration)
int add(int, int);
int main() {
int result = add(5, 3); // Function call
printf("The sum is: %d\n", result);
return 0;
}
// Function definition
int add(int a, int b) {
return a + b;
}
Explanation of the Example:
1. Function Prototype:
o int add(int, int); is the prototype of the function add.
o It tells the compiler that there is a function named add that takes
two int parameters and returns an int.
2. Function Call:
o In the main() function, we call add(5, 3).
o The compiler knows about the function because of the prototype,
even though the actual definition of add() comes after main().
3. Function Definition:
o The function definition provides the actual implementation of the
function, where the two integers are added together and the
result is returned.
Benefits of Function Prototypes:
1. Early Function Calls: They allow functions to be called before they are
defined in the code.
2. Type Safety: Prototypes help the compiler check if the correct number
of arguments and their types are passed when the function is called.
3. Better Code Organization: They enable modular code where function
definitions can be placed later in the program or even in different files.
Q – Explain the advantages and disadvantages of pointers in C language.
Ans - Pointers in C Language
In C, a pointer is a variable that stores the memory address of another
variable. Instead of holding data directly, pointers hold the address of a
location in memory where data is stored. This allows indirect access to data and
enables powerful features like dynamic memory management and efficient
function argument passing.
Declaring a Pointer
A pointer is declared using the * symbol. The general syntax is:
data_type *pointer_name;
For example:
int *ptr; // Declares a pointer to an integer
To store the address of a variable, the address-of operator (&) is used:
int num = 10;
ptr = # // ptr now stores the address of num
Dereferencing a Pointer
To access the value stored at the memory address the pointer is pointing to,
the dereference operator (*) is used:
printf("%d", *ptr); // Prints the value stored at the address pointed by
ptr
Example
#include <stdio.h>
int main() {
int num = 5;
int *ptr; // Declare pointer
ptr = # // Store address of num in ptr
printf("Address of num: %p\n", ptr); // Print address
printf("Value of num: %d\n", *ptr); // Print value pointed by ptr
return 0;
}
Advantages of Pointers in C
1. Memory Efficiency: Pointers allow for efficient memory usage. By passing
memory addresses instead of large data structures or arrays, the
program avoids unnecessary copying of data, leading to faster execution.
2. Dynamic Memory Allocation: Pointers enable dynamic memory
management using functions like malloc() and free(). This allows programs
to allocate memory at runtime, which is particularly useful for handling
unknown or large amounts of data.
3. Function Argument Passing: Pointers enable pass-by-reference in
function calls, meaning a function can modify the actual values of
variables passed to it, without needing to return the modified values.
4. Direct Memory Access: Pointers provide direct access to memory
locations, allowing low-level operations like working with hardware, system
memory, or manipulating data structures efficiently.
5. Linked Data Structures: Pointers are essential for implementing complex
data structures like linked lists, trees, and graphs, where each element
needs to point to another.
6. Efficient Array Handling: Arrays and pointers are closely related in C.
Pointers can be used to efficiently iterate through arrays, and pointer
arithmetic allows easy manipulation of array elements.
7. Pointer Arithmetic: Pointers allow direct manipulation of memory
locations using pointer arithmetic. This is useful for accessing elements in
arrays or navigating through memory blocks.
Disadvantages of Pointers in C
1. Complexity: Pointers can be difficult for beginners to understand.
Incorrect pointer usage can lead to errors that are often hard to track
and fix, making debugging more challenging.
2. Memory Management Issues: Improper use of memory allocation and
deallocation can lead to memory leaks (unused memory) or segmentation
faults (accessing invalid memory), both of which can affect program
performance.
3. Dangling Pointers: A dangling pointer arises when a pointer references a
memory location that has been freed. Accessing this memory can cause
undefined behavior and crashes.
4. Pointer Arithmetic Errors: Incorrect pointer arithmetic can lead to
accessing memory outside the intended range, causing undefined behavior
or data corruption.
5. Security Vulnerabilities: Pointers, if misused, can introduce serious
security risks such as buffer overflows, where data overwrites adjacent
memory, potentially leading to security breaches or malicious code
execution.
6. Lack of Type Safety: Pointers do not inherently check the types of data
they point to. This can result in type mismatches, causing unexpected
behavior or crashes when the wrong data type is dereferenced.
7. Increased Code Maintenance: Programs that heavily rely on pointers can
become harder to maintain and understand, especially if pointers are used
in multiple places and their behavior is not well documented.
Q – What is an algorithm ? How to measure efficiency of an algorithm?
Ans - What is an Algorithm?
An algorithm is a step-by-step set of instructions or procedures designed to
perform a specific task or solve a particular problem. It takes an input,
processes it through a series of defined steps, and produces an output.
Algorithms are fundamental to computer science and are used to develop
software, perform calculations, sort data, and much more.
An algorithm is generally characterized by the following properties:
1. Finiteness: It must have a clear starting and ending point.
2. Definiteness: Each step must be clearly defined and unambiguous.
3. Input: It takes zero or more inputs to work on.
4. Output: It produces at least one output, which is the result of the
algorithm’s execution.
5. Effectiveness: Every step must be basic enough to be performed exactly
and in a finite amount of time.
For example, an algorithm to add two numbers is:
1. Take two numbers as input.
2. Add them together.
3. Return the result.
Measuring the Efficiency of an Algorithm
The efficiency of an algorithm refers to how well it performs in terms of the
resources it uses, mainly time and space. Two primary factors are considered
when measuring the efficiency of an algorithm:
1. Time Complexity:
o Time complexity refers to the amount of time an algorithm takes
to complete as a function of the size of the input.
o It is usually expressed using Big O notation, which describes the
worst-case scenario. For example, O(n) means the algorithm’s time
increases linearly with the input size.
o Common time complexities include:
O(1): Constant time (doesn’t depend on input size).
O(n): Linear time (time grows linearly with input size).
O(n²): Quadratic time (time grows with the square of input
size).
2. Space Complexity:
o Space complexity refers to the amount of memory an algorithm
uses as a function of the input size.
o Like time complexity, space complexity is also expressed using Big
O notation. An algorithm that uses more memory for larger inputs
has higher space complexity.
For example, consider sorting algorithms:
Bubble Sort has a time complexity of O(n²), meaning it performs poorly
with large inputs.
Quick Sort has a time complexity of O(n log n), making it more efficient
than Bubble Sort for large datasets.
Q – What is circular linked list? Explain with the help of example.
Ans - Circular Linked List
A circular linked list is a variation of a linked list in which the last node points
back to the first node instead of having a NULL reference. This creates a
circular structure where you can traverse the list starting from any node and
keep going in a loop.
In a regular linked list, the last node’s next pointer is NULL, marking the end of
the list. But in a circular linked list, the next pointer of the last node points to
the first node, which allows for continuous traversal.
There are two types of circular linked lists:
1. Singly Circular Linked List: In this list, each node points to the next
node, and the last node points back to the first node.
2. Doubly Circular Linked List: In this type, each node has two pointers:
one pointing to the next node and another pointing to the previous
node, with both the first and last nodes pointing to each other.
C Code for circular Linked List
#include <stdio.h>
#include <stdlib.h>
// Define the structure of a node
struct Node {
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = newNode; // Points to itself initially
return newNode;
// Function to print the circular linked list
void printList(struct Node* head) {
if (head == NULL) return; // If the list is empty, return
struct Node* temp = head;
do {
printf("%d -> ", temp->data);
temp = temp->next;
} while (temp != head); // Loop until we reach the head again
printf("...\n");
// Function to insert a node at the end of the circular linked list
void insertNode(struct Node** head, int value) {
struct Node* newNode = createNode(value);
if (*head == NULL) {
*head = newNode; // If the list is empty, make newNode the head
} else {
struct Node* temp = *head;
// Traverse to the last node
while (temp->next != *head) {
temp = temp->next;
}
temp->next = newNode; // Point the last node to the new node
newNode->next = *head; // Point the new node to the head,
completing the circle
int main() {
struct Node* head = NULL;
// Inserting nodes into the circular linked list
insertNode(&head, 10);
insertNode(&head, 20);
insertNode(&head, 30);
// Printing the circular linked list
printList(head);
return 0;
Q – Convert the following infix expression into its equivalent postfix expression.
Expression : A-B/C+D*E+F
Ans – DO IT YOURSELF.
Q – Define the recursion. Write a recursive and non- recursive program to
calculate the factorial of the given program.
Ans - Recursion
Recursion is a process in which a function calls itself directly or indirectly in
order to solve a problem. The key concept of recursion is breaking down a
complex problem into simpler subproblems. Each recursive call typically works
with a smaller portion of the problem, and a base case is defined to stop the
recursion when the problem becomes simple enough to solve directly. Without a
base case, recursion would continue indefinitely, leading to a stack overflow.
Recursion is commonly used in problems like tree traversal, searching, sorting,
and calculating factorials, among others. In a recursive function, there are two
main components:
1. Base Case: The condition that stops further recursive calls.
2. Recursive Case: The part where the function calls itself with modified
parameters, working towards the base case.
The most common example of recursion is calculating the factorial of a number,
defined as:
n!=n×(n−1)×(n−2)×...×1n! = n \times (n-1) \times (n-2) \times ... \times
1n!=n×(n−1)×(n−2)×...×1
where the base case is 0!=10! = 10!=1.
#include <stdio.h>
// Recursive function to calculate factorial
int factorial_recursive(int n) {
if (n == 0) // base case
return 1;
else
return n * factorial_recursive(n - 1);
}
int main() {
int n = 5;
printf("Factorial of %d is %d\n", n, factorial_recursive(n));
return 0;
#include <stdio.h>
// Non-recursive function to calculate factorial
int factorial_non_recursive(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
return result;
int main() {
int n = 5;
printf("Factorial of %d is %d\n", n, factorial_non_recursive(n));
return 0;
}
Q – What is a Threaded Binary Tree ? Explain the advantages of using a
threaded binary tree.
Ans - Threaded Binary Tree
A Threaded Binary Tree is a type of binary tree where the null pointers of the
leaf nodes (or other null pointers in the tree) are replaced with threads that
point to the next node in the tree in an in-order traversal. Essentially, a
threaded binary tree has additional "threads" that help in quickly navigating
through the tree, especially for tree traversal.
In a traditional binary tree, each node has two pointers: left and right. If there
is no child node, these pointers are set to null. However, in a threaded binary
tree, these null pointers are used to store references to the in-order
predecessor or in-order successor instead of pointing to null. This makes it
easier to traverse the tree in order without the need for recursion or using a
stack.
Types of Threaded Binary Trees
1. Single Threaded Binary Tree: In this type, only one null pointer (either
left or right) of each node is replaced by a thread.
o If the right pointer is null, it points to the in-order successor.
o If the left pointer is null, it points to the in-order predecessor.
2. Double Threaded Binary Tree: Both left and right null pointers are
replaced with threads to the in-order predecessor and successor,
respectively.
Advantages of Using a Threaded Binary Tree
1. Efficient Tree Traversal: Traditional binary trees require either
recursion or stacks for traversal. In a threaded binary tree, you can
traverse the tree in an in-order manner without requiring extra space for
a stack or recursion. The threads point to the next node in the in-order
sequence, making traversal more efficient.
2. Reduced Space Complexity: Threaded binary trees reduce the space
complexity for traversing the tree since they eliminate the need for
recursion or a stack. The threads themselves act as a navigation system.
3. Faster Traversal: In a regular binary tree, finding the next node during
an in-order traversal requires backtracking, which can be time-consuming.
Threaded binary trees eliminate this problem by directly linking the
nodes via threads, allowing faster traversal.
4. No Need for Extra Data Structures: With threaded binary trees, you
don’t need to store nodes in additional data structures like a stack or
queue for traversal, which is often necessary in traditional binary trees.
5. Simplified Algorithms: Traversing or searching for specific values in the
tree becomes easier, as the tree can be traversed without needing extra
space or complex recursive functions.
6. Efficient Searching: When performing an in-order traversal to search for
a specific value, threaded binary trees can quickly move to the next node
without needing to perform unnecessary operations. This is especially
beneficial in scenarios where searching and traversing the tree need to
be done frequently.
7.
8. Better Memory Utilization: Threaded binary trees make efficient use of
the memory allocated to each node. Instead of leaving null pointers, they
reuse the space to store references to the next node in the traversal,
reducing wasted memory.
Q – what is the difference between complete binary tree and almost complete
binary tree ? give example.
Ans - Complete Binary Tree
A Complete Binary Tree is a type of binary tree in which every level, except
possibly the last, is completely filled. Additionally, all nodes are as far left as
possible. In other words, all the nodes in the tree are filled from top to bottom,
and from left to right, without any gaps. Example :
/ \
2 3
/\ /\
4 56 7
Almost complete Binary Tree
An almost complete binary tree is a binary tree that is similar to a complete
binary tree, but the last level might not be completely filled
/ \
2 3
/\ /
4 56
Differences between complete b-tree and almost complete b-tree
Property Complete Binary Tree Almost Complete Binary Tree
Level All levels are completely filled All levels are completely filled
Filling except possibly the last. except possibly the last.
The last level is filled from left
The last level is completely
Last Level to right but may not be fully
filled.
filled.
May have empty nodes on the last
Empty No empty nodes at any level
level, but they are placed to the
Nodes except the last.
left.
Some nodes may be missing in the
Nodes in All nodes in the last level are
last level, but remaining nodes are
Last Level present.
left-aligned.
The tree has a nearly balanced
Shape of The tree has a compact,
shape, with some gaps in the last
Tree balanced shape with no gaps.
level.
Leaf All leaf nodes are positioned Leaf nodes are positioned from
Nodes as left as possible on the last left to right, but not all may be
Position level. present.
Height is slightly greater than a
Height is minimized, as all
Tree complete tree with the same
levels are filled as much as
Height number of nodes due to partially
possible.
filled last level.
Traversal is almost as efficient as
Traversal and node insertion a complete binary tree, but
Traversal
are efficient due to the filled insertion might require
Efficiency
levels. adjustments due to the last
level's emptiness.
Q – write an algorithm to in-order tree traversal of binary tree. Draw a binary
tree with following traversals:
Inorder : B C A E G D H F I J
Preorder: A B C D E G F H I J
Ans – DO IT YOURSELF.
Q – What are Asymptotic Notations? Explain all types of asymptotic notations.
Ans - Asymptotic Notations
Asymptotic notations are mathematical tools used to describe the behavior of
algorithms as the input size grows towards infinity. These notations help analyze
the efficiency of an algorithm in terms of time complexity and space
complexity. By using asymptotic notations, we can express how an algorithm
performs relative to its input size, regardless of hardware or implementation
specifics.
Types of Asymptotic Notations
1. Big O Notation (O):
o Big O notation is used to describe the upper bound of an
algorithm’s time or space complexity.
o It provides the worst-case scenario, meaning it describes the
maximum time or space the algorithm will take as the input size
grows.
o For example, if an algorithm has a time complexity of
O(n2)O(n^2)O(n2), it means that as the input size increases, the
execution time grows at most proportional to the square of the
input size.
Example: O(n2)O(n^2)O(n2) represents an algorithm that may take at most
n2n^2n2 steps.
2. Omega Notation (Ω):
o Omega notation represents the lower bound of an algorithm’s time
or space complexity.
o It describes the minimum time or space an algorithm will take in
the best-case scenario, regardless of the input size.
o For instance, Ω(n)Ω(n)Ω(n) means the algorithm will take at least
nnn steps, even in the best case.
Example: Ω(n)Ω(n)Ω(n) suggests that the algorithm takes at least linear time.
3. Theta Notation (Θ):
o Theta notation describes the tight bound of an algorithm’s time or
space complexity.
o It gives both the upper and lower bounds, meaning it provides an
exact asymptotic behavior.
o If an algorithm has Θ(n)Θ(n)Θ(n), it means the algorithm’s time
complexity grows linearly with the input size in both the best and
worst cases.
Example: Θ(nlogn)Θ(n \log n)Θ(nlogn) means that the algorithm’s performance
is both bounded above and below by nlognn \log nnlogn.
4. Little o Notation (o):
o Little o notation provides an upper bound that is not tight.
o It means that the algorithm’s time complexity grows slower than a
given function for large inputs.
o If f(n)=o(g(n))f(n) = o(g(n))f(n)=o(g(n)), it means f(n)f(n)f(n) grows
strictly slower than g(n)g(n)g(n).
Example: o(n2)o(n^2)o(n2) means the algorithm’s time complexity grows slower
than n2n^2n2 and never equals n2n^2n2.
5. Little ω Notation (ω):
o Little omega notation is the opposite of little o. It describes an
lower bound that is not tight.
o It means that the algorithm’s time complexity grows faster than a
given function for large inputs.
o If f(n)=ω(g(n))f(n) = ω(g(n))f(n)=ω(g(n)), it means f(n)f(n)f(n)
grows strictly faster than g(n)g(n)g(n).
Example: ω(n)ω(n)ω(n) means the algorithm’s time complexity grows faster than
linear time.
Q – Write a note on Dequeue.
Ans - Dequeue (Double-Ended Queue)
A Dequeue (or Double-Ended Queue) is a linear data structure that allows
elements to be added or removed from both ends—front and rear—of the
queue. Unlike a regular queue, which follows the FIFO (First-In-First-Out)
principle where elements are added at the rear and removed from the front, a
dequeue allows more flexibility by enabling insertions and deletions at both
ends.
Operations on Dequeue
1. Insert Front: Adds an element at the front of the dequeue.
2. Insert Rear: Adds an element at the rear of the dequeue.
3. Delete Front: Removes an element from the front of the dequeue.
4. Delete Rear: Removes an element from the rear of the dequeue.
5. Front: Returns the element at the front without removing it.
6. Rear: Returns the element at the rear without removing it.
7. Is Empty: Checks if the dequeue is empty.
8. Is Full: Checks if the dequeue is full (if it has a fixed size).
Types of Dequeues
Input-restricted Dequeue: Elements can only be inserted at one end, but
can be removed from both ends.
Output-restricted Dequeue: Elements can only be removed from one end,
but can be inserted at both ends.
Example of Dequeue (Optional)
#include <stdio.h>
#include <stdlib.h>
#define MAX 5
// Structure for Dequeue
struct Deque {
int arr[MAX];
int front;
int rear;
};
// Initialize the dequeue
void initDeque(struct Deque* dq) {
dq->front = -1;
dq->rear = -1;
// Check if dequeue is empty
int isEmpty(struct Deque* dq) {
return dq->front == -1;
// Check if dequeue is full
int isFull(struct Deque* dq) {
return dq->rear == MAX - 1;
// Insert element at the front
void insertFront(struct Deque* dq, int value) {
if (isFull(dq)) {
printf("Deque is full!\n");
return;
if (dq->front == -1) { // First element
dq->front = dq->rear = 0;
} else {
dq->front--;
}
dq->arr[dq->front] = value;
// Insert element at the rear
void insertRear(struct Deque* dq, int value) {
if (isFull(dq)) {
printf("Deque is full!\n");
return;
if (dq->front == -1) { // First element
dq->front = dq->rear = 0;
} else {
dq->rear++;
dq->arr[dq->rear] = value;
// Delete element from the front
void deleteFront(struct Deque* dq) {
if (isEmpty(dq)) {
printf("Deque is empty!\n");
return;
if (dq->front == dq->rear) { // Only one element
dq->front = dq->rear = -1;
} else {
dq->front++;
// Delete element from the rear
void deleteRear(struct Deque* dq) {
if (isEmpty(dq)) {
printf("Deque is empty!\n");
return;
if (dq->front == dq->rear) { // Only one element
dq->front = dq->rear = -1;
} else {
dq->rear--;
// Display elements of the dequeue
void display(struct Deque* dq) {
if (isEmpty(dq)) {
printf("Deque is empty!\n");
return;
for (int i = dq->front; i <= dq->rear; i++) {
printf("%d ", dq->arr[i]);
printf("\n");
}
int main() {
struct Deque dq;
initDeque(&dq);
insertRear(&dq, 10);
insertFront(&dq, 20);
insertRear(&dq, 30);
insertFront(&dq, 40);
printf("Deque elements: ");
display(&dq);
deleteFront(&dq);
deleteRear(&dq);
printf("Deque elements after deletions: ");
display(&dq);
return 0;