C++ for DSA and Placements: A Comprehensive Guide
elcome to your journey into C++! This guide is designed to take you from the very
W
basics to advanced concepts, with a strong emphasis on what you need for Data
Structures and Algorithms (DSA) and competitive programming/placement
preparation. Each concept is explained with clear, concise code examples to help
solidify your understanding.
Table of Contents
1. Introduction to C++
2. Basic Building Blocks
○ Variables and Data Types
○ Operators
○ Input and Output
3. Control Flow
○ Conditional Statements (if-else, switch)
○ Loops (for, while, do-while)
4. Functions
○ Function Declaration and Definition
○ Function Overloading
5. Arrays and Pointers
○ Arrays
○ Pointers
○ Pointers and Arrays
○ Dynamic Memory Allocation
6. Strings
○ C-style Strings
○ std::string
7. Object-Oriented Programming (OOP) in C++
○ Classes and Objects
○ Constructors and Destructors
○ Encapsulation (Access Specifiers)
○ Inheritance
○ Polymorphism (Function Overriding, Virtual Functions)
○ Abstraction (Abstract Classes and Pure Virtual Functions)
8. Standard Template Library (STL) - Your DSA Powerhouse
○ Containers
■ std::vector
■ std::list
■ std::deque
■ std::map and std::unordered_map
■ std::set and std::unordered_set
■ std::stack
■ std::queue
■ std::priority_queue
○ Iterators
○ Algorithms
9. Recursion
10.Basic Data Structures
○ Linked Lists
○ Stacks (Manual Implementation)
○ Queues (Manual Implementation)
○ Trees (Binary Tree, Binary Search Tree)
○ Graphs (Adjacency List/Matrix)
11. Basic Algorithms
○ Searching Algorithms (Linear, Binary)
○ Sorting Algorithms (Bubble, Selection, Insertion, Merge, Quick)
○ Hashing
12.File I/O
1. Introduction to C++
++ is a powerful, general-purpose programming language that supports procedural,
C
object-oriented, and generic programming. It's an extension of the C language.
Why C++ for DSA?
● Performance:C++ offers high performance due to low-levelmemory
anagement and direct hardware access.
m
STL:The Standard Template Library provides ready-to-usedata structures and
●
algorithms, making development faster and more efficient.
● Object-Oriented:Helps in organizing complex codeinto modular, reusable
components.
● Widely Used:A popular choice in competitive programmingand for
systems-level development.
Basic Structure of a C++ Program:
#include <iostream> // Header for input/output operations
// The main function where program execution begins
int main() {
// Print "Hello, World!" to the console
std::cout << "Hello, World!" << std::endl;
/ / Return 0 to indicate successful execution
return 0;
}
● #include <iostream>: Includes the iostream library, which provides functionalities
f or input and output operations (like std::cout).
int main(): The entry point of every C++ program. Execution starts here.
●
● std::cout: Used to print output to the console. std::endl inserts a new line and
flushes the output buffer.
● return 0;: Indicates that the program executed successfully.
2. Basic Building Blocks
2.1. Variables and Data Types
ariables are named storage locations that can hold data. Data types specify the type
V
of data a variable can hold.
include <iostream>
#
#include <string> // Required for std::string
int main() {
// Integer type: Stores whole numbers
int age = 25;
std::cout << "Age: " << age << std::endl;
/ / Floating-point types: Stores numbers with decimal points
float temperature = 98.6f; // 'f' suffix for float literal
double pi = 3.1415926535;
std::cout << "Temperature: " << temperature << std::endl;
std::cout << "Pi: " << pi << std::endl;
/ / Character type: Stores single characters
char grade = 'A';
std::cout << "Grade: " << grade << std::endl;
/ / Boolean type: Stores true or false
bool isStudent = true;
std::cout << "Is Student: " << isStudent << std::endl; // Prints 1 for true, 0 for false
/ / String type: Stores sequences of characters (text)
std::string name = "Alice";
std::cout << "Name: " << name << std::endl;
/ / Long long: For very large integers
long long bigNumber = 123456789012345LL; // 'LL' suffix for long long literal
std::cout << "Big Number: " << bigNumber << std::endl;
/ / Sizeof operator: Get the size of a data type in bytes
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
std::cout << "Size of double: " << sizeof(double) << " bytes" << std::endl;
return 0;
}
2.2. Operators
Operators perform operations on variables and values.
#include <iostream>
int main() {
int a = 10, b = 3;
/ / Arithmetic Operators
std::cout << "--- Arithmetic Operators ---" << std::endl;
std::cout << "a + b = " << (a + b) << std::endl; // Addition
std::cout << "a - b = " << (a - b) << std::endl; // Subtraction
std::cout << "a * b = " << (a * b) << std::endl; // Multiplication
std::cout << "a / b = " << (a / b) << std::endl; // Division (integer division)
std::cout << "a % b = " << (a % b) << std::endl; // Modulo (remainder)
/ / Relational Operators (return true/false)
std::cout << "\n--- Relational Operators ---" << std::endl;
std::cout << "a == b: " << (a == b) << std::endl; // Equal to
s td::cout << "a != b: " << (a != b) << std::endl; // Not equal to
std::cout << "a > b: " << (a > b) << std::endl; // Greater than
std::cout << "a < b: " << (a < b) << std::endl; // Less than
std::cout << "a >= b: " << (a >= b) << std::endl; // Greater than or equal to
std::cout << "a <= b: " << (a <= b) << std::endl; // Less than or equal to
/ / Logical Operators (work with boolean values)
bool x = true, y = false;
std::cout << "\n--- Logical Operators ---" << std::endl;
std::cout << "x && y: " << (x && y) << std::endl; // Logical AND
std::cout << "x || y: " << (x || y) << std::endl; // Logical OR
std::cout << "!x: " << (!x) << std::endl; // Logical NOT
/ / Assignment Operators
std::cout << "\n--- Assignment Operators ---" << std::endl;
int c = 5;
std::cout << "Initial c: " << c << std::endl;
c += 2; // c = c + 2;
std::cout << "c after c += 2: " << c << std::endl;
c *= 3; // c = c * 3;
std::cout << "c after c *= 3: " << c << std::endl;
/ / Increment/Decrement Operators
std::cout << "\n--- Increment/Decrement Operators ---" << std::endl;
int i = 5;
std::cout << "Initial i: " << i << std::endl;
std::cout << "i++ (post-increment): " << i++ << std::endl; // Uses current value, then
increments
std::cout << "i after i++: " << i << std::endl;
std::cout << "++i (pre-increment): " << ++i << std::endl; // Increments, then uses new
value
std::cout << "i after ++i: " << i << std::endl;
return 0;
}
2.3. Input and Output
std::cin is used to get input from the user.
include <iostream>
#
#include <string>
int main() {
std::string name;
int age;
/ / Prompt the user for input
std::cout << "Enter your name: ";
// Read a single word (stops at whitespace)
std::cin >> name;
s td::cout << "Enter your age: ";
// Read an integer
std::cin >> age;
std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
/ / To read a full line with spaces, use std::getline
std::cin.ignore(); // Clear the buffer after previous std::cin >> age;
std::cout << "Tell me something about yourself (press Enter when done): ";
std::string about;
std::getline(std::cin, about); // Reads until newline character
std::cout << "You said: " << about << std::endl;
return 0;
}
3. Control Flow
Control flow statements determine the order in which instructions are executed.
3.1. Conditional Statements (if-else, switch)
#include <iostream>
int main() {
int score = 75;
/ / if-else if-else ladder
std::cout << "--- if-else if-else ---" << std::endl;
if (score >= 90) {
std::cout << "Grade: A" << std::endl;
} else if (score >= 80) {
std::cout << "Grade: B" << std::endl;
} else if (score >= 70) {
std::cout << "Grade: C" << std::endl;
} else {
std::cout << "Grade: F" << std::endl;
}
/ / switch statement (for discrete values)
std::cout << "\n--- switch statement ---" << std::endl;
char choice = 'B';
switch (choice) {
case 'A':
std::cout << "Excellent choice!" << std::endl;
break; // Exit switch after this case
case 'B':
std::cout << "Good choice!" << std::endl;
break;
case 'C':
std::cout << "Okay choice." << std::endl;
break;
default: // Executed if no other case matches
std::cout << "Invalid choice." << std::endl;
break;
}
return 0;
}
3.2. Loops (for, while, do-while)
#include <iostream>
int main() {
/ / for loop: When you know the number of iterations
std::cout << "--- for loop (counting 1 to 5) ---" << std::endl;
for (int i = 1; i <= 5; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
/ / while loop: When the number of iterations is unknown, based on a condition
std::cout << "\n--- while loop (countdown from 3) ---" << std::endl;
int count = 3;
while (count > 0) {
std::cout << count << " ";
count--;
}
std::cout << std::endl;
/ / do-while loop: Executes at least once, then checks the condition
std::cout << "\n--- do-while loop (always runs once) ---" << std::endl;
int num = 0;
do {
std::cout << "Enter a positive number: ";
std::cin >> num;
} while (num <= 0);
std::cout << "You entered: " << num << std::endl;
/ / break and continue
std::cout << "\n--- break and continue ---" << std::endl;
for (int i = 1; i <= 10; ++i) {
if (i == 5) {
std::cout << "Skipping 5 (continue)" << std::endl;
continue; // Skip the rest of the current iteration
}
if (i == 8) {
std::cout << "Breaking loop at 8" << std::endl;
break; // Exit the loop entirely
}
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
4. Functions
unctions are blocks of code that perform a specific task. They promote code
F
reusability and modularity.
4.1. Function Declaration and Definition
#include <iostream>
/ / Function Declaration (Prototype): Tells the compiler about the function
int add(int a, int b); // Function signature: return_type name(parameters);
/ / Function Definition: Contains the actual code of the function
void greet(std::string name) { // void means no return value
std::cout << "Hello, " << name << "!" << std::endl;
}
int main() {
greet("Alice"); // Calling the greet function
int sum_result = add(5, 7); // Calling the add function
std::cout << "Sum: " << sum_result << std::endl;
return 0;
}
/ / Definition of the add function (can be after main if declared before)
int add(int a, int b) {
return a + b;
}
4.2. Function Overloading
llows multiple functions with the same name but different parameters (different
A
number or different types of parameters).
include <iostream>
#
#include <string>
/ / Function to add two integers
int add(int a, int b) {
std::cout << "Adding two integers: ";
return a + b;
}
/ / Overloaded function to add three integers
int add(int a, int b, int c) {
std::cout << "Adding three integers: ";
return a + b + c;
}
/ / Overloaded function to concatenate two strings
std::string add(std::string s1, std::string s2) {
std::cout << "Concatenating two strings: ";
return s1 + s2;
}
int main() {
std::cout << add(10, 20) << std::endl;
std::cout << add(1, 2, 3) << std::endl;
std::cout << add("Hello, ", "World!") << std::endl;
return 0;
}
5. Arrays and Pointers
5.1. Arrays
rrays are collections of elements of the same data type stored in contiguous memory
A
locations.
include <iostream>
#
#include <array> // For std::array (fixed-size, safer)
#include <vector> // For std::vector (dynamic-size, more flexible)
int main() {
// C-style array: Fixed size at compile time
int numbers[5]; // Declares an array of 5 integers (indices 0 to 4)
/ / Initialize elements
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
/ / Access elements using index
std::cout << "First element: " << numbers[0] << std::endl;
std::cout << "Third element: " << numbers[2] << std::endl;
/ / Iterate through the array
std::cout << "All elements of C-style array: ";
for (int i = 0; i < 5; ++i) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;
/ / Initialization during declaration
int scores[] = {100, 95, 88, 72}; // Size is automatically determined (4 elements)
std::cout << "Scores: ";
for (int score : scores) { // Range-based for loop (C++11 and later)
std::cout << score << " ";
}
std::cout << std::endl;
/ / std::array (C++11): Fixed size, but behaves like a container (safer)
std::array<int, 3> arr_std = {1, 2, 3};
std::cout << "std::array elements: ";
for (int val : arr_std) {
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "Size of std::array: " << arr_std.size() << std::endl;
/ / 2D Arrays (Matrices)
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
std::cout << "\n2D Matrix:" << 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;
}
return 0;
}
5.2. Pointers
Pointers are variables that store memory addresses of other variables.
#include <iostream>
int main() {
int var = 10; // Declare an integer variable
/ / Declare a pointer variable 'ptr' that can store the address of an integer
int* ptr;
/ / Store the address of 'var' into 'ptr' using the address-of operator (&)
ptr = &var;
s td::cout << "Value of var: " << var << std::endl;
std::cout << "Address of var: " << &var << std::endl; // Address in memory
std::cout << "Value of ptr (address stored): " << ptr << std::endl; // Same as &var
/ / Dereference operator (*): Access the value at the address stored in the pointer
std::cout << "Value at address stored in ptr (*ptr): " << *ptr << std::endl;
/ / Changing value using pointer
*ptr = 20; // Change the value at the address pointed to by ptr
std::cout << "New value of var: " << var << std::endl; // var is now 20
/ / Null pointer: A pointer that doesn't point to any valid memory location
int* nullPtr = nullptr; // C++11 way (preferred over NULL)
// int* nullPtr = NULL; // Old C-style way
std::cout << "Value of nullPtr: " << nullPtr << std::endl;
/ / Pointer to a pointer
int** ptrToPtr = &ptr;
std::cout << "Value of ptrToPtr (address of ptr): " << ptrToPtr << std::endl;
std::cout << "Value at *ptrToPtr (value of ptr): " << *ptrToPtr << std::endl;
std::cout << "Value at **ptrToPtr (value of var): " << **ptrToPtr << std::endl;
return 0;
}
5.3. Pointers and Arrays
An array name can be treated as a pointer to its first element.
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int size = sizeof(arr) / sizeof(arr[0]);
/ / Array name 'arr' itself acts as a pointer to the first element
std::cout << "Address of first element (arr): " << arr << std::endl;
std::cout << "Address of first element (&arr[0]): " << &arr[0] << std::endl;
/ / Accessing elements using pointer arithmetic
std::cout << "Elements using pointer arithmetic:" << std::endl;
for (int i = 0; i < size; ++i) {
// *(arr + i) means "value at (address of arr[0] + i * sizeof(int))"
std::cout << *(arr + i) << " ";
}
std::cout << std::endl;
/ / A pointer variable can also point to the array
int* p = arr; // p now points to arr[0]
s td::cout << "Elements using pointer variable 'p':" << std::endl;
for (int i = 0; i < size; ++i) {
std::cout << p[i] << " "; // Array-like access using pointer
}
std::cout << std::endl;
s td::cout << "Elements using incrementing pointer 'p':" << std::endl;
for (int i = 0; i < size; ++i) {
std::cout << *p << " "; // Dereference current position
p++; // Move pointer to next element
}
std::cout << std::endl;
// Note: 'p' now points past the end of the array. 'arr' still points to the beginning.
return 0;
}
5.4. Dynamic Memory Allocation
llocating memory during program execution using new and delete. This is crucial for
A
DSA when array sizes are not known at compile time.
#include <iostream>
int main() {
int size;
std::cout << "Enter the number of elements: ";
std::cin >> size;
/ / Dynamically allocate an array of integers
// 'new int[size]' requests memory for 'size' integers and returns a pointer to the first
element.
int* dynamicArray = new int[size];
// Check if allocation was successful (optional but good practice)
if (dynamicArray == nullptr) {
std::cout << "Memory allocation failed!" << std::endl;
return 1; // Indicate error
}
/ / Initialize and print elements
std::cout << "Enter " << size << " integers:" << std::endl;
for (int i = 0; i < size; ++i) {
std::cout << "Element " << i + 1 << ": ";
std::cin >> dynamicArray[i]; // Access like a normal array
}
s td::cout << "Dynamically allocated array elements: ";
for (int i = 0; i < size; ++i) {
std::cout << dynamicArray[i] << " ";
}
std::cout << std::endl;
/ / Deallocate the memory to prevent memory leaks
// 'delete[]' is used for arrays, 'delete' for single objects.
delete[] dynamicArray;
dynamicArray = nullptr; // Set pointer to nullptr after deallocation (good practice)
/ / Dynamically allocate a single integer
int* singleInt = new int;
*singleInt = 100;
std::cout << "Dynamically allocated single int: " << *singleInt << std::endl;
delete singleInt;
singleInt = nullptr;
return 0;
}
6. Strings
6.1. C-style Strings (Character Arrays)
Null-terminated character arrays. Less safe and flexible than std::string.
include <iostream>
#
#include <cstring> // For C-style string functions like strlen, strcpy
int main() {
// Declaring and initializing a C-style string
char greeting[20] = "Hello"; // Size 20, includes space for null terminator '\0'
char name[] = "World"; // Size automatically determined (6 including '\0')
s td::cout << "Greeting: " << greeting << std::endl;
std::cout << "Name: " << name << std::endl;
/ / Concatenating strings: strcat_s (safer version) or strcat (less safe)
// For competitive programming, often strcat is used, but for general code, use safer
versions
char combined[50];
strcpy(combined, greeting); // Copy "Hello" to combined
strcat(combined, " "); // Append space
strcat(combined, name); // Append "World"
strcat(combined, "!"); // Append "!"
std::cout << "Combined: " << combined << std::endl;
/ / Length of string: strlen
std::cout << "Length of combined: " << strlen(combined) << std::endl;
/ / Comparing strings: strcmp (returns 0 if equal)
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";
s td::cout << "Comparing 'apple' and 'banana': " << strcmp(str1, str2) << std::endl; //
Non-zero
std::cout << "Comparing 'apple' and 'apple': " << strcmp(str1, str3) << std::endl; //
Zero
return 0;
}
6.2. std::string
he preferred way to handle strings in modern C++. Safer, more flexible, and easier to
T
use.
include <iostream>
#
#include <string> // Required for std::string
int main() {
// Declaring and initializing std::string
std::string s1 = "Hello";
std::string s2("World");
std::string s3; // Empty string
s td::cout << "s1: " << s1 << std::endl;
std::cout << "s2: " << s2 << std::endl;
/ / Concatenation using + operator
s3 = s1 + " " + s2 + "!";
std::cout << "s3 (concatenated): " << s3 << std::endl;
/ / Appending using += operator
std::string message = "Good";
message += " Morning";
std::cout << "Message: " << message << std::endl;
/ / Length of string
std::cout << "Length of s3: " << s3.length() << std::endl; // or s3.size()
/ / Accessing characters (like an array)
std::cout << "First character of s3: " << s3[0] << std::endl;
std::cout << "Last character of s3: " << s3[s3.length() - 1] << std::endl;
/ / Substring
std::string sub = s3.substr(7, 5); // Start index 7, length 5
std::cout << "Substring (World): " << sub << std::endl;
/ / Finding a substring
size_t pos = s3.find("World"); // Returns position or std::string::npos if not found
if (pos != std::string::npos) {
std::cout << "'World' found at position: " << pos << std::endl;
} else {
std::cout << "'World' not found." << std::endl;
}
/ / Comparison
std::string strA = "apple";
std::string strB = "banana";
std::string strC = "apple";
if (strA == strC) {
std::cout << "strA and strC are equal." << std::endl;
}
if (strA != strB) {
std::cout << "strA and strB are not equal." << std::endl;
}
/ / Input with spaces (using getline)
std::string full_line;
std::cout << "Enter a full line of text: ";
std::getline(std::cin >> std::ws, full_line); // std::ws consumes leading whitespace
std::cout << "You entered: " << full_line << std::endl;
return 0;
}
7. Object-Oriented Programming (OOP) in C++
OP is a programming paradigm based on the concept of "objects", which can
O
contain data and code. The four pillars are Encapsulation, Inheritance, Polymorphism,
and Abstraction.
7.1. Classes and Objects
● Class:A blueprint or template for creating objects.It defines the properties (data
embers) and behaviors (member functions) that objects of its type will have.
m
Object:An instance of a class.
●
include <iostream>
#
#include <string>
/ / Define a class named 'Car'
class Car {
public: // Access specifier: members are accessible from outside the class
// Data members (attributes)
std::string brand;
std::string model;
int year;
/ / Member functions (behaviors)
void displayInfo() {
std::cout << "Brand: " << brand << ", Model: " << model << ", Year: " << year <<
std::endl;
}
void startEngine() {
std::cout << model << " engine started!" << std::endl;
}
}; // Don't forget the semicolon after class definition!
int main() {
// Create objects (instances) of the Car class
Car car1; // Object 1
Car car2; // Object 2
/ / Access data members and assign values using the dot operator (.)
car1.brand = "Toyota";
car1.model = "Camry";
car1.year = 2020;
ar2.brand = "Honda";
c
car2.model = "Civic";
car2.year = 2022;
/ / Call member functions using the dot operator
std::cout << "Car 1 Info: ";
car1.displayInfo();
car1.startEngine();
std::cout << "Car 2 Info: ";
ar2.displayInfo();
c
car2.startEngine();
return 0;
}
7.2. Constructors and Destructors
● Constructor:A special member function that is automaticallycalled when an
bject is created. Used for initializing object's data members. Has the same name
o
as the class and no return type.
Destructor:A special member function that is automaticallycalled when an
●
object is destroyed (goes out of scope). Used for cleaning up resources (e.g.,
freeing dynamically allocated memory). Has the same name as the class, prefixed
with a tilde (~), and no return type or parameters.
include <iostream>
#
#include <string>
lass Dog {
c
public:
std::string name;
int age;
/ / Default Constructor (no parameters)
Dog() {
name = "Unknown";
age = 0;
std::cout << "Default constructor called for " << name << std::endl;
}
/ / Parameterized Constructor
Dog(std::string n, int a) {
name = n;
age = a;
std::cout << "Parameterized constructor called for " << name << std::endl;
}
// Copy Constructor (creates a new object as a copy of an existing one)
Dog(const Dog& other) {
name = "Copy of " + other.name;
age = other.age;
std::cout << "Copy constructor called for " << name << std::endl;
}
/ / Destructor
~Dog() {
std::cout << "Destructor called for " << name << std::endl;
}
void bark() {
std::cout << name << " says Woof!" << std::endl;
}
};
int main() {
// Calls default constructor
Dog myDog;
myDog.bark();
std::cout << "\n--- Creating another dog ---\n";
/ / Calls parameterized constructor
Dog buddy("Buddy", 3);
buddy.bark();
std::cout << "\n--- Creating a copy ---\n";
/ / Calls copy constructor
Dog anotherDog = buddy; // Or Dog anotherDog(buddy);
anotherDog.bark();
s td::cout << "\n--- End of main function ---\n";
// Objects will be destroyed here in reverse order of creation (anotherDog, buddy,
myDog)
return 0;
}
7.3. Encapsulation (Access Specifiers)
ncapsulation is the bundling of data (attributes) and methods (functions) that
E
operate on the data into a single unit (class). It also involves restricting direct access
to some of an object's components, which is achieved using access specifiers:
● public: Members are accessible from anywhere.
● private: Members are only accessible from within the same class. (Default for
lasses)
c
protected: Members are accessible from within the same class and by derived
●
classes.
include <iostream>
#
#include <string>
lass BankAccount {
c
private: // Private members (data hiding)
std::string accountNumber;
double balance;
std::string ownerName;
public: // Public interface (controlled access)
// Constructor
BankAccount(std::string accNum, std::string owner, double initialBalance) {
accountNumber = accNum;
ownerName = owner;
if (initialBalance >= 0) {
balance = initialBalance;
} else {
balance = 0;
std::cout << "Initial balance cannot be negative. Setting to 0." << std::endl;
}
std::cout << "Account created for " << ownerName << std::endl;
}
/ / Public methods to interact with private data (getters and setters)
void deposit(double amount) {
if (amount > 0) {
balance += amount;
s td::cout << "Deposited: $" << amount << ". New balance: $" << balance <<
std::endl;
} else {
std::cout << "Deposit amount must be positive." << std::endl;
}
}
void withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
std::cout << "Withdrew: $" << amount << ". New balance: $" << balance <<
std::endl;
} else if (amount <= 0) {
std::cout << "Withdrawal amount must be positive." << std::endl;
} else {
std::cout << "Insufficient balance. Current balance: $" << balance << std::endl;
}
}
ouble getBalance() const { // 'const' means this method does not modify object's
d
state
return balance;
}
std::string getOwnerName() const {
return ownerName;
}
};
int main() {
BankAccount myAccount("123456789", "John Doe", 1000.0);
/ / Direct access to private members is NOT allowed:
// myAccount.balance = 5000.0; // ERROR! 'balance' is private
s td::cout << "Current balance for " << myAccount.getOwnerName() << ": $" <<
myAccount.getBalance() << std::endl;
myAccount.deposit(200.0);
yAccount.withdraw(150.0);
m
myAccount.withdraw(2000.0); // Insufficient balance
std::cout << "Final balance: $" << myAccount.getBalance() << std::endl;
return 0;
}
7.4. Inheritance
Inheritance allows a new class (derived or child class) to inherit properties and
behaviors from an existing class (base or parent class). This promotes code
reusability.
include <iostream>
#
#include <string>
/ / Base class
class Animal {
public:
std::string name;
int age;
Animal(std::string n, int a) : name(n), age(a) { // Initializer list for constructor
std::cout << "Animal constructor called for " << name << std::endl;
}
void eat() {
std::cout << name << " is eating." << std::endl;
}
void sleep() {
std::cout << name << " is sleeping." << std::endl;
}
};
/ / Derived class 'Dog' inherits from 'Animal' using 'public' inheritance
class Dog : public Animal {
public:
std::string breed;
/ / Dog constructor calls Animal's constructor using initializer list
Dog(std::string n, int a, std::string b) : Animal(n, a), breed(b) {
std::cout << "Dog constructor called for " << name << std::endl;
}
void bark() {
std::cout << name << " (" << breed << ") says Woof!" << std::endl;
}
// Dog can also use inherited methods like eat() and sleep()
};
/ / Another derived class 'Cat'
class Cat : public Animal {
public:
Cat(std::string n, int a) : Animal(n, a) {
std::cout << "Cat constructor called for " << name << std::endl;
}
void meow() {
std::cout << name << " says Meow!" << std::endl;
}
};
int main() {
Dog myDog("Buddy", 3, "Golden Retriever");
myDog.eat(); // Inherited from Animal
myDog.sleep(); // Inherited from Animal
myDog.bark(); // Specific to Dog
std::cout << std::endl;
at myCat("Whiskers", 2);
C
myCat.eat(); // Inherited from Animal
myCat.meow(); // Specific to Cat
return 0;
}
7.5. Polymorphism (Function Overriding, Virtual Functions)
olymorphism means "many forms". In C++, it allows objects of different classes to be
P
treated as objects of a common base class. This is achieved primarily throughvirtual
functions.
● Function Overriding:When a derived class providesa specific implementation
f or a function that is already defined in its base class.
Virtual Functions:A function declared with the virtualkeyword in the base class.
●
It ensures that the correct overridden function is called based on theactual
object typeat runtime, not the pointer/referencetype. This is known asruntime
polymorphismordynamic dispatch.
include <iostream>
#
#include <string>
lass Shape {
c
public:
std::string name;
Shape(std::string n) : name(n) {}
/ / Declare 'draw' as a virtual function
// This allows derived classes to provide their own implementation,
// and the correct version will be called at runtime via a base class pointer/reference.
virtual void draw() {
std::cout << "Drawing a generic Shape: " << name << std::endl;
}
/ / Virtual destructor is good practice when you have virtual functions
virtual ~Shape() {
std::cout << "Shape destructor called for " << name << std::endl;
}
};
lass Circle : public Shape {
c
public:
double radius;
Circle(std::string n, double r) : Shape(n), radius(r) {}
/ / Override the draw function
void draw() override { // 'override' keyword (C++11) is optional but good practice for
clarity
std::cout << "Drawing a Circle: " << name << " with radius " << radius << std::endl;
}
~Circle() override {
std::cout << "Circle destructor called for " << name << std::endl;
}
};
lass Rectangle : public Shape {
c
public:
double width;
double height;
Rectangle(std::string n, double w, double h) : Shape(n), width(w), height(h) {}
/ / Override the draw function
void draw() override {
std::cout << "Drawing a Rectangle: " << name << " with width " << width << " and
height " << height << std::endl;
}
~Rectangle() override {
std::cout << "Rectangle destructor called for " << name << std::endl;
}
};
int main() {
// Base class pointer pointing to derived class objects
Shape* s1 = new Circle("My Circle", 5.0);
Shape* s2 = new Rectangle("My Rectangle", 4.0, 6.0);
Shape* s3 = new Shape("Generic Shape");
// Calling draw() through base class pointers
/ / Due to 'virtual' keyword, the correct derived class's draw() is called at runtime.
s1->draw(); // Calls Circle::draw()
s2->draw(); // Calls Rectangle::draw()
s3->draw(); // Calls Shape::draw()
/ / Clean up dynamically allocated memory
delete s1;
delete s2;
delete s3;
s td::cout << "\n--- Without Virtual Function (for comparison) ---" << std::endl;
// If 'draw' was NOT virtual in Shape:
// Shape* s4 = new Circle("Another Circle", 7.0);
// s4->draw(); // Would call Shape::draw() instead of Circle::draw() - Slicing problem
// delete s4;
return 0;
}
7.6. Abstraction (Abstract Classes and Pure Virtual Functions)
bstraction means showing only essential information and hiding the implementation
A
details. In C++, this is achieved usingabstract classesandpure virtual functions.
● Pure Virtual Function:A virtual function declaredwith = 0; in the base class. It
has no implementation in the base class.
○ Example: virtual void draw() = 0;
Abstract Class:A class that contains at least onepure virtual function.
●
○ Youcannot create objectsof an abstract class.
○ Derived classesmust implementall pure virtual functionsfrom the abstract
base class, or they too become abstract.
include <iostream>
#
#include <string>
/ / Abstract Base Class
class Vehicle {
public:
std::string make;
std::string model;
Vehicle(std::string m, std::string mod) : make(m), model(mod) {}
/ / Pure virtual function: Forces derived classes to implement this.
// A class with at least one pure virtual function is an abstract class.
virtual void startEngine() = 0;
/ / Regular virtual function (can be overridden, but not mandatory)
virtual void drive() {
std::cout << make << " " << model << " is driving." << std::endl;
}
/ / Virtual destructor for proper cleanup in polymorphic hierarchies
virtual ~Vehicle() {
std::cout << "Vehicle destructor called for " << make << " " << model << std::endl;
}
};
/ / Concrete Derived Class (must implement startEngine)
class Car : public Vehicle {
public:
Car(std::string m, std::string mod) : Vehicle(m, mod) {}
/ / Implementing the pure virtual function
void startEngine() override {
std::cout << make << " " << model << " Car engine starts with a key turn." <<
std::endl;
}
/ / Can optionally override drive()
void drive() override {
std::cout << make << " " << model << " Car is cruising on the highway." << std::endl;
}
~Car() override {
std::cout << "Car destructor called for " << make << " " << model << std::endl;
}
};
/ / Another Concrete Derived Class
class Motorcycle : public Vehicle {
public:
Motorcycle(std::string m, std::string mod) : Vehicle(m, mod) {}
/ / Implementing the pure virtual function
void startEngine() override {
std::cout << make << " " << model << " Motorcycle engine starts with a kick." <<
std::endl;
}
~Motorcycle() override {
std::cout << "Motorcycle destructor called for " << make << " " << model <<
std::endl;
}
};
int main() {
// Vehicle myVehicle; // ERROR: Cannot create an object of an abstract class
ar myCar("Toyota", "Camry");
C
Motorcycle myBike("Harley-Davidson", "Iron 883");
/ / Using base class pointers for polymorphic behavior
Vehicle* v1 = &myCar;
Vehicle* v2 = &myBike;
v 1->startEngine(); // Calls Car::startEngine()
v1->drive(); // Calls Car::drive()
std::cout << std::endl;
v 2->startEngine(); // Calls Motorcycle::startEngine()
v2->drive(); // Calls Vehicle::drive() (Motorcycle didn't override it)
/ / Example with dynamic allocation (remember to delete!)
Vehicle* anotherCar = new Car("Honda", "Civic");
anotherCar->startEngine();
delete anotherCar; // Calls Car destructor, then Vehicle destructor
return 0;
}
8. Standard Template Library (STL) - Your DSA Powerhouse
he STL is a set of C++ template classes that provide common programming data
T
structures and functions. It's a must-know for DSA.
8.1. Containers
Containers are objects that store collections of other objects (elements).
8.1.1. std::vector
A dynamic array that can grow or shrink in size. Very versatile and commonly used.
include <iostream>
#
#include <vector>
#include <algorithm> // For std::sort
int main() {
// Declare a vector of integers
std::vector<int> numbers;
/ / Add elements to the end (dynamic resizing)
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(5);
numbers.push_back(30);
s td::cout << "Vector elements: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Access elements (like an array)
std::cout << "First element: " << numbers[0] << std::endl;
std::cout << "Second element (at): " << numbers.at(1) << std::endl; // at() provides
ounds checking
b
/ / Size and Capacity
std::cout << "Size of vector: " << numbers.size() << std::endl;
std::cout << "Capacity of vector: " << numbers.capacity() << std::endl; // Memory
allocated
/ / Iterate using traditional for loop
std::cout << "Elements using traditional loop: ";
for (size_t i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;
/ / Check if empty
if (numbers.empty()) {
std::cout << "Vector is empty." << std::endl;
} else {
std::cout << "Vector is not empty." << std::endl;
}
/ / Remove last element
numbers.pop_back();
std::cout << "After pop_back: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Insert element at a specific position (expensive for vectors)
// numbers.begin() gives an iterator to the first element
numbers.insert(numbers.begin() + 1, 15); // Insert 15 at index 1
std::cout << "After inserting 15 at index 1: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Erase element at a specific position
numbers.erase(numbers.begin()); // Erase first element
s td::cout << "After erasing first element: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Sort the vector (requires <algorithm>)
std::sort(numbers.begin(), numbers.end());
std::cout << "After sorting: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Clear all elements
numbers.clear();
std::cout << "After clearing, size: " << numbers.size() << std::endl;
return 0;
}
8.1.2. std::list
doubly linked list. Good for frequent insertions/deletions anywhere, but slow random
A
access.
include <iostream>
#
#include <list>
#include <algorithm> // For std::sort
int main() {
std::list<int> myList;
/ / Add elements
myList.push_back(10); // Add to end
myList.push_front(5); // Add to beginning
myList.push_back(20);
myList.push_front(1);
s td::cout << "List elements: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 1 5 10 20
/ / Access front and back elements
std::cout << "Front element: " << myList.front() << std::endl;
std::cout << "Back element: " << myList.back() << std::endl;
/ / Remove elements
myList.pop_front(); // Remove from beginning
myList.pop_back(); // Remove from end
std::cout << "After pop_front and pop_back: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 5 10
/ / Insert at specific position (using iterator)
auto it = myList.begin(); // Iterator to first element (5)
it++; // Iterator to second element (10)
myList.insert(it, 7); // Insert 7 before 10
std::cout << "After inserting 7: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 5 7 10
/ / Erase at specific position (using iterator)
it = myList.begin();
it++; // Iterator to 7
myList.erase(it); // Erase 7
std::cout << "After erasing 7: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 5 10
/ / Sort the list (list has its own sort method, more efficient for lists)
myList.sort();
std::cout << "After sorting: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl;
/ / Reverse the list
myList.reverse();
std::cout << "After reversing: ";
for (int val : myList) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
8.1.3. std::deque (Double-Ended Queue)
double-ended queue. Supports efficient insertion and deletion at both ends. Good
A
for implementing queues or deques.
include <iostream>
#
#include <deque>
int main() {
std::deque<int> myDeque;
/ / Add elements to front and back
myDeque.push_back(10);
myDeque.push_front(5);
myDeque.push_back(15);
myDeque.push_front(2);
s td::cout << "Deque elements: ";
for (int val : myDeque) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 2 5 10 15
/ / Access elements (like vector)
std::cout << "Front element: " << myDeque.front() << std::endl;
std::cout << "Back element: " << myDeque.back() << std::endl;
std::cout << "Element at index 1: " << myDeque[1] << std::endl; // Output: 5
/ / Remove elements from front and back
myDeque.pop_front();
myDeque.pop_back();
std::cout << "After pop_front and pop_back: ";
for (int val : myDeque) {
std::cout << val << " ";
}
std::cout << std::endl; // Output: 5 10
return 0;
}
8.1.4. std::map and std::unordered_map
● std::map: Stores key-value pairs in sorted order ofkeys (implemented using a
s elf-balancing binary search tree, usually Red-Black Tree).
std::unordered_map: Stores key-value pairs in an unorderedfashion
●
(implemented using hash table). Provides average O(1) complexity for insertion,
deletion, and access.
include <iostream>
#
#include <map>
#include <string>
#include <unordered_map>
int main() {
// --- std::map (sorted by key) ---
std::map<std::string, int> studentScores;
/ / Insert elements
studentScores["Alice"] = 95;
s tudentScores["Bob"] = 88;
studentScores["Charlie"] = 92;
studentScores.insert({"David", 78}); // Another way to insert
s td::cout << "--- std::map (sorted order by key) ---" << std::endl;
// Iterate through map (elements are sorted by key)
for (const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
/ / Accessing values
std::cout << "Alice's score: " << studentScores["Alice"] << std::endl;
// If key doesn't exist, it will be inserted with default value (0 for int)
std::cout << "Eve's score (newly inserted): " << studentScores["Eve"] << std::endl;
/ / Check if a key exists
if (studentScores.count("Bob")) { // Returns 1 if exists, 0 otherwise
std::cout << "Bob is in the map." << std::endl;
}
/ / Erase an element
studentScores.erase("Eve");
std::cout << "After erasing Eve, map size: " << studentScores.size() << std::endl;
/ / --- std::unordered_map (unordered, average O(1)) ---
std::unordered_map<std::string, int> cityPopulations;
ityPopulations["New York"] = 8000000;
c
cityPopulations["London"] = 9000000;
cityPopulations["Tokyo"] = 14000000;
s td::cout << "\n--- std::unordered_map (unordered) ---" << std::endl;
// Iterate through unordered_map (order is not guaranteed)
for (const auto& pair : cityPopulations) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
std::cout << "Population of Tokyo: " << cityPopulations["Tokyo"] << std::endl;
return 0;
}
8.1.5. std::set and std::unordered_set
● std::set: Stores unique elements in sorted order (implementedusing a
s elf-balancing binary search tree).
std::unordered_set: Stores unique elements in an unorderedfashion
●
(implemented using hash table). Provides average O(1) complexity for insertion,
deletion, and lookup.
include <iostream>
#
#include <set>
#include <string>
#include <unordered_set>
int main() {
// --- std::set (sorted unique elements) ---
std::set<int> uniqueNumbers;
/ / Insert elements
uniqueNumbers.insert(10);
uniqueNumbers.insert(5);
uniqueNumbers.insert(20);
uniqueNumbers.insert(5); // Duplicate, will not be inserted
s td::cout << "--- std::set (sorted unique elements) ---" << std::endl;
// Iterate through set (elements are sorted)
for (int num : uniqueNumbers) {
std::cout << num << " ";
}
std::cout << std::endl; // Output: 5 10 20
/ / Check if an element exists
if (uniqueNumbers.count(10)) { // Returns 1 if exists, 0 otherwise
std::cout << "10 is in the set." << std::endl;
}
if (uniqueNumbers.find(25) == uniqueNumbers.end()) { // find() returns iterator to
end if not found
std::cout << "25 is not in the set." << std::endl;
}
/ / Erase an element
uniqueNumbers.erase(10);
std::cout << "After erasing 10: ";
for (int num : uniqueNumbers) {
std::cout << num << " ";
}
std::cout << std::endl; // Output: 5 20
/ / --- std::unordered_set (unordered unique elements, average O(1)) ---
std::unordered_set<std::string> uniqueWords;
niqueWords.insert("apple");
u
uniqueWords.insert("banana");
uniqueWords.insert("apple"); // Duplicate, will not be inserted
uniqueWords.insert("orange");
s td::cout << "\n--- std::unordered_set (unordered unique elements) ---" <<
std::endl;
// Iterate through unordered_set (order is not guaranteed)
for (const std::string& word : uniqueWords) {
std::cout << word << " ";
}
std::cout << std::endl;
if (uniqueWords.count("banana")) {
std::cout << "banana is in the unordered set." << std::endl;
}
return 0;
}
8.1.6. std::stack
A LIFO (Last-In, First-Out) container adapter. Think of a stack of plates.
include <iostream>
#
#include <stack> // For std::stack
int main() {
std::stack<int> myStack;
/ / Push elements onto the stack
myStack.push(10); // 10 is at bottom
myStack.push(20);
myStack.push(30); // 30 is at top
std::cout << "Stack size: " << myStack.size() << std::endl;
/ / Access top element
std::cout << "Top element: " << myStack.top() << std::endl; // Output: 30
/ / Pop elements (removes from top)
myStack.pop(); // Removes 30
std::cout << "After pop, new top: " << myStack.top() << std::endl; // Output: 20
/ / Iterate and pop all elements (note: cannot directly iterate a stack)
std::cout << "Popping all elements: ";
while (!myStack.empty()) {
std::cout << myStack.top() << " ";
myStack.pop();
}
std::cout << std::endl; // Output: 20 10 (LIFO)
std::cout << "Is stack empty? " << (myStack.empty() ? "Yes" : "No") << std::endl;
return 0;
}
8.1.7. std::queue
A FIFO (First-In, First-Out) container adapter. Think of a line at a store.
include <iostream>
#
#include <queue> // For std::queue
int main() {
std::queue<std::string> myQueue;
/ / Push elements onto the queue
myQueue.push("Alice"); // Alice is first in line
myQueue.push("Bob");
myQueue.push("Charlie"); // Charlie is last in line
std::cout << "Queue size: " << myQueue.size() << std::endl;
/ / Access front and back elements
std::cout << "Front element: " << myQueue.front() << std::endl; // Output: Alice
std::cout << "Back element: " << myQueue.back() << std::endl; // Output: Charlie
/ / Pop elements (removes from front)
myQueue.pop(); // Removes Alice
std::cout << "After pop, new front: " << myQueue.front() << std::endl; // Output: Bob
/ / Iterate and pop all elements
std::cout << "Processing queue: ";
while (!myQueue.empty()) {
std::cout << myQueue.front() << " ";
myQueue.pop();
}
std::cout << std::endl; // Output: Bob Charlie (FIFO)
std::cout << "Is queue empty? " << (myQueue.empty() ? "Yes" : "No") << std::endl;
return 0;
}
8.1.8. std::priority_queue
container adapter that provides constant time lookup of the largest (or smallest)
A
element. Elements are retrieved in order of priority (largest by default). Implemented
using a heap.
include <iostream>
#
#include <queue> // For std::priority_queue
#include <vector> // Default underlying container for priority_queue
int main() {
// Max-heap (default): largest element has highest priority
std::priority_queue<int> maxHeap;
axHeap.push(10);
m
maxHeap.push(30);
maxHeap.push(20);
maxHeap.push(5);
s td::cout << "Max-Heap elements (popped in order): ";
while (!maxHeap.empty()) {
std::cout << maxHeap.top() << " "; // Access the largest element
maxHeap.pop(); // Remove the largest element
}
std::cout << std::endl; // Output: 30 20 10 5
/ / Min-heap: smallest element has highest priority
// Use std::greater<int> as the comparator
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
inHeap.push(10);
m
minHeap.push(30);
minHeap.push(20);
minHeap.push(5);
s td::cout << "Min-Heap elements (popped in order): ";
while (!minHeap.empty()) {
std::cout << minHeap.top() << " "; // Access the smallest element
minHeap.pop(); // Remove the smallest element
}
std::cout << std::endl; // Output: 5 10 20 30
return 0;
}
8.2. Iterators
Iterators are like pointers that allow you to traverse through containers.
include <iostream>
#
#include <vector>
#include <list>
#include <map>
int main() {
std::vector<int> numbers = {10, 20, 30, 40, 50};
/ / Get an iterator to the beginning of the vector
std::vector<int>::iterator it_vec = numbers.begin();
s td::cout << "Vector elements using iterator: ";
while (it_vec != numbers.end()) { // Iterate until the end iterator
std::cout << *it_vec << " "; // Dereference iterator to get value
it_vec++; // Move to the next element
}
std::cout << std::endl;
/ / Const iterator (for read-only access)
std::vector<int>::const_iterator const_it_vec = numbers.cbegin();
std::cout << "Vector elements using const iterator: ";
while (const_it_vec != numbers.cend()) {
std::cout << *const_it_vec << " ";
// *const_it_vec = 100; // ERROR: Cannot modify value through const iterator
const_it_vec++;
}
std::cout << std::endl;
s td::list<std::string> names = {"Alice", "Bob", "Charlie"};
std::list<std::string>::iterator it_list = names.begin();
std::cout << "List elements using iterator: ";
for (; it_list != names.end(); ++it_list) {
std::cout << *it_list << " ";
}
std::cout << std::endl;
s td::map<char, int> grades = {{'A', 90}, {'B', 80}, {'C', 70}};
std::map<char, int>::iterator it_map = grades.begin();
std::cout << "Map elements using iterator: ";
for (; it_map != grades.end(); ++it_map) {
std::cout << it_map->first << ":" << it_map->second << " "; // Access key and value
}
std::cout << std::endl;
/ / Reverse iterators (iterate from end to beginning)
std::cout << "Vector elements in reverse using reverse iterator: ";
for (std::vector<int>::reverse_iterator rit = numbers.rbegin(); rit != numbers.rend();
++rit) {
std::cout << *rit << " ";
}
std::cout << std::endl;
return 0;
}
8.3. Algorithms
he <algorithm> header provides a rich set of functions for common operations on
T
ranges (like containers).
include <iostream>
#
#include <vector>
#include <algorithm> // For sort, find, reverse, min_element, max_element, etc.
#include <numeric> // For std::accumulate
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9, 4, 7, 3, 6};
s td::cout << "Original vector: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Sorting
std::sort(numbers.begin(), numbers.end()); // Sorts in ascending order
std::cout << "Sorted vector: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Reversing
std::reverse(numbers.begin(), numbers.end());
std::cout << "Reversed vector: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
/ / Finding an element
auto it_find = std::find(numbers.begin(), numbers.end(), 5);
if (it_find != numbers.end()) {
std::cout << "Found 5 at index: " << std::distance(numbers.begin(), it_find) <<
std::endl;
} else {
std::cout << "5 not found." << std::endl;
}
/ / Min and Max elements
auto min_val_it = std::min_element(numbers.begin(), numbers.end());
auto max_val_it = std::max_element(numbers.begin(), numbers.end());
std::cout << "Min element: " << *min_val_it << std::endl;
std::cout << "Max element: " << *max_val_it << std::endl;
/ / Sum of elements (using std::accumulate from <numeric>)
int sum = std::accumulate(numbers.begin(), numbers.end(), 0); // 0 is initial sum
std::cout << "Sum of elements: " << sum << std::endl;
/ / Count occurrences
int count_fives = std::count(numbers.begin(), numbers.end(), 5);
std::cout << "Number of 5s: " << count_fives << std::endl;
/ / Check if all elements satisfy a condition
bool all_positive = std::all_of(numbers.begin(), numbers.end(), [](int i){ return i > 0;
});
std::cout << "Are all elements positive? " << (all_positive ? "Yes" : "No") << std::endl;
/ / Lambda functions (used with algorithms for custom logic)
// Sort in descending order using a lambda
std::sort(numbers.begin(), numbers.end(), [](int a, int b){
return a > b; // Custom comparison for descending
});
std::cout << "Sorted descending: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
9. Recursion
ecursion is a programming technique where a function calls itself to solve a smaller
R
instance of the same problem. Essential for many DSA problems (trees, graphs,
dynamic programming).
Key components of a recursive function:
1. Base Case:The condition that stops the recursion.Without it, the function would
all itself indefinitely, leading to a stack overflow.
c
. Recursive Step:The part where the function callsitself with a modified (smaller)
2
input.
#include <iostream>
/ / Example 1: Factorial calculation
// n! = n * (n-1)!
// Base case: 0! = 1
long long factorial(int n) {
/ / Base case
if (n == 0 || n == 1) {
return 1;
}
// Recursive step
return n * factorial(n - 1);
}
/ / Example 2: Fibonacci sequence
// F(n) = F(n-1) + F(n-2)
// Base cases: F(0) = 0, F(1) = 1
int fibonacci(int n) {
// Base cases
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
// Recursive step
return fibonacci(n - 1) + fibonacci(n - 2);
}
/ / Example 3: Sum of digits of a number
// sumDigits(123) = 3 + sumDigits(12)
// Base case: sumDigits(0) = 0
int sumDigits(int n) {
// Base case
if (n == 0) {
return 0;
}
// Recursive step
return (n % 10) + sumDigits(n / 10);
}
int main() {
std::cout << "Factorial of 5: " << factorial(5) << std::endl; // 5 * 4 * 3 * 2 * 1 = 120
std::cout << "Factorial of 0: " << factorial(0) << std::endl; // 1
s td::cout << "\nFibonacci sequence up to 10 terms: " << std::endl;
for (int i = 0; i < 10; ++i) {
std::cout << fibonacci(i) << " ";
}
std::cout << std::endl; // Output: 0 1 1 2 3 5 8 13 21 34
s td::cout << "\nSum of digits of 456: " << sumDigits(456) << std::endl; // 4 + 5 + 6 = 15
std::cout << "Sum of digits of 12345: " << sumDigits(12345) << std::endl; // 1 + 2 + 3 +
+ 5 = 15
4
return 0;
}
10. Basic Data Structures
nderstanding how basic data structures work is fundamental, even if you use STL
U
containers.
10.1. Linked Lists
linear data structure where elements are not stored in contiguous memory
A
locations. Each element (node) contains data and a pointer to the next node.
#include <iostream>
/ / Define a Node structure for the linked list
struct Node {
int data;
Node* next; // Pointer to the next node
/ / Constructor for Node
Node(int val) : data(val), next(nullptr) {}
};
/ / Class for Linked List operations
class LinkedList {
private:
Node* head; // Pointer to the first node of the list
public:
/ / Constructor
LinkedList() : head(nullptr) {}
/ / Destructor to free memory
~LinkedList() {
Node* current = head;
while (current != nullptr) {
Node* nextNode = current->next;
delete current;
current = nextNode;
}
head = nullptr; // Ensure head is null after deletion
std::cout << "Linked List destroyed." << std::endl;
}
/ / Function to insert a new node at the beginning
void insertAtBeginning(int val) {
Node* newNode = new Node(val);
newNode->next = head; // New node points to current head
head = newNode; // Head becomes the new node
std::cout << "Inserted " << val << " at beginning." << std::endl;
}
/ / Function to insert a new node at the end
void insertAtEnd(int val) {
Node* newNode = new Node(val);
if (head == nullptr) { // If list is empty
head = newNode;
return;
}
Node* current = head;
while (current->next != nullptr) { // Traverse to the last node
current = current->next;
}
current->next = newNode; // Last node points to new node
std::cout << "Inserted " << val << " at end." << std::endl;
}
// Function to delete a node with a specific value
void deleteNode(int val) {
if (head == nullptr) {
std::cout << "List is empty. Cannot delete " << val << std::endl;
return;
}
/ / If head node needs to be deleted
if (head->data == val) {
Node* temp = head;
head = head->next;
delete temp;
std::cout << "Deleted " << val << " from beginning." << std::endl;
return;
}
ode* current = head;
N
Node* prev = nullptr;
while (current != nullptr && current->data != val) {
prev = current;
current = current->next;
}
if (current == nullptr) { // Value not found
std::cout << val << " not found in the list." << std::endl;
} else { // Value found
prev->next = current->next; // Bypass the current node
delete current;
std::cout << "Deleted " << val << "." << std::endl;
}
}
/ / Function to display the linked list
void displayList() {
if (head == nullptr) {
std::cout << "List is empty." << std::endl;
return;
}
Node* current = head;
std::cout << "Linked List: ";
while (current != nullptr) {
std::cout << current->data << " -> ";
current = current->next;
}
std::cout << "nullptr" << std::endl;
}
/ / Function to search for a value
bool search(int val) {
Node* current = head;
while (current != nullptr) {
if (current->data == val) {
return true;
}
current = current->next;
}
return false;
}
};
int main() {
LinkedList myList;
yList.insertAtEnd(10);
m
myList.insertAtBeginning(5);
myList.insertAtEnd(20);
myList.insertAtBeginning(1);
myList.displayList(); // Output: 1 -> 5 -> 10 -> 20 -> nullptr
s td::cout << "Searching for 10: " << (myList.search(10) ? "Found" : "Not Found") <<
std::endl;
std::cout << "Searching for 100: " << (myList.search(100) ? "Found" : "Not Found")
<< std::endl;
yList.deleteNode(10);
m
myList.displayList(); // Output: 1 -> 5 -> 20 -> nullptr
yList.deleteNode(1);
m
myList.displayList(); // Output: 5 -> 20 -> nullptr
yList.deleteNode(25); // Not found
m
myList.displayList(); // Output: 5 -> 20 -> nullptr
yList.deleteNode(20);
m
myList.displayList(); // Output: 5 -> nullptr
yList.deleteNode(5);
m
myList.displayList(); // Output: List is empty.
return 0;
}
10.2. Stacks (Manual Implementation)
A LIFO data structure. Can be implemented using arrays or linked lists.
include <iostream>
#
#include <vector> // Using vector as underlying storage for simplicity
lass MyStack {
c
private:
std::vector<int> elements;
public:
// Push an element onto the stack
void push(int val) {
elements.push_back(val);
std::cout << "Pushed: " << val << std::endl;
}
/ / Pop an element from the stack
int pop() {
if (empty()) {
std::cerr << "Error: Stack is empty. Cannot pop." << std::endl;
return -1; // Or throw an exception
}
int top_val = elements.back();
elements.pop_back();
s td::cout << "Popped: " << top_val << std::endl;
return top_val;
}
/ / Get the top element without removing it
int top() {
if (empty()) {
std::cerr << "Error: Stack is empty. No top element." << std::endl;
return -1; // Or throw an exception
}
return elements.back();
}
/ / Check if the stack is empty
bool empty() const {
return elements.empty();
}
/ / Get the size of the stack
size_t size() const {
return elements.size();
}
};
int main() {
MyStack s;
s .push(10);
s.push(20);
s.push(30);
s td::cout << "Current top element: " << s.top() << std::endl; // Output: 30
std::cout << "Stack size: " << s.size() << std::endl; // Output: 3
s .pop(); // Popped: 30
std::cout << "Current top element: " << s.top() << std::endl; // Output: 20
s .pop(); // Popped: 20
s.pop(); // Popped: 10
s.pop(); // Error: Stack is empty. Cannot pop.
s td::cout << "Is stack empty? " << (s.empty() ? "Yes" : "No") << std::endl; // Output:
Yes
return 0;
}
10.3. Queues (Manual Implementation)
A FIFO data structure. Can be implemented using arrays or linked lists.
include <iostream>
#
#include <list> // Using std::list as underlying storage for simplicity (efficient front/back
operations)
lass MyQueue {
c
private:
std::list<int> elements;
public:
// Enqueue (add to back)
void enqueue(int val) {
elements.push_back(val);
std::cout << "Enqueued: " << val << std::endl;
}
/ / Dequeue (remove from front)
int dequeue() {
if (empty()) {
std::cerr << "Error: Queue is empty. Cannot dequeue." << std::endl;
return -1; // Or throw an exception
}
int front_val = elements.front();
elements.pop_front();
std::cout << "Dequeued: " << front_val << std::endl;
return front_val;
}
/ / Get front element without removing
int front() {
if (empty()) {
std::cerr << "Error: Queue is empty. No front element." << std::endl;
return -1; // Or throw an exception
}
return elements.front();
}
/ / Check if queue is empty
bool empty() const {
return elements.empty();
}
/ / Get size of queue
size_t size() const {
return elements.size();
}
};
int main() {
MyQueue q;
.enqueue(10);
q
q.enqueue(20);
q.enqueue(30);
s td::cout << "Current front element: " << q.front() << std::endl; // Output: 10
std::cout << "Queue size: " << q.size() << std::endl; // Output: 3
.dequeue(); // Dequeued: 10
q
std::cout << "Current front element: " << q.front() << std::endl; // Output: 20
.dequeue(); // Dequeued: 20
q
q.dequeue(); // Dequeued: 30
q.dequeue(); // Error: Queue is empty. Cannot dequeue.
s td::cout << "Is queue empty? " << (q.empty() ? "Yes" : "No") << std::endl; // Output:
Yes
return 0;
}
10.4. Trees (Binary Tree, Binary Search Tree)
Binary Tree
tree data structure in which each node has at most two children, referred to as the
A
left child and the right child.
include <iostream>
#
#include <queue> // For level order traversal
/ / Node structure for a Binary Tree
struct TreeNode {
int data;
TreeNode* left;
TreeNode* right;
TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
/ / Functions for Binary Tree traversals
void inorderTraversal(TreeNode* node) {
if (node == nullptr) {
return;
}
inorderTraversal(node->left);
std::cout << node->data << " ";
inorderTraversal(node->right);
}
void preorderTraversal(TreeNode* node) {
if (node == nullptr) {
return;
}
std::cout << node->data << " ";
preorderTraversal(node->left);
preorderTraversal(node->right);
}
void postorderTraversal(TreeNode* node) {
if (node == nullptr) {
return;
}
postorderTraversal(node->left);
postorderTraversal(node->right);
std::cout << node->data << " ";
}
void levelOrderTraversal(TreeNode* root) {
if (root == nullptr) {
return;
}
std::queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* current = q.front();
q.pop();
std::cout << current->data << " ";
if (current->left != nullptr) {
q.push(current->left);
}
if (current->right != nullptr) {
q.push(current->right);
}
}
}
/ / Function to delete the entire tree (post-order traversal for deletion)
void deleteTree(TreeNode* node) {
if (node == nullptr) {
return;
}
deleteTree(node->left);
deleteTree(node->right);
s td::cout << "Deleting node: " << node->data << std::endl;
delete node;
}
int main() {
// Constructing a sample Binary Tree:
// 1
// / \
// 2 3
// / \
// 4 5
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);
s td::cout << "Inorder Traversal (Left, Root, Right): ";
inorderTraversal(root); // Output: 4 2 5 1 3
std::cout << std::endl;
s td::cout << "Preorder Traversal (Root, Left, Right): ";
preorderTraversal(root); // Output: 1 2 4 5 3
std::cout << std::endl;
s td::cout << "Postorder Traversal (Left, Right, Root): ";
postorderTraversal(root); // Output: 4 5 2 3 1
std::cout << std::endl;
s td::cout << "Level Order Traversal: ";
levelOrderTraversal(root); // Output: 1 2 3 4 5
std::cout << std::endl;
/ / Clean up memory
deleteTree(root);
root = nullptr; // Important to set to nullptr after deletion
return 0;
}
Binary Search Tree (BST)
binary tree where for each node, all elements in its left subtree are less than the
A
node's data, and all elements in its right subtree are greater than the node's data.
#include <iostream>
/ / Node structure for a Binary Search Tree
struct BSTNode {
int data;
BSTNode* left;
BSTNode* right;
BSTNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
/ / Function to insert a new node into BST
BSTNode* insert(BSTNode* root, int val) {
if (root == nullptr) {
return new BSTNode(val);
}
if (val < root->data) {
root->left = insert(root->left, val);
} else if (val > root->data) {
root->right = insert(root->right, val);
}
// If val == root->data, do nothing (assuming unique values, or handle duplicates)
return root;
}
/ / Function to search for a value in BST
BSTNode* search(BSTNode* root, int val) {
if (root == nullptr || root->data == val) {
return root;
}
if (val < root->data) {
return search(root->left, val);
} else {
return search(root->right, val);
}
}
/ / Function to find the minimum value node in a BST (leftmost node)
BSTNode* findMin(BSTNode* node) {
BSTNode* current = node;
while (current && current->left != nullptr) {
current = current->left;
}
return current;
}
/ / Function to delete a node from BST
BSTNode* deleteNode(BSTNode* root, int key) {
if (root == nullptr) {
return root;
}
/ / Traverse to find the node to be deleted
if (key < root->data) {
root->left = deleteNode(root->left, key);
} else if (key > root->data) {
root->right = deleteNode(root->right, key);
} else { // Node with only one child or no child
if (root->left == nullptr) {
BSTNode* temp = root->right;
delete root;
return temp;
} else if (root->right == nullptr) {
BSTNode* temp = root->left;
delete root;
return temp;
}
// Node with two children: Get the inorder successor (smallest in the right
subtree)
BSTNode* temp = findMin(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;
}
/ / Inorder traversal (prints sorted elements for BST)
void inorderTraversalBST(BSTNode* node) {
if (node == nullptr) {
return;
}
inorderTraversalBST(node->left);
std::cout << node->data << " ";
inorderTraversalBST(node->right);
}
/ / Function to delete the entire BST
void deleteBST(BSTNode* node) {
if (node == nullptr) {
return;
}
deleteBST(node->left);
deleteBST(node->right);
delete node;
}
int main() {
BSTNode* root = nullptr;
root = insert(root, 50);
insert(root, 30);
insert(root, 20);
insert(root, 40);
insert(root, 70);
insert(root, 60);
insert(root, 80);
s td::cout << "Inorder traversal of BST: ";
inorderTraversalBST(root); // Output: 20 30 40 50 60 70 80 (sorted!)
std::cout << std::endl;
/ / Search
if (search(root, 40) != nullptr) {
std::cout << "40 found in BST." << std::endl;
} else {
std::cout << "40 not found." << std::endl;
}
if (search(root, 90) != nullptr) {
std::cout << "90 found in BST." << std::endl;
} else {
std::cout << "90 not found in BST." << std::endl;
}
/ / Delete nodes
std::cout << "\nDeleting 20 (leaf node):" << std::endl;
root = deleteNode(root, 20);
std::cout << "Inorder traversal: ";
inorderTraversalBST(root);
std::cout << std::endl;
s td::cout << "\nDeleting 70 (node with one child):" << std::endl;
root = deleteNode(root, 70);
std::cout << "Inorder traversal: ";
inorderTraversalBST(root);
std::cout << std::endl;
s td::cout << "\nDeleting 50 (root node with two children):" << std::endl;
root = deleteNode(root, 50);
std::cout << "Inorder traversal: ";
inorderTraversalBST(root);
std::cout << std::endl;
/ / Clean up memory
deleteBST(root);
root = nullptr;
return 0;
}
10.5. Graphs (Adjacency List/Matrix)
A graph is a non-linear data structure consisting of nodes (vertices) and edges.
● Adjacency Matrix:A 2D array where matrix[i][j] is1 if there's an edge between
v ertex i and j, else 0. Good for dense graphs, but uses more space for sparse
graphs.
Adjacency List:An array of lists (or vectors), whereadj[i] contains a list of all
●
vertices adjacent to vertex i. Good for sparse graphs, more memory efficient.
include <iostream>
#
#include <vector>
#include <list> // For adjacency list
#include <queue> // For BFS
#include <stack> // For DFS (iterative)
/ / --- Adjacency Matrix Representation ---
void addEdgeMatrix(std::vector<std::vector<int>>& adjMatrix, int u, int v) {
adjMatrix[u][v] = 1; // For directed graph
// For undirected graph: adjMatrix[v][u] = 1;
}
void printMatrix(const std::vector<std::vector<int>>& adjMatrix) {
int V = adjMatrix.size();
std::cout << "\nAdjacency Matrix:" << std::endl;
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
std::cout << adjMatrix[i][j] << " ";
}
std::cout << std::endl;
}
}
/ / --- Adjacency List Representation ---
void addEdgeList(std::vector<std::list<int>>& adjList, int u, int v) {
adjList[u].push_back(v); // For directed graph
// For undirected graph: adjList[v].push_back(u);
}
void printList(const std::vector<std::list<int>>& adjList) {
int V = adjList.size();
std::cout << "\nAdjacency List:" << std::endl;
for (int i = 0; i < V; ++i) {
std::cout << "Vertex " << i << ":";
for (int neighbor : adjList[i]) {
std::cout << " -> " << neighbor;
}
std::cout << std::endl;
}
}
/ / --- Graph Traversal: Breadth-First Search (BFS) ---
// Uses a queue to explore neighbors level by level
void BFS(const std::vector<std::list<int>>& adjList, int startNode) {
int V = adjList.size();
std::vector<bool> visited(V, false);
std::queue<int> q;
v isited[startNode] = true;
q.push(startNode);
s td::cout << "\nBFS Traversal (starting from " << startNode << "): ";
while (!q.empty()) {
int u = q.front();
q.pop();
std::cout << u << " ";
for (int v : adjList[u]) {
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
std::cout << std::endl;
}
// --- Graph Traversal: Depth-First Search (DFS) ---
/ / Uses recursion (or a stack) to explore as deep as possible along each branch
void DFS_recursive(const std::vector<std::list<int>>& adjList, int u, std::vector<bool>&
visited) {
visited[u] = true;
std::cout << u << " ";
for (int v : adjList[u]) {
if (!visited[v]) {
DFS_recursive(adjList, v, visited);
}
}
}
void DFS_iterative(const std::vector<std::list<int>>& adjList, int startNode) {
int V = adjList.size();
std::vector<bool> visited(V, false);
std::stack<int> s;
s .push(startNode);
visited[startNode] = true;
s td::cout << "\nDFS Traversal (iterative, starting from " << startNode << "): ";
while (!s.empty()) {
int u = s.top();
s.pop();
std::cout << u << " ";
/ / Push unvisited neighbors onto the stack (order might vary from recursive)
// For consistent order with recursive DFS, push in reverse order of adjacency list
for (auto it = adjList[u].rbegin(); it != adjList[u].rend(); ++it) {
int v = *it;
if (!visited[v]) {
visited[v] = true;
s.push(v);
}
}
}
std::cout << std::endl;
}
int main() {
int V = 5; // Number of vertices (0, 1, 2, 3, 4)
/ / Adjacency Matrix Example
std::vector<std::vector<int>> adjMatrix(V, std::vector<int>(V, 0));
addEdgeMatrix(adjMatrix, 0, 1);
addEdgeMatrix(adjMatrix, 0, 4);
addEdgeMatrix(adjMatrix, 1, 2);
addEdgeMatrix(adjMatrix, 1, 3);
addEdgeMatrix(adjMatrix, 1, 4);
addEdgeMatrix(adjMatrix, 2, 3);
addEdgeMatrix(adjMatrix, 3, 4);
printMatrix(adjMatrix);
/ / Adjacency List Example
std::vector<std::list<int>> adjList(V);
addEdgeList(adjList, 0, 1);
addEdgeList(adjList, 0, 4);
addEdgeList(adjList, 1, 2);
addEdgeList(adjList, 1, 3);
addEdgeList(adjList, 1, 4);
addEdgeList(adjList, 2, 3);
addEdgeList(adjList, 3, 4);
printList(adjList);
/ / BFS Traversal
BFS(adjList, 0); // Start BFS from vertex 0
/ / DFS Traversal (Recursive)
std::vector<bool> visited_dfs_rec(V, false);
std::cout << "\nDFS Traversal (recursive, starting from 0): ";
DFS_recursive(adjList, 0, visited_dfs_rec);
std::cout << std::endl;
/ / DFS Traversal (Iterative)
DFS_iterative(adjList, 0);
return 0;
}
11. Basic Algorithms
11.1. Searching Algorithms (Linear, Binary)
Linear Search
equentially checks each element until a match is found or the end of the list is
S
reached.
● Time Complexity: O(N) in worst case.
include <iostream>
#
#include <vector>
#include <algorithm> // For std::find (though we implement it here)
/ / Function for Linear Search
int linearSearch(const std::vector<int>& arr, int target) {
for (size_t i = 0; i < arr.size(); ++i) {
if (arr[i] == target) {
return i; // Return index if found
}
}
return -1; // Return -1 if not found
}
int main() {
std::vector<int> numbers = {10, 5, 20, 15, 25, 30};
int target1 = 15;
int target2 = 100;
int index1 = linearSearch(numbers, target1);
if (index1 != -1) {
std::cout << target1 << " found at index: " << index1 << std::endl;
} else {
std::cout << target1 << " not found." << std::endl;
}
int index2 = linearSearch(numbers, target2);
if (index2 != -1) {
std::cout << target2 << " found at index: " << index2 << std::endl;
} else {
std::cout << target2 << " not found." << std::endl;
}
return 0;
}
Binary Search
fficiently finds an element in asortedarray byrepeatedly dividing the search interval
E
in half.
● Time Complexity: O(log N)
include <iostream>
#
#include <vector>
#include <algorithm> // For std::sort and std::binary_search
/ / Function for Binary Search (iterative)
int binarySearch(const std::vector<int>& arr, int target) {
int low = 0;
int high = arr.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2; // Avoids potential overflow compared to (low +
igh) / 2
h
if (arr[mid] == target) {
return mid; // Target found
} else if (arr[mid] < target) {
low = mid + 1; // Target is in the right half
} else {
high = mid - 1; // Target is in the left half
}
}
return -1; // Target not found
}
int main() {
std::vector<int> numbers = {10, 5, 20, 15, 25, 30};
/ / Binary search requires a sorted array!
std::sort(numbers.begin(), numbers.end());
std::cout << "Sorted array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl; // Output: 5 10 15 20 25 30
int target1 = 15;
int target2 = 100;
int index1 = binarySearch(numbers, target1);
if (index1 != -1) {
std::cout << target1 << " found at index: " << index1 << std::endl;
} else {
std::cout << target1 << " not found." << std::endl;
}
int index2 = binarySearch(numbers, target2);
if (index2 != -1) {
std::cout << target2 << " found at index: " << index2 << std::endl;
} else {
std::cout << target2 << " not found." << std::endl;
}
/ / Using STL's std::binary_search (returns true/false)
if (std::binary_search(numbers.begin(), numbers.end(), 20)) {
std::cout << "20 is present in the array (using std::binary_search)." << std::endl;
}
return 0;
}
11.2. Sorting Algorithms (Bubble, Selection, Insertion, Merge, Quick)
Bubble Sort
epeatedly steps through the list, compares adjacent elements and swaps them if
R
they are in the wrong order.
● Time Complexity: O(N^2)
include <iostream>
#
#include <vector>
#include <algorithm> // For std::swap
void bubbleSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; ++i) {
// Last i elements are already in place
for (int j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
}
int main() {
std::vector<int> numbers = {64, 34, 25, 12, 22, 11, 90};
std::cout << "Original array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
bubbleSort(numbers);
s td::cout << "Sorted array (Bubble Sort): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Selection Sort
Finds the minimum element from the unsorted part and puts it at the beginning.
● Time Complexity: O(N^2)
include <iostream>
#
#include <vector>
#include <algorithm> // For std::swap
void selectionSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n - 1; ++i) {
int min_idx = i;
for (int j = i + 1; j < n; ++j) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
// Swap the found minimum element with the first element
std::swap(arr[min_idx], arr[i]);
}
}
int main() {
std::vector<int> numbers = {64, 25, 12, 22, 11};
std::cout << "Original array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
selectionSort(numbers);
s td::cout << "Sorted array (Selection Sort): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Insertion Sort
uilds the final sorted array one item at a time. It iterates through the input elements
B
and inserts each element into its correct position in the already sorted part of the
array.
● Time Complexity: O(N^2) in worst case, O(N) in best case (already sorted).
include <iostream>
#
#include <vector>
void insertionSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 1; i < n; ++i) {
int key = arr[i];
int j = i - 1;
/ / Move elements of arr[0..i-1], that are greater than key,
// to one position ahead of their current position
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
int main() {
std::vector<int> numbers = {12, 11, 13, 5, 6};
std::cout << "Original array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
insertionSort(numbers);
s td::cout << "Sorted array (Insertion Sort): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Merge Sort
divide and conquer algorithm. It divides the input array into two halves, calls itself
A
for the two halves, and then merges the two sorted halves.
● Time Complexity: O(N log N)
● Space Complexity: O(N)
include <iostream>
#
#include <vector>
/ / Merges two subarrays of arr[].
// First subarray is arr[left..mid]
// Second subarray is arr[mid+1..right]
void merge(std::vector<int>& arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
/ / Create temporary arrays
std::vector<int> L(n1);
std::vector<int> R(n2);
/ / Copy data to temp arrays L[] and R[]
for (int i = 0; i < n1; ++i) {
L[i] = arr[left + i];
}
for (int j = 0; j < n2; ++j) {
R[j] = arr[mid + 1 + j];
}
/ / Merge the temp arrays back into arr[left..right]
int i = 0; // Initial index of first subarray
int j = 0; // Initial index of second subarray
int k = left; // Initial index of merged subarray
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
/ / Copy the remaining elements of L[], if any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
/ / Copy the remaining elements of R[], if any
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
/ / Main function that sorts arr[left..right] using merge()
void mergeSort(std::vector<int>& arr, int left, int right) {
if (left >= right) { // Base case: array with 0 or 1 element is sorted
return;
}
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid); // Sort first half
mergeSort(arr, mid + 1, right); // Sort second half
merge(arr, left, mid, right); // Merge the sorted halves
}
int main() {
std::vector<int> numbers = {12, 11, 13, 5, 6, 7};
std::cout << "Original array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
mergeSort(numbers, 0, numbers.size() - 1);
s td::cout << "Sorted array (Merge Sort): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Quick Sort
divide and conquer algorithm. It picks an element as a pivot and partitions the given
A
array around the picked pivot.
● Time Complexity: O(N log N) on average, O(N^2) in worst case.
● Space Complexity: O(log N) due to recursion stack.
include <iostream>
#
#include <vector>
#include <algorithm> // For std::swap
/ / Function to partition the array around a pivot
// This implementation uses the last element as pivot
int partition(std::vector<int>& arr, int low, int high) {
int pivot = arr[high]; // Choose the last element as pivot
int i = (low - 1); // Index of smaller element
for (int j = low; j <= high - 1; ++j) {
// If current element is smaller than or equal to pivot
if (arr[j] <= pivot) {
i++; // Increment index of smaller element
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]); // Put pivot in its correct sorted position
return (i + 1);
}
/ / Main function that implements QuickSort
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
// pi is partitioning index, arr[pi] is now at right place
int pi = partition(arr, low, high);
/ / Separately sort elements before partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
std::vector<int> numbers = {10, 7, 8, 9, 1, 5};
std::cout << "Original array: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
quickSort(numbers, 0, numbers.size() - 1);
s td::cout << "Sorted array (Quick Sort): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
11.3. Hashing
ashing is a technique to convert a large key into a small key (hash value) that can be
H
used to store and retrieve data efficiently. Hash tables (like std::unordered_map and
std::unordered_set) use hashing.
include <iostream>
#
#include <string>
#include <vector>
#include <list> // For chaining in case of collisions
/ / Simple Hash Table implementation using chaining
class HashTable {
private:
int capacity;
// A vector of lists to handle collisions (chaining)
std::vector<std::list<std::pair<std::string, int>>> table;
/ / Simple hash function for strings
int hashFunction(const std::string& key) {
long long hash_val = 0;
for (char c : key) {
hash_val = (hash_val * 31 + c) % capacity; // A common way to hash strings
}
return static_cast<int>(hash_val);
}
public:
HashTable(int size) : capacity(size) {
table.resize(capacity);
}
/ / Insert a key-value pair
void insert(const std::string& key, int value) {
int index = hashFunction(key);
for (auto& pair : table[index]) {
if (pair.first == key) {
pair.second = value; // Update value if key already exists
std::cout << "Updated key: " << key << std::endl;
return;
}
}
table[index].push_back({key, value}); // Add new key-value pair
std::cout << "Inserted key: " << key << ", value: " << value << " at index " << index <<
std::endl;
}
/ / Search for a key and return its value
int search(const std::string& key) {
int index = hashFunction(key);
for (const auto& pair : table[index]) {
if (pair.first == key) {
std::cout << "Found key: " << key << ", value: " << pair.second << std::endl;
return pair.second;
}
}
std::cout << "Key: " << key << " not found." << std::endl;
return -1; // Indicate not found
}
/ / Delete a key-value pair
void remove(const std::string& key) {
int index = hashFunction(key);
auto& chain = table[index]; // Get the list at this index
for (auto it = chain.begin(); it != chain.end(); ++it) {
if (it->first == key) {
chain.erase(it); // Erase from the list
std::cout << "Removed key: " << key << std::endl;
return;
}
}
std::cout << "Key: " << key << " not found for removal." << std::endl;
}
void display() {
s td::cout << "\n--- Hash Table Contents ---" << std::endl;
for (int i = 0; i < capacity; ++i) {
std::cout << "Bucket " << i << ": ";
if (table[i].empty()) {
std::cout << "Empty";
} else {
for (const auto& pair : table[i]) {
std::cout << "[" << pair.first << ":" << pair.second << "] ";
}
}
std::cout << std::endl;
}
std::cout << "--------------------------" << std::endl;
}
};
int main() {
HashTable myHashTable(10); // Create a hash table with 10 buckets
yHashTable.insert("apple", 10);
m
myHashTable.insert("banana", 20);
myHashTable.insert("cherry", 30);
myHashTable.insert("date", 40); // Might cause collision depending on hash function
myHashTable.insert("grape", 50);
myHashTable.display();
yHashTable.search("banana");
m
myHashTable.search("kiwi");
yHashTable.insert("apple", 100); // Update existing key
m
myHashTable.display();
yHashTable.remove("cherry");
m
myHashTable.remove("mango"); // Not found
myHashTable.display();
return 0;
}
12. File I/O
Reading from and writing to files.
include <iostream>
#
#include <fstream> // Required for file stream operations
#include <string>
int main() {
std::string filename = "example.txt";
/ / --- Writing to a file ---
// Create an ofstream object (output file stream)
// std::ios::out: open for writing (default)
// std::ios::trunc: truncate file to 0 length if it exists (default)
// std::ios::app: append to the end of the file
std::ofstream outFile(filename, std::ios::out); // Opens file for writing, creates if not
exists, truncates if exists
if (outFile.is_open()) {
outFile << "Hello, C++ File I/O!" << std::endl;
outFile << "This is the second line." << std::endl;
outFile << 12345 << std::endl; // Write numbers too
outFile.close(); // Close the file
std::cout << "Data written to " << filename << std::endl;
} else {
std::cerr << "Error: Could not open file " << filename << " for writing." << std::endl;
}
/ / --- Reading from a file ---
// Create an ifstream object (input file stream)
std::ifstream inFile(filename); // Opens file for reading
if (inFile.is_open()) {
std::string line;
s td::cout << "\nReading from " << filename << ":" << std::endl;
while (std::getline(inFile, line)) { // Read line by line until end of file
std::cout << line << std::endl;
}
inFile.close(); // Close the file
} else {
std::cerr << "Error: Could not open file " << filename << " for reading." << std::endl;
}
/ / --- Appending to a file ---
std::ofstream appendFile(filename, std::ios::app); // Open in append mode
if (appendFile.is_open()) {
appendFile << "Appended new line." << std::endl;
appendFile.close();
std::cout << "\nData appended to " << filename << std::endl;
} else {
std::cerr << "Error: Could not open file " << filename << " for appending." <<
std::endl;
}
/ / Read again to see appended content
std::ifstream inFileAppended(filename);
if (inFileAppended.is_open()) {
std::string line;
std::cout << "\nReading from " << filename << " after appending:" << std::endl;
while (std::getline(inFileAppended, line)) {
std::cout << line << std::endl;
}
inFileAppended.close();
}
return 0;
}
his guide covers the essential C++ concepts and foundational DSA elements. To truly
T
master these, consistent practice is key. Work through problems on platforms like
LeetCode, HackerRank, or Codeforces, applying these concepts. Good luck with your
placements!