Data Structure Using C++
Data Structure Using C++
UNIT-I
UNIT-II
UNIT-III
UNIT-IV
• BINARY AND OTHER TREES: Trees, Binary trees, Properties and representation
of binary trees, Common binary tree operations, Binary tree traversal the ADT binary
tree, ADT binary tree and the class linked binary tree. L1, L2, L3
UNIT-V
INTRODUCTION
3. Recursion:
• realloc (Reallocation):
o Changes the size of the previously allocated memory block, which might move
the memory block to a new location.
o If the new size is larger, the additional memory is uninitialized.
o Syntax: void* realloc(void* ptr, size_t newSize);
o Example:
• Efficient Memory Usage: Only allocates memory when needed, reducing wastage.
• Flexibility: Allows for the allocation of memory blocks of varying sizes, which is
particularly useful for data structures like linked lists, trees, and graphs.
• Resizability: Memory can be resized at runtime as needed.
Example:
#include <iostream>
#include <cstdlib>
int main() {
int n;
std::cout << "Enter number of elements: ";
std::cin >> n;
if (arr == NULL) {
std::cerr << "Memory allocation failed\n";
return 1;
}
Linear Lists
Linear lists are a type of data structure where elements are arranged in a sequential or linear
order. Each element in a linear list is positioned in a specific sequence, and there is a first and
last element with each element (except the first) having a unique predecessor and each element
(except the last) having a unique successor.
• Arrays:
o Definition: A collection of elements of the same type, stored in contiguous
memory locations.
o Access: Elements are accessed using indices, where the first element is at index
0.
o Advantages:
▪ Quick access to elements using an index.
▪ Simple and easy to implement.
o Disadvantages:
▪ Fixed size, which limits flexibility.
▪ Insertion and deletion operations can be costly in terms of time
complexity.
• Linked Lists:
o Definition: A collection of nodes where each node contains a data element and
a reference (or link) to the next node in the sequence.
o Types:
▪ Singly Linked List: Each node contains a single reference to the next
node.
▪ Doubly Linked List: Each node contains references to both the next
and the previous nodes.
▪ Circular Linked List: The last node points back to the first node,
forming a circle.
o Advantages:
▪ Dynamic size, can grow or shrink as needed.
▪ Efficient insertions and deletions (especially at the beginning or middle
of the list).
o Disadvantages:
▪ Slower access time compared to arrays since elements are accessed
sequentially.
▪ Extra memory required for storing references to the next (and previous)
nodes.
• Array Representation:
o Elements are stored in contiguous memory locations.
o Each element can be accessed directly using its index.
o Operations:
▪ Insertion: Adding a new element involves shifting elements to make
space.
▪ Deletion: Removing an element involves shifting elements to fill the
gap.
• Linked List Representation:
o Singly Linked List:
▪ Each node contains a data field and a pointer/reference to the next node.
▪ Insertion: A new node can be inserted by adjusting the pointers, which
is efficient.
▪ Deletion: Deleting a node involves re-linking the adjacent nodes.
o Doubly Linked List:
▪ Each node contains a data field and two pointers/references (one to the
next node and one to the previous node).
▪ Insertion/Deletion: Easier in some cases because you can traverse the
list in both directions.
o Circular Linked List:
▪ The last node's next pointer points to the first node, forming a circle.
▪ Usage: Useful in scenarios where the list needs to be looped through
continuously.
• Traversal: Visiting each element in the list to perform some operation (e.g., printing).
• Insertion: Adding an element at a specific position in the list.
• Deletion: Removing an element from the list.
• Search: Finding a specific element in the list based on some criteria.
• Update: Modifying the value of an existing element in the list.
• Array-Based Lists:
o Used in situations where fast access to elements is required (e.g., databases,
lookup tables).
o Commonly used in implementing other data structures like stacks and queues.
• Linked Lists:
o Used in scenarios where the size of the data structure is dynamic and frequent
insertions and deletions are needed (e.g., memory management, adjacency lists
in graphs).
#include <iostream>
struct Node {
int data;
Node* next;
};
class LinkedList {
public:
Node* head;
LinkedList() {
head = nullptr;
}
int main() {
LinkedList list;
list.insertAtBeginning(10);
list.insertAtBeginning(20);
list.insertAtBeginning(30);
return 0;
}
Explanation:
• This code demonstrates a simple singly linked list where new elements are inserted at
the beginning of the list. The printList function traverses the list and prints all the
elements.
1. Data Objects:
• Definition:
o A data object is a collection of data that is treated as a unit. It represents an
instance of a data type and stores the actual values that an application processes.
o In programming, data objects can be variables, constants, or data structures that
hold data values.
• Examples:
o Primitive Data Objects: These include basic data types like integers, floats,
characters, and booleans.
o Composite Data Objects: These are made up of multiple primitive data
objects. Examples include arrays, structures, and classes.
• Characteristics:
o Type: The data type of the object (e.g., integer, float).
o Value: The actual data held by the object.
o Memory Location: The address where the data object is stored in memory.
2. Data Structures:
• Definition:
o A data structure is a way of organizing and storing data in a computer so that it
can be accessed and modified efficiently.
o Data structures are used to implement abstract data types (ADTs) like lists,
stacks, queues, trees, and graphs.
• Types of Data Structures:
o Linear Data Structures:
▪ Arrays: A collection of elements of the same type stored in contiguous
memory locations.
▪ Linked Lists: A sequence of nodes where each node contains data and
a reference (or link) to the next node.
▪ Stacks: A collection of elements that follows the Last In, First Out
(LIFO) principle.
▪ Queues: A collection of elements that follows the First In, First Out
(FIFO) principle.
o Non-Linear Data Structures:
▪ Trees: A hierarchical structure where each node has a value and
references to its child nodes. A binary tree is a common example.
▪ Graphs: A collection of nodes (or vertices) connected by edges. Graphs
can be directed or undirected.
o Hash Tables: A structure that maps keys to values using a hash function. It
allows for efficient data retrieval based on key values.
• Operations on Data Structures:
o Traversal: Visiting each element in the data structure to perform an operation
(e.g., printing, searching).
o Insertion: Adding a new element to the data structure.
o Deletion: Removing an element from the data structure.
o Search: Finding an element in the data structure that meets certain criteria.
o Sorting: Arranging the elements in a data structure in a specific order.
• Efficiency:
o The choice of data structure affects the efficiency of algorithms. Some
operations are faster on certain data structures than others.
o For example, searching for an element in a sorted array is faster with binary
search (O(log n)) than with a linear search (O(n)).
• Scalability:
oProper use of data structures ensures that applications can handle growing
amounts of data efficiently.
o Data structures like trees and graphs are essential for representing hierarchical
or networked data.
• Maintainability:
o Data structures help in organizing code better, making it easier to understand,
maintain, and debug.
• Arrays: Used in scenarios where quick access to elements is needed, such as in matrix
operations.
• Linked Lists: Useful in situations where frequent insertion and deletion operations are
required, such as in dynamic memory allocation.
• Stacks: Used in function call management in programming languages (call stack) and
in algorithms like depth-first search (DFS).
• Queues: Commonly used in scheduling processes in operating systems, breadth-first
search (BFS) algorithms, and handling asynchronous data (like printers, IO buffers).
• Trees: Widely used in databases (B-trees, AVL trees), in organizing hierarchical data
like file systems, and in parsing expressions.
• Graphs: Essential in representing networks like social networks, transportation
networks, and in algorithms like shortest path (Dijkstra’s) and minimum spanning tree
(Kruskal’s).
1. Definition: A linear list is a data structure where elements are stored in a sequential manner,
and each element is connected to the previous and next elements. This structure allows for
straightforward access and manipulation of data.
2. Types:
• Array:
o Description: A collection of elements stored in contiguous memory locations.
o Access Time: O(1) for access by index.
o Insertion/Deletion Time: O(n) in the worst case.
o Advantages: Fast access by index, simple implementation.
o Disadvantages: Fixed size, costly resizing.
• Linked List:
o Description: A collection of nodes where each node contains data and a
reference (or pointer) to the next node.
o Access Time: O(n) for searching.
o Insertion/Deletion Time: O(1) if the position is known.
o Advantages: Dynamic size, efficient insertions/deletions.
o Disadvantages: Slower access time, additional memory for pointers.
struct Node {
int data;
Node* next;
};
Node* head = nullptr; // Initialize empty list
3. Comparison:
4. Common Operations:
• Array:
o Accessing an element: arr[index]
o Inserting an element: Requires shifting elements.
o Deleting an element: Requires shifting elements.
• Linked List:
o Inserting at the beginning, end, or specific position: O(1) if the position is
known.
o Deleting a node: O(1) if the node reference is known.
5. Use Cases:
• Arrays: Suitable for scenarios requiring fast access and fixed-size data.
• Linked Lists: Useful for dynamic data where frequent insertions and deletions are
required.
In C++, arrays are a fundamental data structure that stores elements of the same type in
contiguous memory locations. Here's a detailed overview of array representation:
1. Definition:
An array is a collection of elements of the same type, indexed by contiguous integers. The size
of an array is fixed at the time of declaration.
• Partial Initialization:
3. Accessing Elements:
4. Modification:
5. Array Size:
6. Multi-dimensional Arrays:
• Initialization of 2D Array:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
• Pointer to Array:
• Pointer Arithmetic:
8. Limitations:
9. Common Operations:
• Traversing an Array:
• Finding an Element:
Arrays in C++ are simple and efficient for many use cases, but for dynamic sizing and more
flexible operations, other data structures like vectors (from the C++ Standard Library) might
be preferred.
In C++, the std::vector is a dynamic array provided by the Standard Library, which offers
flexible and efficient handling of arrays with dynamic sizes. It is part of the <vector> header
and is widely used for its versatility and convenience.
1. Definition:
A std::vector is a sequence container that encapsulates dynamic size arrays. Unlike regular
arrays, vectors can grow or shrink in size, and they manage their own memory.
2. Basic Operations:
#include <vector>
• Accessing Elements:
• Modifying Elements:
3. Common Operations:
• Traversing a Vector:
• Finding an Element:
4. Vector Methods:
• Empty Check:
bool isEmpty = vec4.empty(); // Checks if the vector is empty
• Dynamic Size: Unlike arrays, vectors can grow and shrink as needed.
• Memory Management: Automatically handles memory allocation and deallocation.
• Flexible Operations: Provides various methods for manipulation and access.
• Efficiency: Maintains performance through amortized constant-time complexity for
insertions at the end.
6. Limitations:
• Overhead: More memory overhead compared to arrays due to additional features and
management.
• Element Access: Slightly slower than array access due to boundary checks in debug
mode.
7. Example Usage:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Adding an element
numbers.push_back(6);
// Accessing elements
std::cout << "First element: " << numbers.front() << std::endl;
std::cout << "Last element: " << numbers.back() << std::endl;
// Traversing
for (const int& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
1. Definition:
A std::vector is a sequence container that encapsulates dynamic size arrays. Unlike regular
arrays, vectors can grow or shrink in size, and they manage their own memory.
2. Basic Operations:
#include <vector>
• Accessing Elements:
• Modifying Elements:
3. Common Operations:
• Traversing a Vector:
• Finding an Element:
4. Vector Methods:
• Empty Check:
• Dynamic Size: Unlike arrays, vectors can grow and shrink as needed.
• Memory Management: Automatically handles memory allocation and deallocation.
• Flexible Operations: Provides various methods for manipulation and access.
• Efficiency: Maintains performance through amortized constant-time complexity for insertions
at the end.
6. Limitations:
• Overhead: More memory overhead compared to arrays due to additional features and
management.
• Element Access: Slightly slower than array access due to boundary checks in debug mode.
7. Example Usage:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Adding an element
numbers.push_back(6);
// Accessing elements
std::cout << "First element: " << numbers.front() << std::endl;
std::cout << "Last element: " << numbers.back() << std::endl;
// Traversing
for (const int& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
This example demonstrates basic operations with a std::vector, showing how to create,
modify, and access elements effectively.
You said:
Singly Linked lists and chains
ChatGPT said:
ChatGPT
1. Definition:
A singly linked list is a linear data structure where each element, known as a node, contains
data and a pointer to the next node in the sequence. It allows for dynamic memory allocation
and efficient insertions and deletions.
2. Structure of a Node:
struct Node {
int data; // Data part
Node* next; // Pointer to the next node
};
3. Basic Operations:
• Deleting a Node:
if (current != nullptr) {
previous->next = current->next;
delete current;
}
}
4. Advantages:
5. Disadvantages:
• Sequential Access: Accessing elements requires traversing from the head to the desired
position, which can be slow (O(n) time complexity).
• Extra Memory: Each node requires additional memory for the pointer.
6. Chains:
Chains are a concept where nodes are connected in a way similar to linked lists, but they can
represent various types of linked data structures, such as hash tables or more complex
structures. In the context of singly linked lists, "chains" typically refer to sequences of nodes
where each node points to the next in a single direction.
#include <iostream>
struct Node {
int data;
Node* next;
};
if (head->data == value) {
Node* temp = head;
head = head->next;
delete temp;
return;
}
if (current != nullptr) {
previous->next = current->next;
delete current;
}
}
insertAtBeginning(head, 10);
insertAtBeginning(head, 20);
insertAtEnd(head, 30);
deleteNode(head, 20);
std::cout << "After deleting 20: ";
printList(head);
return 0;
}
This example demonstrates creating a singly linked list, inserting nodes at both the beginning
and end, deleting a node, and printing the list.
This example demonstrates basic operations with a std::vector, showing how to create,
modify, and access elements effectively.
1. Definition:
L1 and L2 are two separate singly linked lists. Each list contains nodes where each node has a
data value and a pointer to the next node.
3. Common Operations:
Suppose you have two sorted linked lists and want to merge them into a single sorted list.
return dummy.next;
}
To find if two linked lists intersect and find the intersection node.
Node* a = headA;
Node* b = headB;
// Traverse both lists. When reaching the end of one list, start at the
beginning of the other list
while (a != b) {
a = (a == nullptr) ? headB : a->next;
b = (b == nullptr) ? headA : b->next;
}
To check if two linked lists are identical (same data in the same order).
Example Program:
#include <iostream>
struct Node {
int data;
Node* next;
};
return dummy.next;
}
// Main function
int main() {
// Create two linked lists
Node* l1 = createNode(1);
l1->next = createNode(3);
l1->next->next = createNode(5);
Node* l2 = createNode(2);
l2->next = createNode(4);
l2->next->next = createNode(6);
// Concatenate lists
concatenateLists(l1, l2);
std::cout << "Concatenated List: ";
printList(l1);
return 0;
}
This example shows how to create, merge, and concatenate linked lists. Modify the code and
functions as needed for specific operations involving two singly linked lists.
Unit II
This section covers arrays and matrices in C++, focusing on their definition, operations, and
applications.
1. Arrays
1.1 Definition:
An array is a collection of elements of the same type, stored in contiguous memory locations.
The size of an array is fixed at the time of declaration.
• Single-Dimensional Array:
• Accessing Elements:
• Modification:
• Size of Array:
• Two-Dimensional Array:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
• Accessing Elements:
• Traversing a 2D Array:
2. Matrices
#include <vector>
std::vector<std::vector<int>> matrix(3, std::vector<int>(4, 0)); //
3x4 matrix initialized with 0
• Traversing a Matrix:
#include <iostream>
#include <vector>
int main() {
// 1D Array Example
int arr[5] = {1, 2, 3, 4, 5};
std::cout << "1D Array: ";
for (int i : arr) {
std::cout << i << " ";
}
std::cout << std::endl;
// 2D Array Example
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
std::cout << "2D Array: " << std::endl;
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}
// Matrix Addition
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
result[i][j] = mat1[i][j] + mat2[i][j];
}
}
// Matrix Multiplication
result = std::vector<std::vector<int>>(2, std::vector<int>(3, 0));
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
result[i][j] = 0;
for (int k = 0; k < 3; ++k) {
result[i][j] += mat1[i][k] * mat2[k][j];
}
}
}
return 0;
}
Summary
1. Arrays
1.1 Definition:
An array is a collection of elements of the same type, stored in contiguous memory locations.
It allows access to its elements using indices.
• Accessing Elements:
• Modification:
• Size Calculation:
2. Matrices
2.1 Definition:
A matrix is a two-dimensional array where elements are arranged in rows and columns.
2.2 Operations:
• Accessing Elements:
• Traversing a Matrix:
3. Special Matrices
Special matrices are matrices with particular properties that are useful in mathematical and
computational problems.
3.1 Identity Matrix:
An upper triangular matrix has non-zero elements only on or above the main diagonal.
A lower triangular matrix has non-zero elements only on or below the main diagonal.
4. Sparse Matrices
4.1 Definition:
A sparse matrix is a matrix in which most of the elements are zero. Sparse matrices are stored
more efficiently than dense matrices by only storing non-zero elements.
4.2 Representation:
#include <vector>
struct Triplet {
int row;
int col;
int value;
};
return result;
}
Summary
These concepts and operations are fundamental in various fields including computer science,
engineering, and mathematics.
A stack is a fundamental data structure used to manage and store data in a Last-In-First-Out
(LIFO) manner. This section covers the abstract data type of stacks, their array and linked
representations, and some common applications.
1.1 Definition:
A stack is an abstract data type that follows the Last-In-First-Out (LIFO) principle. It supports
the following operations:
• Push: Add an element to the top of the stack.
• Pop: Remove the element from the top of the stack.
• Peek/Top: Retrieve the top element without removing it.
• isEmpty: Check if the stack is empty.
• Push: O(1)
• Pop: O(1)
• Peek/Top: O(1)
• isEmpty: O(1)
2.1 Definition:
An array-based stack uses a fixed-size array to store the stack elements. It maintains a pointer
(or index) to the top element.
2.2 Implementation:
#include <iostream>
#define MAX 100 // Define maximum size of stack
class Stack {
private:
int arr[MAX];
int top;
public:
Stack() { top = -1; }
bool isEmpty() {
return top == -1;
}
bool isFull() {
return top == MAX - 1;
}
void push(int x) {
if (isFull()) {
std::cout << "Stack Overflow" << std::endl;
return;
}
arr[++top] = x;
}
void pop() {
if (isEmpty()) {
std::cout << "Stack Underflow" << std::endl;
return;
}
--top;
}
int peek() {
if (isEmpty()) {
std::cout << "Stack is Empty" << std::endl;
return -1; // or some sentinel value
}
return arr[top];
}
};
2.3 Usage:
int main() {
Stack s;
s.push(10);
s.push(20);
std::cout << "Top element: " << s.peek() << std::endl;
s.pop();
std::cout << "Top element after pop: " << s.peek() << std::endl;
return 0;
}
3.1 Definition:
A linked representation of a stack uses a linked list where each node points to the next node in
the stack.
3.2 Implementation:
#include <iostream>
struct Node {
int data;
Node* next;
};
class Stack {
private:
Node* top;
public:
Stack() { top = nullptr; }
bool isEmpty() {
return top == nullptr;
}
void push(int x) {
Node* newNode = new Node();
newNode->data = x;
newNode->next = top;
top = newNode;
}
void pop() {
if (isEmpty()) {
std::cout << "Stack Underflow" << std::endl;
return;
}
Node* temp = top;
top = top->next;
delete temp;
}
int peek() {
if (isEmpty()) {
std::cout << "Stack is Empty" << std::endl;
return -1; // or some sentinel value
}
return top->data;
}
};
3.3 Usage:
int main() {
Stack s;
s.push(10);
s.push(20);
std::cout << "Top element: " << s.peek() << std::endl;
s.pop();
std::cout << "Top element after pop: " << s.peek() << std::endl;
return 0;
}
4. Applications of Stack
#include <iostream>
#include <stack>
#include <string>
int main() {
std::string expr = "{[()]}";
if (isValidParentheses(expr)) {
std::cout << "Balanced Parentheses" << std::endl;
} else {
std::cout << "Unbalanced Parentheses" << std::endl;
}
return 0;
}
The Towers of Hanoi problem can be solved using a stack-based approach, but is often
explained recursively. The problem involves moving a set of disks from one peg to another,
using an auxiliary peg.
Recursive Solution:
#include <iostream>
int main() {
int n = 3; // Number of disks
hanoi(n, 'A', 'C', 'B'); // A, B, and C are names of rods
return 0;
}
Summary
• Stack ADT: LIFO structure supporting push, pop, peek, and isEmpty operations.
• Array Representation: Fixed-size implementation with array and index.
• Linked Representation: Dynamic-size implementation using a linked list.
• Applications:
o Parenthesis Matching: Ensures expressions have balanced parentheses.
o Towers of Hanoi: Classic problem solvable using stack principles.
These concepts are fundamental in computer science and are widely used in algorithm design
and problem-solving.
UNIT-III
Queues are essential data structures that manage elements in a First-In-First-Out (FIFO)
manner. This section provides an overview of the queue abstract data type, its array and linked
representations, and an application example.
1.1 Definition:
A queue is an abstract data type that follows the First-In-First-Out (FIFO) principle. It supports
the following operations:
• Enqueue: O(1)
• Dequeue: O(1)
• Front/Peek: O(1)
• isEmpty: O(1)
• isFull: O(1) (if applicable)
2.1 Definition:
An array-based queue uses a fixed-size array and two pointers (or indices) to manage the front
and rear of the queue.
2.2 Implementation:
#include <iostream>
#define MAX 100 // Define maximum size of queue
class Queue {
private:
int arr[MAX];
int front, rear, size;
public:
Queue() : front(0), rear(0), size(0) {}
bool isEmpty() {
return size == 0;
}
bool isFull() {
return size == MAX;
}
void enqueue(int x) {
if (isFull()) {
std::cout << "Queue Overflow" << std::endl;
return;
}
arr[rear] = x;
rear = (rear + 1) % MAX;
size++;
}
void dequeue() {
if (isEmpty()) {
std::cout << "Queue Underflow" << std::endl;
return;
}
front = (front + 1) % MAX;
size--;
}
int frontElement() {
if (isEmpty()) {
std::cout << "Queue is Empty" << std::endl;
return -1; // or some sentinel value
}
return arr[front];
}
};
2.3 Usage:
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
std::cout << "Front element: " << q.frontElement() << std::endl;
q.dequeue();
std::cout << "Front element after dequeue: " << q.frontElement() <<
std::endl;
return 0;
}
A linked representation of a queue uses a linked list where each node points to the next node.
Two pointers, front and rear, manage the front and rear of the queue.
3.2 Implementation:
#include <iostream>
struct Node {
int data;
Node* next;
};
class Queue {
private:
Node* front;
Node* rear;
public:
Queue() : front(nullptr), rear(nullptr) {}
bool isEmpty() {
return front == nullptr;
}
void enqueue(int x) {
Node* newNode = new Node();
newNode->data = x;
newNode->next = nullptr;
if (isEmpty()) {
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
}
void dequeue() {
if (isEmpty()) {
std::cout << "Queue Underflow" << std::endl;
return;
}
Node* temp = front;
front = front->next;
delete temp;
if (front == nullptr) {
rear = nullptr;
}
}
int frontElement() {
if (isEmpty()) {
std::cout << "Queue is Empty" << std::endl;
return -1; // or some sentinel value
}
return front->data;
}
};
3.3 Usage:
int main() {
Queue q;
q.enqueue(10);
q.enqueue(20);
std::cout << "Front element: " << q.frontElement() << std::endl;
q.dequeue();
std::cout << "Front element after dequeue: " << q.frontElement() <<
std::endl;
return 0;
}
4. Applications of Queue
One interesting application of queues is the railroad car arrangement problem. In this problem,
train cars need to be arranged in a specific order using a stack (acting as a temporary holding
area) and a queue (for final arrangement).
Problem Description: Given a sequence of train cars, we want to rearrange them into a specific
order. We can only use a stack to temporarily hold the cars and a queue to achieve the final
arrangement.
Solution Approach:
#include <iostream>
#include <queue>
#include <stack>
while (!inputQueue.empty()) {
int car = inputQueue.front();
inputQueue.pop();
// Move cars to the stack until we find the correct order
while (!tempStack.empty() && tempStack.top() == outputQueue.front())
{
outputQueue.push(tempStack.top());
tempStack.pop();
}
int main() {
std::queue<int> inputQueue;
std::queue<int> outputQueue;
// Input cars
inputQueue.push(1);
inputQueue.push(2);
inputQueue.push(3);
inputQueue.push(4);
rearrangeCars(inputQueue, outputQueue);
return 0;
}
Summary
• Queue ADT: FIFO structure supporting enqueue, dequeue, front, and isEmpty operations.
• Array Representation: Fixed-size implementation with array and circular indexing.
• Linked Representation: Dynamic-size implementation using a linked list.
• Applications:
o Railroad Car Arrangement: Uses queues and stacks to rearrange train cars.
1. Dictionaries
1.1 Definition:
A dictionary is a data structure that stores key-value pairs, where each key is unique and maps
to a specific value. It is also known as an associative array or map.
1.2 Operations:
Example Implementation:
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
std::unordered_map<std::string, int> dict;
// Insertion
dict["apple"] = 3;
dict["banana"] = 2;
// Search
if (dict.find("apple") != dict.end()) {
std::cout << "Apple count: " << dict["apple"] << std::endl;
}
// Update
dict["apple"] = 5;
// Deletion
dict.erase("banana");
return 0;
}
2. Linear Representation of Hashing
2.1 Definition:
Linear hashing techniques use a hash function to map keys to indices in an array. Collisions
(when multiple keys hash to the same index) are resolved using methods like linear probing.
Linear probing resolves collisions by searching for the next available slot in a sequential
manner.
Implementation:
#include <iostream>
#include <vector>
class HashTable {
private:
std::vector<int> table;
int size;
public:
HashTable(int s) : size(s) {
table.resize(size, -1); // Initialize table with -1 (indicating empty
slots)
}
Usage:
int main() {
HashTable ht(10);
ht.insert(5);
ht.insert(15);
ht.remove(5);
std::cout << "5 present after removal: " << ht.search(5) << std::endl;
return 0;
}
3.1 Definition:
A hash table is an implementation of a dictionary using a hash function to map keys to indices
in an array. Collisions are resolved through various methods, including chaining and open
addressing.
3.2 Chaining:
Chaining involves using a linked list (or another data structure) to handle collisions at each
index in the hash table.
Implementation:
#include <iostream>
#include <list>
#include <vector>
class HashTableChaining {
private:
std::vector<std::list<int>> table;
int size;
public:
HashTableChaining(int s) : size(s) {
table.resize(size);
}
Usage:
int main() {
HashTableChaining ht(10);
ht.insert(5);
ht.insert(15);
ht.remove(5);
std::cout << "5 present after removal: " << ht.search(5) << std::endl;
return 0;
}
Open addressing handles collisions by finding another open slot within the array using
techniques such as linear probing (discussed above), quadratic probing, or double hashing.
#include <iostream>
#include <vector>
class HashTableQuadraticProbing {
private:
std::vector<int> table;
std::vector<bool> occupied;
int size;
public:
HashTableQuadraticProbing(int s) : size(s) {
table.resize(size, -1);
occupied.resize(size, false);
}
Usage:
int main() {
HashTableQuadraticProbing ht(10);
ht.insert(5);
ht.insert(15);
return 0;
}
Summary
• Dictionaries: Key-value pair data structures allowing efficient lookup, insertion, and deletion.
• Linear Representation: Uses array and linear probing to handle collisions.
• Hash Table Representation: Includes chaining and open addressing (linear probing, quadratic
probing) for collision resolution.
Hashing techniques are crucial for optimizing performance in data retrieval and storage
operations across various applications.
UNIT-IV
Trees are hierarchical data structures that are essential in various computational scenarios. This
section covers binary trees, their properties, representations, common operations, and traversal
methods, as well as the abstract data type (ADT) for binary trees and implementations using
linked structures.
1. Trees
1.1 Definition:
A tree is a non-linear data structure consisting of nodes connected by edges. Each node contains
a value and pointers to child nodes. The top node is called the root, and nodes without children
are called leaves.
1.2 Terminology:
2. Binary Trees
2.1 Definition:
A binary tree is a type of tree where each node has at most two children, typically referred to
as the left and right child.
2.2 Properties:
2.3 Representation:
Binary trees can be represented using nodes where each node has pointers to its left and right
children.
Node Structure in C++:
struct Node {
int data;
Node* left;
Node* right;
Insertion Example:
Deletion Example:
Search Example:
Traversal refers to visiting all the nodes in a specific order. Common traversal methods include:
4.1 Definition:
The ADT for a binary tree defines the operations and properties without specifying the
implementation details.
ADT Operations:
5.1 Definition:
A linked binary tree class represents a binary tree using nodes with left and right pointers,
allowing for dynamic tree structures.
class BinaryTree {
private:
Node* root;
public:
BinaryTree() : root(nullptr) {}
Class Implementation:
void BinaryTree::insert(int value) {
insert(root, value);
}
void BinaryTree::inorderTraversal() {
inorder(root);
std::cout << std::endl;
}
void BinaryTree::preorderTraversal() {
preorder(root);
std::cout << std::endl;
}
void BinaryTree::postorderTraversal() {
postorder(root);
std::cout << std::endl;
}
• Trees: Hierarchical structures with nodes and edges, where each node can have multiple
children.
• Binary Trees: Trees where each node has at most two children.
• Operations: Include insertion, deletion, searching, and traversal (inorder, preorder,
postorder).
• ADT Binary Tree: Defines operations and properties of binary trees without implementation
specifics.
• Linked Binary Tree Class: A practical implementation using linked nodes to create a dynamic
binary tree structure.
Understanding these concepts provides a solid foundation for working with hierarchical data
structures and performing operations efficiently.
UNIT-V
A priority queue is a data structure that allows for efficient retrieval and removal of elements
based on their priority. Unlike regular queues where elements are processed in a FIFO manner,
a priority queue processes elements according to their priority levels.
1.1 Definition:
A priority queue is an abstract data type that supports operations to insert elements with an
associated priority and to remove the element with the highest priority.
2.1 Definition:
In a linear list representation of a priority queue, elements are stored in a list, and priorities are
managed explicitly.
2.2 Implementation:
Insertion: Insert an element into the list while maintaining order based on priority.
Extract-Max/Extract-Min: Find the element with the highest/lowest priority and remove it
from the list.
Example:
#include <iostream>
#include <list>
#include <algorithm>
class PriorityQueue {
private:
std::list<std::pair<int, int>> elements; // Pair of (priority, value)
public:
void insert(int value, int priority) {
elements.push_back({priority, value});
elements.sort([](const std::pair<int, int>& a, const std::pair<int,
int>& b) {
return a.first > b.first; // Sort in descending order of priority
});
}
int extractMax() {
if (elements.empty()) {
std::cerr << "Priority Queue is empty!" << std::endl;
return -1; // or some sentinel value
}
int value = elements.front().second;
elements.pop_front();
return value;
}
Usage:
int main() {
PriorityQueue pq;
pq.insert(10, 2);
pq.insert(20, 1);
pq.insert(30, 3);
return 0;
}
3. Heaps
3.1 Definition:
A heap is a complete binary tree that satisfies the heap property. There are two types of heaps:
• Max-Heap: The key at the root is the maximum among all keys in the heap, and each subtree
is a max-heap.
• Min-Heap: The key at the root is the minimum among all keys in the heap, and each subtree
is a min-heap.
Implementation of a Max-Heap:
#include <iostream>
#include <vector>
#include <algorithm>
class MaxHeap {
private:
std::vector<int> heap;
if (largest != index) {
std::swap(heap[index], heap[largest]);
heapify(largest);
}
}
public:
void insert(int value) {
heap.push_back(value);
int index = heap.size() - 1;
int extractMax() {
if (heap.empty()) {
std::cerr << "Heap is empty!" << std::endl;
return -1; // or some sentinel value
}
return root;
}
Usage:
int main() {
MaxHeap maxHeap;
maxHeap.insert(10);
maxHeap.insert(20);
maxHeap.insert(30);
return 0;
}
Heap sorting is an efficient sorting algorithm that uses a binary heap. It works by building a
max-heap (or min-heap) from the input data and then repeatedly extracting the maximum (or
minimum) element to get the sorted output.
#include <iostream>
#include <vector>
#include <algorithm>
if (largest != i) {
std::swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}
Usage:
int main() {
std::vector<int> arr = {12, 11, 13, 5, 6, 7};
heapSort(arr);
return 0;
}
Summary
• Priority Queues: Data structures that allow elements to be processed based on priority.
• Linear List Representation: Simple but less efficient, using a sorted list to manage priorities.
• Heaps: Efficient data structures (max-heaps, min-heaps) for implementing priority queues.
• Heap Sorting: A sorting algorithm that utilizes the heap data structure to achieve efficient
sorting.
Priority queues are essential for various applications such as scheduling tasks, managing
resources, and implementing algorithms like Dijkstra's shortest path. Understanding these
concepts enables efficient management and processing of data based on priority.
Search trees are hierarchical data structures used to manage and retrieve data efficiently. The
most common type of search tree is the Binary Search Tree (BST). This section covers BST
operations, implementation, and handling duplicates.
1.1 Definition:
A Binary Search Tree (BST) is a binary tree in which each node has at most two children, and
the left child contains values less than the parent node, while the right child contains values
greater than the parent node. This property allows for efficient searching, insertion, and
deletion operations.
1.2 Properties:
2. BST Operations
2.1 Insertion:
Inserting a new node involves finding the appropriate location in the tree where the new node
will maintain the BST property.
Insertion Algorithm:
Insertion Implementation:
struct Node {
int data;
Node* left;
Node* right;
2.2 Search:
Searching involves comparing the target value with the current node's value and recursively
moving left or right depending on whether the target is smaller or larger.
Search Implementation:
2.3 Deletion:
1. Node to delete has no children (leaf node): Simply remove the node.
2. Node to delete has one child: Replace the node with its child.
3. Node to delete has two children: Find the in-order successor (smallest value in the right
subtree), replace the node's value with the successor’s value, and delete the successor.
Deletion Implementation:
2.4 Traversal:
Traversal methods visit all nodes in a specific order:
Traversal Implementations:
BSTs typically do not allow duplicate values. However, if duplicates are allowed, you need to
define a rule for their placement. Common strategies include:
• Insert duplicates in the right subtree if the new value is equal to the current node’s value.
• Insert duplicates in the left subtree if the new value is equal to the current node’s value.
Summary
• Binary Search Trees (BST): A hierarchical data structure where each node has at most two
children, with values organized to allow efficient searching and sorting.
• Operations: Include insertion, search, deletion, and various traversals.
• Handling Duplicates: Duplicates can be managed by defining specific rules for their placement
in the tree.
BSTs are foundational for many algorithms and data structures, and understanding their
operations and variations with duplicates is crucial for effective implementation and use in
applications.