0% found this document useful (0 votes)
1 views

Data Structures Revision Notes-pt3

The document provides detailed notes on data structures, specifically focusing on stacks and queues implemented using linked lists, as well as reversing a linked list. It explains the principles, implementation, advantages, and basic operations of these data structures, including code examples in C. Additionally, it includes a Q&A section addressing common questions related to each topic.

Uploaded by

thangenabil44
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views

Data Structures Revision Notes-pt3

The document provides detailed notes on data structures, specifically focusing on stacks and queues implemented using linked lists, as well as reversing a linked list. It explains the principles, implementation, advantages, and basic operations of these data structures, including code examples in C. Additionally, it includes a Q&A section addressing common questions related to each topic.

Uploaded by

thangenabil44
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 29

Data Structures - Final Revision Notes

Stack using Linked List

What is Stack using Linked List?


A stack implementation that uses linked list instead of arrays
Follows LIFO (Last-In-First-Out) principle

Dynamic size - no overflow problem like array-based stacks


Each node has data and a pointer to the next node

Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

// Global variable - pointer to the top node


struct Node* top = NULL;

// Function to check if stack is empty


int isEmpty() {
if (top == NULL) { // If top is NULL
return 1; // Return true (stack is empty)
} else {
return 0; // Return false (stack is not empty)
}
}

// Function to push an element onto the stack


void push(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memory
if (newNode == NULL) { // If memory allocation failed
printf("Stack Overflow\n");
return;
}
newNode->data = data; // Set the data
newNode->next = top; // Link newNode to current top
top = newNode; // Update top to newNode
printf("%d pushed to stack\n", data);
}

// Function to pop an element from the stack


int pop() {
if (isEmpty()) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return -1; // Return error value
} else {
struct Node* temp = top; // Store current top
int data = temp->data; // Get the data
top = top->next; // Update top to next node
free(temp); // Free the popped node
return data; // Return the data
}
}

// Function to peek at the top element without removing it


int peek() {
if (isEmpty()) { // Check if stack is empty
printf("Stack is empty\n"); // Print error message
return -1; // Return error value
} else {
return top->data; // Return the top data
}
}

// Function to display the stack


