DSA All Units
DSA All Units
Components:
1. Array of Data: An array where each element stores the data (just like a regular
linked list node).
2. Array of Links (Next): Another array that stores the index of the "next"
element in the array, simulating the pointer in a linked list.
3. Head Index: A variable that keeps track of the index of the first element of the
list (the "head" node).
Example:
Let’s say we have a linked list: 10 -> 20 -> 30 -> null .
Here’s how it would be represented using arrays:
1. Data Array:
2.Next Array:
Next: [1, 2, -1, ...]
Index: 0 1 2
The Next[0] = 1 means the next element after index 0 (data = 10) is at index 1
(data = 20).
The Next[1] = 2 means the next element after index 1 (data = 20) is at index 2
(data = 30).
The Next[2] = -1 means this is the last node (end of the linked list).
3. Head:
The head of the linked list is stored as an index in the array. In this case, the
head = 0, which refers to the element 10 .
Operations:
1. Traversal:
Start from the head index (0) and follow the indices in the Next array.
Example: Start at index 0 (data = 10), move to Next[0] = 1 (data = 20), then
move to Next[1] = 2 (data = 30), and finally, Next[2] = -1 (end of the list).
2. Insertion:
Insert a new element by finding an empty slot in the Data array, update the
Next array accordingly, and adjust the links to maintain the structure.
3. Deletion:
Advantages:
This approach can be useful in memory-constrained environments where
memory allocation is static, and you know the maximum number of nodes
Disadvantages:
Unlike a traditional linked list, this implementation requires a fixed-size
array, limiting flexibility.
Steps:
1. Create a node structure: Each node has two parts—data and a pointer to
the next node.
2. Check for empty list: If the list is empty and the location is 1, simply insert
the node.
3. Traverse to the desired position: Navigate the list until you reach the
position just before the desired location.
4. Insert the node: Update the pointers to insert the new node at the desired
location.
#include <iostream>
using namespace std;
return 0;
}
Explanation:
insertAtPosition(10, 1): Inserts 10 at position 1 (head).
Output:-
10 -> 15 -> 20 -> NULL
This structure can be useful in situations where you need continuous traversal
of the list without having to return to the head explicitly.
Key Characteristics:
1. Circular nature: In a circular linked list, the last node’s next pointer is not
nullptr (as in a singly linked list), but instead points back to the first node.
2. Traversal: Since there is no nullptr at the end, traversal must stop when you
revisit the head node.
Singly circular: Each node has a pointer to the next node, with the last node
pointing back to the head.
Example:-
#include <iostream>
using namespace std;
struct Node {
int data;
Node* next;
};
void insert(Node*& head, int data) {
Node* newNode = new Node();
newNode->data = data;
if (head == nullptr) {
head = newNode;
newNode->next = head; // Point to itself to form a circl
e
} else {
Node* temp = head;
while (temp->next != head) {
temp = temp->next;
}
temp->next = newNode;
newNode->next = head; // Last node points back to the he
ad
}
int main() {
Node* head = nullptr;
printList(head);
return 0;
Explanation:
1. Node Structure: Each node has two parts:
2. insert Function:
If the list is empty, the new node points to itself, forming a circular
structure.
If the list already has elements, we traverse to the last node, insert the new
node, and make the new node’s next point to the head.
3. printList Function:
We use a do-while loop to ensure that even the head is printed, and the
loop terminates when we reach the head again.
Output:-
To add an element at the beginning of a doubly linked list, follow this algorithm:
Allocate memory for a new node and set its data field to the new element.
Set the next pointer of the new node to point to the current head of the
list.
Set the prev pointer of the new node to nullptr because it will be the new
head.
Set the prev pointer of the current head to the new node.
#include <iostream>
using namespace std;
// Node structure for doubly linked list
struct Node {
int data;
Node* next;
Node* prev;
};
// Function to add a node at the beginning of the doubly linked list
void addAtBeginning(Node*& head, int data) {
// Create a new node
Node* newNode = new Node();
newNode->data = data;
newNode->next = head; // New node points to the current head
newNode->prev = nullptr; // New node will be the head, so no previous node
}
// Function to print the list from head to tail
void printList(Node* head) {
Node* temp = head;
int main() {
Node* head = nullptr;
return 0;
Explanation of Code:
1. Node Structure:
2. addAtBeginning Function:
The next pointer of the new node is set to the current head, and prev is
set to nullptr .
If the list is not empty, the current head's prev pointer is updated to point
to the new node.
3. printList Function:
Traverses the list from the head to the end, printing each node's data.
Output Example:
30 -> 20 -> 10 -> NULL
A linked list is a linear data structure in which elements, known as nodes, are
linked together using pointers. Each node contains:
Unlike arrays, where elements are stored in contiguous memory locations, the
nodes in a linked list can be scattered across different memory locations. This
allows linked lists to be dynamic, meaning their size can grow or shrink as needed,
making them more flexible for certain operations (like inserting or deleting
elements).
2. Doubly Linked List: Each node has two pointers, one pointing to the next node
and one pointing to the previous node.
3. Circular Linked List: The last node points back to the first node, forming a
loop.
Each node occupies a non-contiguous memory block, and the nodes are
linked through pointers.
Next pointer: Holds the memory address of the next node (or nullptr for
the last node).
rust
Copy code
[Node 1] ---> [Node 2] ---> [Node 3] ---> [nullptr]
| Data | | Data | | Data |
| Next | ----> | Next | ----> | Next |
Data: 10
Data: 20
3. Node 3:
Data: 30
Visualization:
Address Data Next
0x1000 10 0x2000
0x2000 20 0x3000
0x3000 30 nullptr
Memory Pointers:
Head pointer: The linked list typically has a pointer called head which stores
the address of the first node (Node 1, i.e., 0x1000 in this case).
Each node’s next pointer stores the memory address of the next node.
Start at head .
Follow the next pointer of each node to the next node until nullptr is reached.
Dynamic Nature:
1. Insertion:
New nodes can be inserted anywhere in the list by adjusting the pointers
without shifting data like in arrays.
For example, inserting a new node between Node 2 and Node 3 only
requires updating the next pointers of Node 2 and the new node.
Removing a node only involves updating the pointer of the preceding node
to skip the removed node.
For example, removing Node 2 involves updating the next pointer of Node
1 to point to Node 3.
cpp
Copy code
#include <iostream>using namespace std;
// Node structure
struct Node {
int data;
Node* next;
};
if (head == nullptr) {
head = newNode; // If the list is empty, the new nod
e is the head
} else {
Node* temp = head;
while (temp->next != nullptr) {
temp = temp->next;
}
temp->next = newNode;
}
}
int main() {
Node* head = nullptr;
return 0;
}
Each node is stored at a different memory address and linked via the next
pointers.
In this example, nodes are added dynamically as needed, illustrating the flexibility
of linked lists over arrays in terms of memory usage.
Steps:
1. Create a new node:
Make the next pointer of the new node point to the current head (the first
node in the list).
Set the head pointer to point to the new node (since it is now the first node
in the list).
4. End:
The new node is now the first node in the list, and the old list follows it.
Algorithm:
4. End
#include <iostream>
using namespace std;
int main() {
Node* head = nullptr;
return 0;
}
2. insertAtFront Function:
3. printList Function:
This function traverses the list from the head, printing each node's data
until it reaches the end ( nullptr ).
Output:
This algorithm efficiently inserts a node at the front of a singly linked list.
Unit-2
Que 1). Explain Quick short with suitable example
"Quick sort" is a highly efficient sorting algorithm based on the divide and
conquer approach.
It works by selecting a "pivot" element from the array, partitioning the other
elements into two sub-arrays (those less than the pivot and those greater),
and then recursively sorting the sub-arrays.
2. Partitioning: Rearrange the array so that all elements less than the pivot are
on the left side, and all elements greater than the pivot are on the right side.
Example:
Let’s say we have the array: [8, 3, 1, 7, 0, 10, 2] .
Pivot: 7
3. Recursion:
#include <iostream>
using namespace std;
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // Pivot element
int i = low - 1; // Index of smaller element
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
Explanation:
1. swap(): A utility function to swap two elements.
2. partition(): It selects the pivot (in this case, the last element of the array) and
places it in its correct position in the sorted array. All smaller elements are
3. quickSort(): Recursively sorts the elements before and after the partition.
Sample Output:
Original array: 8 3 1 7 0 10 2
Sorted array: 0 1 2 3 7 8 10
Time Complexity:
Average case: O(n log n)
Worst case: O(n²), happens when the pivot is always the smallest or largest
element.
What is a Stack?
A stack is a linear data structure that follows the Last In First Out (LIFO) principle,
meaning the last element added to the stack will be the first to be removed. You
can visualize it like a stack of plates where you can only add or remove a plate
from the top.
Steps:
1. Step 1: Check if the stack is full. If the stack is full, output "Stack Overflow"
and terminate the operation.
2. Step 2: If the stack is not full, increment the top pointer to point to the next
available space in the stack.
4. Step 4: End.
Explanation:
top: Keeps track of the index of the top element in the stack.
Otherwise, we increment top and add the new element to that position in the
stack.
C++ Code:
#include <iostream>
#define MAX 5 // Defining the maximum size of the stack
class Stack {
int top;
int stack[MAX]; // Array to store the stack elements
public:
// Constructor to initialize top
Stack() {
top = -1; // Stack is empty initially
}
int main() {
Stack s;
s.push(10);
s.push(20);
s.push(30);
s.push(40);
s.push(50);
return 0;
}
Explanation:
The Stack class contains:
An integer top to keep track of the index of the top element (initialized to
1 to signify an empty stack).
The main() function demonstrates inserting elements into the stack and
checking for stack overflow when attempting to push more than the allowed
maximum.
Output:
Let me know if you have any further questions or need additional functionalities!
Que 3). Write an algorithm for translating the infix expression into
postfix notation
B + ), we use a stack to store operators while scanning the infix expression from
left to right. The key idea is to output operands as they arrive and push operators
onto the stack, respecting operator precedence and parentheses.
Steps:
1. Initialize an empty stack for storing operators and an empty string for storing
the output postfix expression.
If the character is a closing parenthesis ) , pop from the stack and add
operators to the postfix expression until an open parenthesis ( is
encountered. Discard the open parenthesis.
1. Pop operators from the stack and add them to the postfix expression
while the precedence of the operator on top of the stack is greater
than or equal to the current operator.
4. After scanning the entire infix expression, pop all the operators remaining in
the stack and add them to the postfix expression.
5. End.
Algorithm InfixToPostfix(expression):
1. Initialize an empty stack (let's call it operatorStack)
2. Initialize an empty string (let's call it postfix)
5. Return postfix
What is a Stack?
A stack is a linear data structure that follows the Last In First Out (LIFO) principle,
meaning the last element added to the stack will be the first to be removed. Think
of it like a stack of books where you can only place or remove books from the top.
2. Fixed Size: In some implementations, stacks can have a fixed size, though
dynamic stacks are also possible.
Stack Operations:
1. Push Operation:
Definition: The push operation adds an element to the top of the stack.
Steps:
2. If the stack is not full, increment the top pointer and insert the new
element at the position pointed by the top.
Example: If a stack has elements [10, 20] and we push 30 , the stack
becomes [10, 20, 30] , with 30 being the top element.
2. Pop Operation:
Definition: The pop operation removes the element from the top of the
stack.
Steps:
2. If the stack is not empty, remove the element at the position pointed by
the top and decrement the top pointer.
Example: If a stack has elements [10, 20, 30] and we pop an element, the
top element 30 is removed, and the stack becomes [10, 20] .
Stack Representation
Let’s take a simple example:
Push Operations:
Pop Operations:
5!=5×4×3×2×1=120
By definition, 0!=1
A recursive algorithm breaks the problem into smaller subproblems, solving them
one by one. In the case of factorial:
Algorithm Factorial(n):
If n == 0:
Return 1 // Base case: 0! = 1
Else:
Return n * Factorial(n - 1) // Recursive case: n * (n-1)!
End Algorithm
Explanation:
If n=0, the function returns 1 as the factorial of 0 is defined as 1.
n=0n = 0
Example:
5!=5×4×3×2×1=120
Problem Description:
There are three rods (A, B, and C) and n disks of different sizes stacked on rod A,
with the largest disk at the bottom and the smallest at the top. The goal is to move
all the disks from rod A (source) to rod C (destination) using rod B (auxiliary) as an
intermediate rod.
Rules:
1. Only one disk can be moved at a time.
2. A disk can only be placed on top of another disk if the disk below is larger.
3. All disks must be moved to the destination rod while following the above two
rules.
Steps to Solve:
Move disk 1 from A to C (so you free up the smallest disk first).
Move disk 2 from A to B (so you can later move disk 3).
Move disk 3 from A to C (the largest disk goes to the destination rod).
Move disk 1 from B to A (temporarily place disk 1 on rod A to free up rod B).
Move disk 1 from A to C (finally, move the smallest disk on top of disk 2 on rod
C).
Recursive Solution:
The recursive nature of the problem involves breaking it down into smaller
subproblems. For \( n \) disks:
1. Move the top \( n - 1 \) disks from rod A to rod B using rod C as auxiliary.
Pseudocode:
Rod B: []
Rod C: []
Rod A: [3, 2]
Rod B: []
Rod A: [3]
Rod B: [2]
Rod C: [1]
Rod A: [3]
Rod B: [2, 1]
Rod C: []
Rod A: []
Rod B: [2, 1]
Rod C: [3]
Rod A: [1]
Rod B: [2]
Rod C: [3]
Rod A: [1]
Rod B: []
Rod C: [3, 2]
Rod A: []
Rod B: []
Rod C: [3, 2, 1]
#include <iostream>
using namespace std;
int main() {
int n = 3; // Number of disks
towerOfHanoi(n, 'A', 'C', 'B'); // A = source, C = desti
nation, B = auxiliary
return 0;
}
Output:
Conclusion:
The Tower of Hanoi problem is a great example of recursion. For 3 disks, it takes 7
steps to move all disks from the source rod to the destination rod. The number of
moves for \( n \) disks can be calculated as \( 2^n - 1 \).
Unit 3
Que 1) Write an algorithm for instertion sort method in c++
Sure, here's an algorithm for the Insertion Sort method in C++:
Compare the key with the elements in the sorted part of the array (i.e., the
elements before the current index).
Shift all the elements that are greater than the key one position to the right.
Place the key at its correct position in the sorted part of the array.
4. Repeat:
#include <iostream>
using namespace std;
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
cout << "Original array: ";
printArray(arr, n);
insertionSort(arr, n);
cout << "Sorted array: ";
printArray(arr, n);
return 0;
}
Explanation
Outer Loop: The loop starting from i = 1 to i < n iterates through the array
elements, treating each element as the key.
Inner Loop: The inner while loop compares the key with the sorted part of the
array (elements before the current index). If an element is greater than the key,
it shifts the element one position to the right.
Insertion: After finding the correct position for the key, it inserts the key at that
position.
This algorithm sorts the array in ascending order using the insertion sort method.
Characteristics of a Queue
1. FIFO Order: Elements are processed in the order they were added.
Basic Operations
1. Enqueue: Adds an element to the end of the queue.
2. Dequeue: Removes and returns the element at the front of the queue.
3. Peek/Front: Returns the element at the front of the queue without removing it.
Types of Queues
1. Simple Queue (Linear Queue): Basic FIFO queue where elements are
enqueued at the rear and dequeued from the front.
2. Circular Queue: The last position is connected back to the first position to
make a circle, improving the use of storage by reusing vacated spaces.
3. Priority Queue: Each element is associated with a priority, and elements are
dequeued based on their priority rather than their order in the queue.
Definitions
queue : array to store the queue elements
Initialization
Initially, set front = -1 and rear = -1 .
Algorithm
1. Check for Full Queue:
3. Normal Insertion:
#include <iostream>
#define SIZE 5 // Define the maximum size of the queue
class CircularQueue {
private:
int queue[SIZE], front, rear;
public:
CircularQueue() : front(-1), rear(-1) {}
void enqueue(int element) {
if ((rear + 1) % SIZE == front) {
std::cout << "Queue Overflow" << std::endl;
return;
}
if (front == -1) front = 0;
rear = (rear + 1) % SIZE;
queue[rear] = element;
std::cout << "Inserted " << element << std::endl;
}
void display() {
if (front == -1) {
std::cout << "Queue is empty" << std::endl;
return;
}
std::cout << "Queue elements are: ";
for (int i = front; ; i = (i + 1) % SIZE) {
std::cout << queue[i] << " ";
if (i == rear) break;
Explanation
1. Initialization:
A queue of fixed size SIZE is declared with front and rear initialized to 1 .
2. Enqueue Operation:
The enqueue function first checks if the queue is full by checking if the next
position of rear (modulo SIZE ) is equal to front .
and rear to 0 .
Otherwise, it increments rear modulo SIZE and inserts the element at the
new rear position.
3. Display Function:
The display function prints the elements from front to rear considering
the circular nature of the queue.
2. Find the smallest element in the array from index i to the end.
4. Move to the next element (i.e., i = i + 1 ) and repeat the process until the
entire array is sorted.
Example
Let's sort the following array in ascending order using selection sort:
\[ 64, 25, 12, 22, 11 \]
Step-by-step process:
2. First pass:
Find the minimum element from index 0 to 4 (i.e., the whole array). The
minimum element is 11.
3. Second pass:
Find the minimum element from index 1 to 4. The minimum element is 12.
Find the minimum element from index 2 to 4. The minimum element is 22.
5. Fourth pass:
Find the minimum element from index 3 to 4. The minimum element is 25.
6. Fifth pass:
Pseudocode
Here's the pseudocode for selection sort:
for i = 0 to n-1
min_index = i
for j = i+1 to n-1
if array[j] < array[min_index]
min_index = j
swap array[i] with array[min_index]
Time Complexity
Best-case time complexity: O(n^2)
Space Complexity
Conclusion
Selection sort is easy to understand and implement but is inefficient on large lists
compared to more advanced algorithms such as quicksort, mergesort, or
heapsort. It's mainly useful for small datasets or educational purposes to illustrate
the concept of sorting.
What is Merging?
Merging is a process of combining two sorted lists into a single sorted list. It is a
fundamental operation used in various sorting algorithms and is essential for
algorithms like MergeSort. The concept of merging ensures that the final
combined list maintains the order of elements from both initial lists.
MergeSort Algorithm
MergeSort is a divide-and-conquer algorithm that divides the input array into two
halves, recursively sorts both halves, and then merges the sorted halves to
produce the sorted array. The key steps in MergeSort are:
3. Combine: Merge the two sorted halves into a single sorted array.
1. Splitting the Array: Divide the array into two halves until each sub-array
contains a single element.
#include <iostream>
using namespace std;
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int leftArr[n1], rightArr[n2];
for (int i = 0; i < n1; i++)
leftArr[i] = arr[left + i];
for (int j = 0; j < n2; j++)
rightArr[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j])
arr[k++] = leftArr[i++];
else
arr[k++] = rightArr[j++];
}
while (i < n1) arr[k++] = leftArr[i++];
while (j < n2) arr[k++] = rightArr[j++];
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++)
cout << arr[i] << " ";
cout << endl;
}
int main() {
Explanation
1. mergeSort Function:
Recursively divides the array into two halves until each sub-array contains
a single element.
2. merge Function:
Utilizes temporary arrays to hold the elements of the sub-arrays and then
merges them back into the original array.
3. printArray Function:
Output
Given array: 12 11 13 5 6 7
Sorted array: 5 6 7 11 12 13
The above output demonstrates the sorted array after applying the MergeSort
algorithm.
Key Characteristics:
Priority: Each element has a priority. The queue processes elements in order
of priority.
Example in C++
Here’s a simple example of a priority queue implementation in C++ using the
Standard Template Library (STL):
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() {
priority_queue<int> pq;
pq.push(10);
pq.push(30);
pq.push(20);
pq.push(5);
cout << "Elements in the priority queue (highest to lowes
t):" << endl;
while (!pq.empty()) {
cout << pq.top() << " "; // Get the element with the
highest priority
pq.pop(); // Remove the element from
the queue
}
cout << endl;
4. Accessing Elements: The element with the highest priority can be accessed
using pq.top() .
5. Removing Elements: The pq.pop() function removes the element with the
highest priority from the queue.
Output
When you run this program, the output will be:
Unit 4
Que 1)What is binary Search tree?Explain with suitable.
A Binary Search Tree (BST) is a type of data structure that maintains a sorted
order of elements. Each node in a BST contains a key, and for any given node:
The left subtree contains keys less than the node's key.
The right subtree contains keys greater than the node's key.
This property allows for efficient searching, insertion, and deletion operations.
2. Ordered: The left child is less than the parent node, and the right child is
greater than the parent node.
#include <iostream>
using namespace std;
public:
BinarySearchTree() : root(nullptr) {}
int main() {
BinarySearchTree bst;
bst.insert(50);
bst.insert(30);
bst.insert(70);
return 0;
}
Insert Method: Recursively adds a new key to the tree while maintaining
the BST properties.
3. Main Function:
Creates a BST, inserts a few values, and searches for a specific key.
Output:
Running this code will output:
Key 30 found in the BST.
Types of Graphs
1. Directed Graph (Digraph): The edges have a direction, going from one vertex
to another (e.g., A → B).
3. Weighted Graph: Each edge has a weight or cost associated with it.
5. Cyclic Graph: Contains at least one cycle (a path that starts and ends at the
same vertex).
1. Adjacency Matrix
An adjacency matrix is a 2D array used to represent a graph. The rows and
columns of the matrix represent the vertices of the graph. If there is an edge
between two vertices, the corresponding cell in the matrix is set to a value
(typically 1 for unweighted graphs) indicating the presence of an edge. For
weighted graphs, the cell can hold the weight of the edge.
Example:
Consider a simple undirected graph with 4 vertices (A, B, C, D) and the following
edges: A-B, A-C, B-D.
Vertices: A, B, C, D
A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 0
D 0 1 0 0
Explanation:
The row for vertex A has 1 s in the columns for B and C, indicating edges
A-B and A-C.
The row for vertex B has a 1 in the column for A and D, indicating edges
B-A and B-D.
2. Adjacency List
An adjacency list is another common representation, especially for sparse graphs.
In this representation, each vertex maintains a list of adjacent vertices. This can
be implemented using an array of linked lists or dynamic arrays.
Example:
Using the same graph as above, the adjacency list representation would look like
this:
A: B, C
B: A, D
C: A
D: B
Summary
Graphs are versatile data structures that can be represented in memory using
various methods. The adjacency matrix is a straightforward way to represent
graphs in a 2D array, while the adjacency list provides a more space-efficient
representation for sparse graphs. Understanding these representations is
fundamental for implementing graph algorithms and solving graph-related
problems.
Use Cases: It’s useful for scenarios like finding connected components,
topological sorting, solving puzzles with a single solution (like mazes), etc.
C++ Implementation
Here’s a simple implementation of DFS for a graph represented using an
adjacency list.
#include <iostream>
#include <vector>
using namespace std;
class Graph {
int vertices; // Number of vertices
vector<vector<int>> adj; // Adjacency list
public:
Graph(int v) : vertices(v), adj(v) {}
void addEdge(int u, int v) {
adj[u].push_back(v); // Add v to u's list
}
void DFSUtil(int v, vector<bool>& visited) {
visited[v] = true; // Mark the current node as visite
d
cout << v << " "; // Process the node
for (int adjNode : adj[v]) {
if (!visited[adjNode]) {
DFSUtil(adjNode, visited); // Visit unvisited
adjacent nodes
Explanation:
The Graph class contains a constructor to initialize the graph and methods to
add edges and perform DFS.
The DFSUtil function is a recursive helper function that marks nodes as visited
and prints them.
The DFS function initializes the visited vector and calls the DFSUtil function to
start the traversal.
In the main function, a graph is created, edges are added, and the DFS
traversal is initiated from vertex 0 .
Notes:
The order of nodes visited may vary based on the order in which edges are
added and how the adjacency list is constructed.
The recursive version of DFS can also be implemented using a similar logic
with a function call stack instead of an explicit stack. Let me know if you want
to see that implementation as well!
Recursive Approach
1. Start at the root node.
Recursive Approach
#include <iostream>
struct TreeNode {
int val;
TreeNode* left;
int main() {
// Creating a simple binary tree
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
return 0;
}
Iterative Approach
#include <iostream>
#include <stack>
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
int main() {
// Creating a simple binary tree
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->left->right = new TreeNode(5);
return 0;
}
Explanation
1. Recursive Approach:
If the node is not null, it first recursively calls itself for the left child, then
prints the node value, and finally calls itself for the right child.
2. Iterative Approach:
The outer loop continues until there are no nodes left to visit.
The inner loop traverses to the leftmost node, pushing nodes onto the
stack.
Both approaches will yield the same in-order traversal output. You can choose
either based on your preference or specific use case.