void display() {
if (isEmpty()) { // Check if stack is empty
printf("Stack is empty\n"); // Print error message
return;
}
struct Node* temp = top; // Start from top
printf("Stack: ");
// Traverse and print each node
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

How Stack using Linked List Works:


1. Push Operation:
Create a new node

Set its data to the value being pushed


Point its next pointer to current top

Update top to point to this new node

Show Image

2. Pop Operation:
Check if stack is empty (top == NULL)

If not empty, store top node in temp


Update top to point to next node
Free the temp node

Return the popped value

Show Image

3. Peek Operation:
Check if stack is empty (top == NULL)
If not empty, return data of top node without removing it

Advantages of Linked List Implementation over Arrays:


Dynamic size - no predefined limit

No overflow problem (unless memory is exhausted)


Better memory utilization - memory allocated as needed

Q&A Section - Stack using Linked List


1. Q: What is a stack using linked list? A: A stack implemented using a linked list data structure where
each element contains data and a pointer to the element beneath it. The top element is accessed with a
'top' pointer.

2. Q: What are the advantages of implementing a stack using linked list over arrays? A: Dynamic
size (no fixed capacity), no overflow condition, better memory utilization, and operations remain O(1)
time complexity.

3. Q: What are the basic operations of a stack using linked list? A: Push (insert at top), Pop (remove
from top), Peek/Top (view top element without removing), and IsEmpty (check if stack is empty).

4. Q: What is the time complexity of Push and Pop operations in a stack implemented using
linked list? A: Both operations have O(1) time complexity as they only involve manipulating the top
pointer.
5. Q: How is memory allocated for a stack implemented using linked list? A: Memory is
dynamically allocated for each node as elements are pushed onto the stack, using malloc() or similar
functions.

6. Q: What happens when Pop is called on an empty stack? A: It results in a "Stack Underflow"
condition, which should be handled by returning an error value or message.

7. Q: Where is the top element located in a stack implemented using linked list? A: The top
element is the node pointed to by the 'top' pointer, which is the head of the linked list.
8. Q: Can a stack implemented using linked list ever be full? A: Theoretically no, unless the system
runs out of memory for allocating new nodes.

Queue using Linked List

What is Queue using Linked List?


A queue implementation that uses linked list instead of arrays

Follows FIFO (First-In-First-Out) principle

Dynamic size - grows and shrinks as needed

Uses front and rear pointers to track both ends

Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

// Global variables - pointers to the front and rear nodes


struct Node* front = NULL;
struct Node* rear = NULL;

// Function to check if queue is empty


int isEmpty() {
if (front == NULL) { // If front is NULL
return 1; // Return true (queue is empty)
} else {
return 0; // Return false (queue is not empty)
}
}

// Function to enqueue an element


void enqueue(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memory
if (newNode == NULL) { // If memory allocation failed
printf("Queue Overflow\n");
return;
}
newNode->data = data; // Set the data
newNode->next = NULL; // Set next pointer to NULL

if (rear == NULL) { // If queue was empty


front = rear = newNode; // Set front and rear to newNode
} else {
rear->next = newNode; // Link rear to newNode
rear = newNode; // Update rear to newNode
}
printf("%d enqueued to queue\n", data);
}

// Function to dequeue an element


int dequeue() {
if (isEmpty()) { // Check if queue is empty
printf("Queue Underflow\n"); // Print error message
return -1; // Return error value
} else {
struct Node* temp = front; // Store current front
int data = temp->data; // Get the data
front = front->next; // Update front to next node

if (front == NULL) { // If the last node was dequeued


rear = NULL; // Update rear to NULL
}

free(temp); // Free the dequeued node


return data; // Return the data
}
}

// Function to get the front element without removing it


int getFront() {
if (isEmpty()) { // Check if queue is empty
printf("Queue is empty\n"); // Print error message
return -1; // Return error value
} else {
return front->data; // Return the front data
}
}

// Function to display the queue


void display() {
if (isEmpty()) { // Check if queue is empty
printf("Queue is empty\n"); // Print error message
return;
}
struct Node* temp = front; // Start from front
printf("Queue: ");
// Traverse and print each node
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

How Queue using Linked List Works:


1. Enqueue Operation:
Create a new node
Set its data to the value being enqueued

If queue is empty, set both front and rear to this new node
Otherwise, set rear's next to point to new node and update rear

Show Image

2. Dequeue Operation:
Check if queue is empty (front == NULL)
If not empty, store front node in temp

Update front to point to next node


If front becomes NULL, set rear to NULL as well
Free the temp node

Return the dequeued value

Show Image

Advantages of Queue using Linked List:


Dynamic size - no predefined limit

No overflow condition (unless memory is exhausted)


No need to implement as circular structure like in arrays
Efficient memory utilization

Q&A Section - Queue using Linked List


1. Q: What is a queue using linked list? A: A queue implemented using a linked list data structure
where elements are added at the rear and removed from the front, maintaining FIFO (First-In-First-Out)
order.
2. Q: What are the main pointers used in a queue implemented using linked list? A: Front pointer
(points to the first element for dequeue operations) and Rear pointer (points to the last element for
enqueue operations).
3. Q: What happens if we dequeue from an empty queue? A: It results in a "Queue Underflow"
condition, which should be handled by returning an error value or message.

4. Q: What is the time complexity of enqueue and dequeue operations in a queue implemented
using linked list? A: Both operations have O(1) time complexity as they only involve manipulating the
front and rear pointers.
5. Q: How does queue using linked list overcome the limitations of array-based queue
implementation? A: It eliminates the need for circular implementation, has no fixed size limitation,
and utilizes memory efficiently.

6. Q: What happens to front and rear pointers when the last element is dequeued? A: Both front
and rear pointers are set to NULL, indicating an empty queue.
7. Q: Can a queue implemented using linked list ever be full? A: Theoretically no, unless the system
runs out of memory for allocating new nodes.

8. Q: What is the special case to handle during the dequeue operation? A: When dequeuing the last
element, we need to set both front and rear pointers to NULL to properly indicate an empty queue.

Reversing a Linked List

What is Reversing a Linked List?


A common operation to change the direction of links in a linked list
Each node's next pointer points to the previous node instead of the next node
The last node becomes the first node and vice versa

Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

// Global variable - pointer to the first node


struct Node* head = NULL;

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memory
newNode->data = data; // Set the data
newNode->next = NULL; // Set next pointer to NULL
return newNode; // Return the new node
}

// Function to insert node at the end


void insertAtEnd(int data) {
struct Node* newNode = createNode(data); // Create a new node
if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
struct Node* temp = head; // Start from head
// Traverse to the last node
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode; // Link the last node to newNode
}
printf("%d inserted at the end\n", data);
}

// Function to display the linked list


void display() {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}
struct Node* temp = head; // Start from head
printf("Linked List: ");
// Traverse and print each node
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

// Function to reverse the linked list


void reverse() {
if (head == NULL || head->next == NULL) { // If list is empty or has only one node
return; // No need to reverse
}
struct Node* prev = NULL; // Previous node
struct Node* current = head; // Current node
struct Node* next = NULL; // Next node

// Traverse the list and reverse the links


while (current != NULL) {
next = current->next; // Store the next node
current->next = prev; // Reverse the link
prev = current; // Move prev to current
current = next; // Move current to next
}
head = prev; // Update head to the last node (now the first)
printf("List reversed successfully\n");
}

How Reversing a Linked List Works:


1. Initialize three pointers: prev = NULL , current = head , next = NULL

2. Iterate through the linked list:


Store the next node: next = current->next

Reverse the current node's pointer: current->next = prev

Move prev to current position: prev = current

Move current to next position: current = next

3. Update the head pointer to point to the last node: head = prev

Show Image

Step-by-Step Example:
Given a linked list: 10 -> 20 -> 30 -> NULL

Step 1:

prev = NULL
current = 10

next = 20
10's next pointer now points to NULL

Step 2:

prev = 10
current = 20

next = 30
20's next pointer now points to 10

Step 3:

prev = 20

current = 30

next = NULL
30's next pointer now points to 20

Step 4:

prev = 30
current = NULL

head = 30

Result: 30 -> 20 -> 10 -> NULL

Q&A Section - Reversing a Linked List


1. Q: Why would we need to reverse a linked list? A: Reversing a linked list is useful in many
algorithms and applications, such as implementing undo operations, certain sorting techniques, or
when needing to process data in reverse order.

2. Q: What is the time complexity of reversing a linked list? A: O(n), where n is the number of nodes
in the linked list, as we need to traverse each node exactly once.

3. Q: What is the space complexity of the iterative approach to reverse a linked list? A: O(1), as we
only use a fixed number of pointers regardless of the list size.
4. Q: Can the reversal of a linked list be implemented recursively? A: Yes, a linked list can be
reversed recursively, though it has O(n) space complexity due to the recursion stack.
5. Q: What are the edge cases to consider when reversing a linked list? A: Empty list (head = NULL)
and single-node list (head->next = NULL) - in both cases, no reversal is needed.

6. Q: How many pointers are needed to reverse a linked list iteratively? A: Three pointers: previous,
current, and next.

7. Q: After reversing a linked list, which node becomes the new head? A: The last node of the
original list becomes the new head.
8. Q: How does reversing a linked list affect its order? A: The elements appear in reverse order - the
first element becomes the last, the second becomes the second-last, and so on.

Trees

What are Trees?


A hierarchical data structure with nodes connected by edges

Non-linear - data is not stored in sequential order


Each node can have multiple children (except leaf nodes)
Only one path exists between any two nodes

Basic Tree Terms:


Root: The topmost node of a tree
Node: Each element in a tree that contains data and pointers to children

Edge: The link between two nodes


Parent: A node that has one or more child nodes

Child: A node that has a parent node


Leaf: A node with no children
Siblings: Nodes with the same parent

Height: The length of the longest path from the root to a leaf

Depth: The length of the path from the root to a node

Level: The depth of a node plus 1

Binary Tree
A tree in which each node has at most two children

Children are typically referred to as "left child" and "right child"

Types of Binary Trees:


1. Full Binary Tree: Every node has 0 or 2 children
2. Complete Binary Tree: All levels are filled except possibly the last level, which is filled from left to right
3. Perfect Binary Tree: All internal nodes have two children and all leaves are at the same level
4. Balanced Binary Tree: Height of the tree is O(log n), where n is the number of nodes

5. Degenerate (or Pathological) Tree: Every parent node has only one child

Show Image

Binary Tree Traversals


There are three main ways to traverse a binary tree:

1. In-order Traversal (Left, Root, Right):


Visit the left subtree

Visit the root node

Visit the right subtree


Result: Nodes in ascending order for BST

2. Pre-order Traversal (Root, Left, Right):


Visit the root node

Visit the left subtree

Visit the right subtree


Useful for: Creating a copy of the tree

3. Post-order Traversal (Left, Right, Root):


Visit the left subtree

Visit the right subtree


Visit the root node
Useful for: Deleting a tree

Show Image

Binary Search Tree (BST)


A binary tree where:

The left subtree of a node contains only nodes with keys less than the node's key
The right subtree of a node contains only nodes with keys greater than the node's key
Both the left and right subtrees must also be binary search trees

Show Image

Implementing a Simple Binary Search Tree:


c
#include <stdio.h>
#include <stdlib.h>

// Structure for a tree node


struct Node {
int data; // Node value
struct Node* left; // Pointer to left child
struct Node* right; // Pointer to right child
};

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to insert a node into BST


struct Node* insert(struct Node* root, int data) {
// If tree is empty, create a new node
if (root == NULL) {
return createNode(data);
}

// Otherwise, recur down the tree


if (data < root->data) {
root->left = insert(root->left, data);
} else if (data > root->data) {
root->right = insert(root->right, data);
}

// Return unchanged root pointer


return root;
}

// Function for inorder traversal


void inorder(struct Node* root) {
if (root != NULL) {
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
}
// Function for preorder traversal
void preorder(struct Node* root) {
if (root != NULL) {
printf("%d ", root->data);
preorder(root->left);
preorder(root->right);
}
}

// Function for postorder traversal


void postorder(struct Node* root) {
if (root != NULL) {
postorder(root->left);
postorder(root->right);
printf("%d ", root->data);
}
}

// Function to search a key in BST


struct Node* search(struct Node* root, int key) {
// Base case: root is NULL or key is present at root
if (root == NULL || root->data == key)
return root;

// Key is greater than root's data


if (root->data < key)
return search(root->right, key);

// Key is smaller than root's data


return search(root->left, key);
}

// Function to find the minimum value node


struct Node* minValueNode(struct Node* node) {
struct Node* current = node;

// Find the leftmost leaf


while (current && current->left != NULL)
current = current->left;

return current;
}

// Function to delete a node from BST


struct Node* deleteNode(struct Node* root, int key) {
// Base case
if (root == NULL) return root;
// If the key to be deleted is smaller than the root's key,
// then it lies in left subtree
if (key < root->data)
root->left = deleteNode(root->left, key);

// If the key is larger than the root's key


else if (key > root->data)
root->right = deleteNode(root->right, key);

// If key is same as root's key, then this is the node to be deleted


else {
// Node with only one child or no child
if (root->left == NULL) {
struct Node* temp = root->right;
free(root);
return temp;
}
else if (root->right == NULL) {
struct Node* temp = root->left;
free(root);
return temp;
}

// Node with two children: Get the inorder successor (smallest


// in the right subtree)
struct Node* temp = minValueNode(root->right);

// Copy the inorder successor's content to this node


root->data = temp->data;

// Delete the inorder successor


root->right = deleteNode(root->right, temp->data);
}
return root;
}

Q&A Section - Trees


1. Q: What is a tree data structure? A: A tree is a hierarchical, non-linear data structure consisting of
nodes connected by edges, where each node can have multiple children but only one parent (except the
root node).
2. Q: What is the difference between a binary tree and a binary search tree? A: A binary tree is a
tree where each node has at most two children. A binary search tree is a special type of binary tree
where for each node, all elements in the left subtree are less than the node, and all elements in the right
subtree are greater.

3. Q: What are the common traversal methods for a binary tree? A: The common traversal methods
are: in-order (left-root-right), pre-order (root-left-right), and post-order (left-right-root).

4. Q: Why is in-order traversal important for binary search trees? A: In-order traversal of a binary
search tree visits nodes in ascending order, which is useful for obtaining sorted data.
5. Q: What is the maximum number of nodes at level 'L' of a binary tree? A: 2^L (where the root is
at level 0).
6. Q: What is a balanced binary tree? A: A binary tree in which the height of the left and right subtrees
of any node differ by not more than 1.
7. Q: What is the height of a perfect binary tree with 'n' nodes? A: log₂(n+1) - 1
8. Q: What is the time complexity of searching, insertion and deletion in a binary search tree? A:
Average case: O(log n), Worst case (unbalanced tree): O(n)
9. Q: What is a leaf node in a tree? A: A node that has no children.

10. Q: How do you find the height of a binary tree? A: The height is the length of the longest path from
the root to any leaf. It can be calculated recursively as max(height(left subtree), height(right subtree)) +
1.
11. Q: What is a complete binary tree? A: A binary tree in which all levels are completely filled except
possibly the last level, which is filled from left to right.

12. Q: What are the applications of binary trees? A: Binary trees are used in expression evaluation,
Huffman coding for data compression, priority queues, and as a basis for other data structures like
heaps and binary search trees.

Applications of Data Structures

Stack Applications

1. Infix, Prefix, and Postfix Conversion and Evaluation

Expression Notations:

Infix: Operators between operands (e.g., A + B)

Prefix: Operators before operands (e.g., + A B)


Postfix: Operators after operands (e.g., A B +)

Converting Infix to Postfix using Stack:


Step Description

1 Scan infix expression from left to right

2 If scanned character is operand, output it

3 If scanned character is operator, check precedence with operator at stack top

4 If higher precedence, push onto stack

5 If lower/equal precedence, pop stack until higher precedence found, then push

6 If opening bracket, push onto stack

7 If closing bracket, pop stack until matching opening bracket found

8 After scanning entire expression, pop remaining operators from stack


 

Example:
Converting A + B * C to postfix:

1. Scan 'A': Output A


2. Scan '+': Push onto stack

3. Scan 'B': Output AB


4. Scan '': '' has higher precedence than '+', push onto stack
5. Scan 'C': Output ABC

6. Pop remaining operators: '' then '+': Output ABC+

Evaluating Postfix Expressions:

1. Scan postfix expression from left to right


2. If operand, push onto stack
3. If operator, pop top two elements, apply operator, push result back

Example:
Evaluating 6 2 3 + - 3 8 2 / + * 2 ^

1. Push 6, Push 2, Push 3

2. '+': Pop 3, Pop 2, Push (2+3)=5


3. '-': Pop 5, Pop 6, Push (6-5)=1

4. Push 3, Push 8, Push 2


5. '/': Pop 2, Pop 8, Push (8/2)=4

6. '+': Pop 4, Pop 3, Push (3+4)=7


7. '': Pop 7, Pop 1, Push (17)=7
8. Push 2

9. '^': Pop 2, Pop 7, Push (7^2)=49 Result: 49


2. Parentheses Checker

A stack can be used to check if an expression has balanced parentheses:


c
#include <stdio.h>
#include <string.h>
#define MAX 100

// Global variables
char stack[MAX]; // Stack to store opening brackets
int top = -1; // Index of top element

// Function to push a character onto stack


void push(char c) {
if (top == MAX - 1) { // Check if stack is full
printf("Stack Overflow\n"); // Print error message
} else {
stack[++top] = c; // Increment top and push character
}
}

// Function to pop a character from stack


char pop() {
if (top == -1) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return '\0'; // Return null character
} else {
return stack[top--]; // Return character and decrement top
}
}

// Function to check if stack is empty


int isEmpty() {
if (top == -1) { // If top is -1
return 1; // Return true (stack is empty)
} else {
return 0; // Return false (stack is not empty)
}
}

// Function to check if two characters are matching brackets


int isMatchingPair(char character1, char character2) {
if (character1 == '(' && character2 == ')') {
return 1;
} else if (character1 == '{' && character2 == '}') {
return 1;
} else if (character1 == '[' && character2 == ']') {
return 1;
} else {
return 0;
}
}

// Function to check if an expression has balanced brackets


int areBracketsBalanced(char expr[]) {
int i = 0;
// Traverse the expression
for (i = 0; expr[i] != '\0'; i++) {
// If the current character is an opening bracket, push it onto stack
if (expr[i] == '(' || expr[i] == '{' || expr[i] == '[') {
push(expr[i]);
}
// If the current character is a closing bracket
else if (expr[i] == ')' || expr[i] == '}' || expr[i] == ']') {
// If stack is empty or the top bracket doesn't match
if (isEmpty() || !isMatchingPair(pop(), expr[i])) {
return 0; // Not balanced
}
}
}
// If stack is empty, the expression is balanced
return isEmpty();
}

How it Works:

1. Push opening brackets onto stack

2. When closing bracket found:


If stack is empty or top doesn't match - unbalanced

If top matches - pop and continue

3. If stack is empty at end - balanced

Example:
Expression: { [ ( ) ] }

1. Push {, [, (
2. Pop ( for )
3. Pop [ for ]

4. Pop { for }
5. Stack is empty - balanced!

Tree Applications

1. Expression Tree
An expression tree is a binary tree that represents an arithmetic expression:

Leaf nodes are operands (numbers or variables)


Internal nodes are operators (+, -, *, /, etc.)

Show Image

Benefits:

Easy to evaluate expressions

Maintains operator precedence


Simplifies expression parsing

How to Evaluate an Expression Tree:

1. Recursively evaluate left subtree


2. Recursively evaluate right subtree

3. Apply root operator to the results

2. Huffman Encoding

Huffman encoding is a technique for loss

You might also like