Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
32 views95 pages

Unit 7

This document provides an overview of C++ Standard Library containers and iterators, focusing on Big O notation, hash tables, and various types of containers such as vectors and deques. It explains the efficiency of algorithms, the structure and operations of hash tables, and the functionalities of sequence containers like std::vector and std::deque. Additionally, it includes code examples to illustrate the usage of these containers and their methods.

Uploaded by

Vansh Patel
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views95 pages

Unit 7

This document provides an overview of C++ Standard Library containers and iterators, focusing on Big O notation, hash tables, and various types of containers such as vectors and deques. It explains the efficiency of algorithms, the structure and operations of hash tables, and the functionalities of sequence containers like std::vector and std::deque. Additionally, it includes code examples to illustrate the usage of these containers and their methods.

Uploaded by

Vansh Patel
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 95

PROGRAMMING WITH C++

UNIT 7
STANDARD LIBRARY CONTAINERS AND ITERATORS

Devang Patel Institute of Advance Technology and Research


Outlines
• Introduction to Big O
• Introduction to Hash Tables
• Introduction to Containers
• Working with Iterators
• Sequence Container (vector, list, deque, Associative)
• Container Adaptors
• Lambda Expression
Introduction to Big O
Big O notation is a mathematical concept used in computer science
to describe the efficiency of an algorithm. It provides a way to
classify algorithms based on their runtime or space requirements as
the input size grows.

Why is Big O Important?


• Helps compare different algorithms.
• Allows developers to optimize code performance.
• Predicts how an algorithm behaves with large inputs.
Common Big O Notations
• Constant Time – O(1)
• The runtime does not depend on the input size.
• Linear Time – O(n)
• The runtime increases proportionally with the input size.
• Logarithmic Time – O(log n)
• The runtime grows logarithmically, common in search operations.
• Quadratic Time – O(n²)
• The runtime grows proportionally to the square of the input size.
• Exponential Time – O(2ⁿ)
• The runtime doubles with each additional input.
Constant Time – O(1)
• The runtime does not depend on the input size.

void constantTime(int arr[], int size) {


cout << arr[0] << endl; // Always takes the same amount of time.
}

• Even if the array size increases, this function always runs in the
same amount of time.
Linear Time – O(n)
• The runtime increases proportionally with the input size.

void linearTime(int arr[], int size) {


for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}

• If size doubles, the time taken doubles.


Logarithmic Time – O(log n)
• The runtime grows logarithmically, common in search operations.
int binarySearch(int arr[], int left, int right, int target) {
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}

• Binary search reduces the search space in half each step, leading to
O(log n) complexity.
Quadratic Time – O(n²)
• The runtime grows proportionally to the square of the input size.

void quadraticTime(int arr[], int size) {


for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
cout << arr[i] << ", " << arr[j] << endl;
}
}
}

• This is typical in nested loops, such as bubble sort.


Exponential Time – O(2ⁿ)
• The runtime doubles with each additional input.

int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

• Recursive Fibonacci is an example of exponential complexity.


Hash Tables in C++
• A hash table (also known as a hash map) is a data structure that
allows for efficient key-value pair storage.
• It is used for fast data retrieval, insertion, and deletion
operations, typically with an average time complexity of O(1).
• Hash tables are particularly useful when you need to associate a
unique key with a value and retrieve data based on that key
quickly.
Key Concepts of Hash Tables
• Hash Function:
• A hash function is a function that takes an input (or "key") and returns an index (or "hash code") in an array where the
corresponding value is stored.
• The function aims to distribute the keys uniformly across the hash table, minimizing collisions (where two keys hash to the
same index).
• Buckets:
• A bucket is a location in the hash table that stores values. Each bucket can hold multiple entries, typically managed through a
linked list or another container.
• When a hash function generates the same hash for different keys, those keys will be placed in the same bucket, resulting in a
collision.
• Collisions:
• Collisions happen when two different keys hash to the same bucket. There are two main ways to handle collisions:
• Chaining: Each bucket holds a collection (such as a linked list) of elements that share the same hash code.
• Open Addressing: All elements are stored directly in the array, and a new location is found if a collision occurs (usually through methods like
linear probing, quadratic probing, or double hashing).

• Load Factor:
• The load factor of a hash table is defined as the number of elements divided by the number of buckets. It determines how
"full" the hash table is.
• A high load factor may lead to performance degradation (more collisions), and the hash table may need reallocation (resize) to
maintain optimal performance.
How Hash Tables Work
• Insertion:
• A key-value pair is inserted by computing the hash of the key and placing it in the
corresponding bucket.
• If a collision occurs, the item is inserted into the appropriate location in the bucket.
• Search:
• To search for a value, the hash table computes the hash of the key and looks for the value
in the corresponding bucket.
• If there are multiple items in the same bucket (due to a collision), a secondary search is
performed within the bucket.
• Deletion:
• To delete a key-value pair, the hash table computes the hash of the key and removes the
corresponding item from the bucket.
• If there is more than one item in the bucket, the appropriate entry is found and removed.
Advantages of Hash Tables
• Efficient Lookup, Insertion, and Deletion:
Hash tables provide O(1) time complexity for lookup, insertion, and deletion
on average, making them very efficient for large datasets.

• Fast Access:
Direct access to an element using its key is possible due to the hash function
that computes the location of the element.

• Flexible Key-Value Mapping:


Hash tables allow you to map a variety of keys (such as integers, strings, or
custom types) to values (like integers, objects, etc.).
Hash Table Operations in C++

• In C++, the std::unordered_map and std::unordered_set are


the standard hash table containers.

• These containers allow for efficient key-value pair storage and


retrieval.

• std::unordered_map<Key, Value>: Stores key-value pairs, where each


key maps to a value.
• std::unordered_set<Key>: Stores keys only, ensuring all keys are
unique.
Example
#include <iostream> cout << student << " not found." << endl;
#include <unordered_map> }
#include <string>
using namespace std; // Iterate through the hash table
for (const auto& pair : studentGrades) {
int main() { cout << pair.first << ": " << pair.second << endl;
// Create a hash table (unordered_map) }
unordered_map<string, int> studentGrades;
return 0;
// Insert key-value pairs (key = student name, value = grade) }
studentGrades["Alice"] = 85;
studentGrades["Bob"] = 92;
studentGrades["Charlie"] = 78;

// Search for a key and print the corresponding value


string student = "Bob";
if (studentGrades.find(student) != studentGrades.end()) {
cout << student << "'s grade: " << studentGrades[student] <<
endl;
} else {

Output:
Introduction to Containers in C++
• Containers in C++ are data structures provided by the Standard
Template Library (STL) that store collections of objects. They
simplify memory management and improve efficiency in handling
data.

C++ provides three main types of containers:


• Sequence Containers – Store elements in a linear order.
• Associative Containers – Store elements in a sorted or hashed manner
for fast lookup.
• Unordered Containers – Use hashing for fast operations.
Sequence Containers
• Sequence containers in C++ store elements in a linear order and
allow efficient traversal, insertion, and deletion.
• They are part of the Standard Template Library (STL) and are
useful for managing collections of elements.
std::vector in C++ (Dynamic Array)
• std::vector is a dynamic array that can automatically resize itself
when elements are added or removed. It is part of the Standard
Template Library (STL) and is one of the most commonly used
sequence containers in C++.

• Key Points of Vector:


• Dynamic Sizing: Grows automatically when needed.
• Fast Random Access: O(1) time complexity for element access.
• Efficient Insert/Remove at End: O(1) for push/pop at the back.
• Slow Insert/Remove at Front/Middle: O(n) due to shifting elements.
Declaring and Initializing std::vector

#include <iostream>
#include <vector>

int main() {
std::vector<int> vec1; // Empty vector
std::vector<int> vec2(5); // Vector of size 5 (default values)
std::vector<int> vec3(5, 100); // Vector of size 5 with all values = 100
std::vector<int> vec4 = {1, 2, 3, 4, 5}; // Initialization with values

return 0;
}
Adding and Removing Elements
#include <iostream>
#include <vector>

int main() {
std::vector<int> vec;

vec.push_back(10); // Add 10 at the end


vec.push_back(20);
vec.push_back(30);
Output:
vec.pop_back(); // Removes the last element (30)

std::cout << "Vector elements: ";


for (int num : vec) {
std::cout << num << " ";
}

return 0;
}
Accessing Elements

#include <iostream>
#include <vector>

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};

std::cout << "First Element: " << vec.front() << std::endl; Output:
std::cout << "Last Element: " << vec.back() << std::endl;
std::cout << "Element at Index 2: " << vec[2] << std::endl;

return 0;
}
Vector Capacity & Size Methods
Vector Element Access Methods
Modifiers (Insertion, Deletion, and
Modification)
Iterators (Traversal & Access)
Relational Operators (Comparisons)
Example
#include <iostream> cout << "Front: " << v.front() << ", Back: " << v.back() << endl;
#include <vector>
#include <algorithm> // for sort, find // at: Safe element access
using namespace std; cout << "Element at index 1: " << v.at(1) << endl;

int main() { // resize: Change number of elements


// Create an empty vector of integers v.resize(6, 100); // fill new slots with 100
vector<int> v;
// sort
// push_back: Add elements sort(v.begin(), v.end());
v.push_back(10);
v.push_back(20); // find
v.push_back(30); auto it = find(v.begin(), v.end(), 100);
if (it != v.end()) {
// emplace_back: More efficient insertion cout << "Found 100 at index: " << distance(v.begin(), it) << endl;
v.emplace_back(40); }

// insert: Insert at a specific position // clear: Remove all elements


v.insert(v.begin() + 2, 25); // insert 25 at index 2 // v.clear();

// erase: Remove element at index // empty: Check if vector is empty


v.erase(v.begin() + 1); // remove element at index 1 (value 20) cout << "Is vector empty? " << (v.empty() ? "Yes" : "No") << endl;

// pop_back: Remove last element // Display vector


Output:
v.pop_back(); // removes 40 cout << "Vector elements: ";
for (int x : v) {
// size: Number of elements cout << x << " ";
cout << "Size: " << v.size() << endl; }
cout << endl;
// capacity: Current allocated memory
cout << "Capacity: " << v.capacity() << endl; return 0;
}
// front and back
std::deque (Double-Ended Queue) in C++
• std::deque (Double-Ended Queue) is a sequence container in C++ that
allows fast insertion and deletion at both ends (front and back).
• It is similar to std::vector, but provides efficient operations at both ends,
whereas std::vector is optimized for operations at the back only.

• Key points of std::deque


• Fast Insertions/Deletions at Front & Back: O(1) for push_front() and
push_back().
• Efficient Random Access: O(1) time complexity for indexing (operator[]).
• Dynamic Resizing: No need to manually manage memory.
• Slower Middle Insertions/Deletions: O(n) due to shifting elements.
Declaring and Initializing std::deque

#include <iostream>
#include <deque>

int main() {
std::deque<int> dq1; // Empty deque
std::deque<int> dq2(5); // Deque of size 5 (default values)
std::deque<int> dq3(5, 100); // Deque of size 5 with all values = 100
std::deque<int> dq4 = {1, 2, 3, 4, 5}; // Initialization with values

return 0;
}
Adding and Removing Elements
#include <iostream>
#include <deque>

int main() {
std::deque<int> dq;

dq.push_back(10); // Add at the end


dq.push_front(20); // Add at the front
dq.push_back(30);
dq.push_front(40);

dq.pop_back(); // Remove last element (30)


dq.pop_front(); // Remove first element (40)
Output:
std::cout << "Deque elements: ";
for (int num : dq) {
std::cout << num << " ";
}

return 0;
}
Accessing Elements

#include <iostream>
#include <deque>

int main() {
std::deque<int> dq = {1, 2, 3, 4, 5};

std::cout << "First Element: " << dq.front() << std::endl; Output:
std::cout << "Last Element: " << dq.back() << std::endl;
std::cout << "Element at Index 2: " << dq[2] << std::endl;

return 0;
}
Methods of std::deque
Methods of std::deque
Example
#include <iostream>
#include <deque> // erase: remove element at position 3
#include <algorithm> // for sort, find dq.erase(dq.begin() + 3);
using namespace std;
// size and empty
int main() { cout << "Size: " << dq.size() << endl;
// Create a deque of integers cout << "Is deque empty? " << (dq.empty() ? "Yes" : "No") << endl;
deque<int> dq;
// sort (only works with random access iterators)
// push_back and push_front sort(dq.begin(), dq.end());
dq.push_back(10);
dq.push_back(20); // find
dq.push_front(5); auto it = find(dq.begin(), dq.end(), 99);
dq.push_front(1); if (it != dq.end()) {
cout << "Found 99 at index: " << distance(dq.begin(), it) << endl;
// emplace_back and emplace_front }
dq.emplace_back(25);
dq.emplace_front(0); // Display deque Output:
cout << "Deque elements: ";
// at, front, back for (int x : dq) {
cout << "Element at index 2: " << dq.at(2) << endl; cout << x << " ";
cout << "Front: " << dq.front() << ", Back: " << dq.back() << endl; }
cout << endl;
// pop_back and pop_front
dq.pop_back(); return 0;
dq.pop_front(); }

// insert: insert 99 at position 2


dq.insert(dq.begin() + 2, 99);
std::list in C++
• std::list is a doubly linked list container in C++ that allows fast
insertions and deletions at any position.
• Unlike std::vector and std::deque, std::list does not support direct
access via indexing ([]).

• Key points of std::list


• Efficient insertions and deletions at any position (O(1)).
• Dynamic memory allocation (no pre-allocation needed).
• Supports forward and backward traversal (doubly linked list).
• No direct random access (O(n) time to access an element at index i).
Declaring and Initializing std::list

#include <iostream>
#include <list>

int main() {
std::list<int> lst1; // Empty list
std::list<int> lst2(5); // List of size 5 (default values)
std::list<int> lst3(5, 100); // List of size 5 with all values
= 100
std::list<int> lst4 = {1, 2, 3, 4, 5}; // Initialization with
values

return 0;
}
Adding and Removing Elements
#include <iostream>
#include <list>

int main() {
std::list<int> lst;

lst.push_back(10); // Add at the end


lst.push_front(20); // Add at the front
lst.push_back(30);
Output:
lst.push_front(40);

lst.pop_back(); // Remove last element (30)


lst.pop_front(); // Remove first element (40)

std::cout << "List elements: ";


for (int num : lst) {
std::cout << num << " ";
}

return 0;
}
Accessing Elements
• std::list does not support direct indexing ([]) or .at(). Use iterators
instead:

#include <iostream>
#include <list>

int main() { Output:


std::list<int> lst = {1, 2, 3, 4, 5};

std::cout << "First Element: " << lst.front() << std::endl;


std::cout << "Last Element: " << lst.back() << std::endl;

return 0;
}
Methods of std::list
Methods of std::list
Example
#include <iostream> advance(it, 3); // move to 4th element
#include <list> lst.erase(it);
#include <algorithm> // for find, sort
using namespace std; // size and empty
cout << "Size: " << lst.size() << endl;
int main() { cout << "Is list empty? " << (lst.empty() ? "Yes" : "No") << endl;
// Create a list of integers
list<int> lst; // find
auto findIt = find(lst.begin(), lst.end(), 99);
// push_back and push_front if (findIt != lst.end()) {
lst.push_back(10); cout << "Found 99 in list." << endl;
lst.push_back(20); }
lst.push_front(5);
lst.push_front(1); // sort (only works on list itself, not from <algorithm>)
lst.sort();
// emplace_back and emplace_front
lst.emplace_back(30); // reverse
lst.emplace_front(0); lst.reverse();

// front and back // remove specific value (e.g., remove 10)


cout << "Front: " << lst.front() << ", Back: " << lst.back() << endl; lst.remove(10);

// pop_back and pop_front // Display list


lst.pop_back(); cout << "List elements: ";
lst.pop_front(); for (int x : lst) { Output:
cout << x << " ";
// insert (only supports insertion using iterator) }
auto it = lst.begin(); cout << endl;
advance(it, 2); // move iterator to 3rd position
lst.insert(it, 99); return 0;
}
// erase (using iterator)
it = lst.begin();
std::forward_list in C++
• std::forward_list is a singly linked list container in C++ that allows fast
insertions and deletions but only supports forward traversal.
• Unlike std::list, it does not support backward traversal since it is singly
linked.

• Key Points of std::forward_list


• Efficient insertions and deletions at any position (O(1)).
• Uses less memory compared to std::list (no extra backward pointers).
• Faster than std::list in certain cases due to lower memory overhead.
• No direct access via indexing ([] or .at()).
• No size() function (requires manual counting).
• Only supports forward iteration (no rbegin() or rend()).
Declaring and Initializing std::forward_list

#include <iostream>
#include <forward_list>

int main() {
std::forward_list<int> flist1; // Empty list
std::forward_list<int> flist2 = {1, 2, 3, 4, 5}; // Initialization with values
std::forward_list<int> flist3(5, 100); // List of size 5 with all values = 100

return 0;
}
Adding and Removing Elements
#include <iostream>
#include <forward_list>

int main() {
std::forward_list<int> flist = {10, 20, 30};

flist.push_front(5); // Add at the front Output:


flist.pop_front(); // Remove the first element

std::cout << "Forward List elements: ";


for (int num : flist) {
std::cout << num << " ";
}

return 0;
}
Methods of std::forward_list
Methods of std::forward_list
Example
#include <iostream> flist.remove(30); // Remove all occurrences of 30
#include <forward_list>
#include <algorithm> // for find // sort
using namespace std; flist.sort();

int main() { // unique: Remove consecutive duplicates


// Create a forward_list of integers flist.unique();
forward_list<int> flist = {10, 20, 30};
// reverse
// push_front: Add elements to the front flist.reverse();
flist.push_front(5);
flist.push_front(1); // Display the forward_list
cout << "Forward list elements: ";
// emplace_front: Efficient in-place construction for (int x : flist) {
flist.emplace_front(0); cout << x << " ";
}
// front: Access first element cout << endl;
cout << "Front: " << flist.front() << endl;
// Check if list is empty
// insert_after: Insert after a given position cout << "Is list empty? " << (flist.empty() ? "Yes" : "No") << endl;
auto it = flist.begin();
advance(it, 2); // Move to the 3rd element return 0;
flist.insert_after(it, 15); // Insert 15 after the 3rd element }

// emplace_after
flist.emplace_after(it, 12); // Insert 12 after the 3rd element

// erase_after: Remove element after a given position Output:


it = flist.begin();
advance(it, 3);
flist.erase_after(it); // Erase element after the 4th element

// remove: Remove elements by value


Associative Containers in C++
• Associative containers in C++ are data structures that store elements in a way
that allows fast searching, insertion, and deletion.
• They automatically maintain a sorted order (except for unordered_ versions).

• Types of Associative Containers:


• Ordered Containers (Sorted by keys using a balanced binary tree - Red-Black Tree):
• std::set (Unique sorted elements)
• std::map (Key-value pairs, unique keys)
• std::multiset (Allows duplicate elements)
• std::multimap (Allows duplicate keys)
• Unordered Containers (Fast access using Hash Tables):
• std::unordered_set
• std::unordered_map
• std::unordered_multiset
• std::unordered_multimap
std::set in C++
• A std::set is an ordered collection of unique elements.
• It is implemented as a balanced binary search tree (Red-Black
Tree), ensuring efficient operations with O(log n) time complexity
for insertions, deletions, and searches.

• Key points of std::set


• Stores unique elements (No duplicates allowed).
• Automatically sorts elements in ascending order (default).
• Fast insert, erase, and find operations (O(log n)).
• No direct access via indexing ([] or .at()).
Declaring and Initializing a std::set

#include <iostream>
#include <set>

int main() {
std::set<int> s1; // Empty set
std::set<int> s2 = {10, 30, 20, 10, 40}; // Initialization with values
std::set<int> s3(s2); // Copy constructor

return 0;
}
Inserting and Erasing Elements
#include <iostream>
#include <set>

int main() {
std::set<int> s;

s.insert(10); // Insert 10
s.insert(20); Output:
s.insert(30);
s.insert(10); // Duplicate, won't be added

s.erase(20); // Removes 20

std::cout << "Set elements: ";


for (int num : s) {
std::cout << num << " ";
}
}
Methods of std::set
Example
#include <iostream> // count (returns 1 if element exists, 0 otherwise)
#include <set> cout << "Is 10 in the set? " << (s.count(10) ? "Yes" : "No") << endl;
#include <algorithm> // for find (optional)
using namespace std; // erase by value
s.erase(20);
int main() {
// Create a set of integers // erase by iterator
set<int> s; auto it2 = s.find(5);
if (it2 != s.end()) {
// insert: Add elements s.erase(it2);
s.insert(10); }
s.insert(5);
s.insert(20); // lower_bound and upper_bound
s.insert(15); cout << "Lower bound of 12: ";
s.insert(10); // Duplicate, will be ignored auto lb = s.lower_bound(12);
if (lb != s.end()) cout << *lb << endl; else cout << "No such element\n";
// emplace: More efficient insertion
s.emplace(25); cout << "Upper bound of 15: ";
auto ub = s.upper_bound(15);
// size and empty if (ub != s.end()) cout << *ub << endl; else cout << "No such element\n";
cout << "Size: " << s.size() << endl;
cout << "Is set empty? " << (s.empty() ? "Yes" : "No") << endl; // Display elements (auto-sorted in ascending order) Output:
cout << "Set elements: ";
// find for (int x : s) {
auto it = s.find(15); cout << x << " ";
if (it != s.end()) { }
cout << "Found: " << *it << endl; cout << endl;
} else {
cout << "15 not found." << endl; return 0;
} }
std::map
• A std::map is an ordered associative container that stores key-
value pairs, where:
• Keys are unique (No duplicates allowed).
• Values are associated with keys (Each key maps to a value).
• Elements are automatically sorted by keys.
• Implemented as a self-balancing binary search tree (Red-Black Tree),
providing O(log n) insert, erase, and search operations.
Declaring and Initializing a std::map
#include <iostream>
#include <map>

int main() {
std::map<int, std::string> myMap; // Empty map

std::map<int, std::string> fruits = {


{1, "Apple"},
{3, "Banana"},
{2, "Cherry"}
};

return 0;
}
Inserting Elements
#include <iostream>
#include <map>

int main() {
std::map<int, std::string> myMap;

// Method 1: Using `insert()`


myMap.insert({1, "Apple"});
myMap.insert({3, "Banana"}); Output:
// Method 2: Using `operator[]`
myMap[2] = "Cherry";
myMap[4] = "Dates";

for (auto &pair : myMap) {


std::cout << pair.first << " -> " << pair.second << std::endl;
}
}
Methods of std::map
Example
#include <iostream> cout << "Bob's Marks: " << it->second << endl;
#include <map> }
#include <string>
using namespace std; // count (returns 1 if key exists, 0 otherwise)
cout << "Is 'David' in map? " << (studentMarks.count("David") ? "Yes" : "No") << endl;
int main() {
// Create a map with string keys and int values // erase by key
map<string, int> studentMarks; studentMarks.erase("Charlie");

// insert elements using insert() // size and empty


studentMarks.insert({"Alice", 85}); cout << "Size of map: " << studentMarks.size() << endl;
studentMarks.insert(make_pair("Bob", 90)); cout << "Is map empty? " << (studentMarks.empty() ? "Yes" : "No") << endl;

// insert or update using [] operator // iterate through map (keys are auto-sorted)
studentMarks["Charlie"] = 78; cout << "All student marks:" << endl;
studentMarks["David"] = 92; for (const auto& pair : studentMarks) {
cout << pair.first << ": " << pair.second << endl;
// emplace: more efficient insertion } Output:
studentMarks.emplace("Eve", 88);
// clear
// Access elements // studentMarks.clear();
cout << "Alice's Marks: " << studentMarks["Alice"] << endl;
return 0;
// find }
auto it = studentMarks.find("Bob");
if (it != studentMarks.end()) {
std::multiset
• A std::multiset is an ordered associative container similar to
std::set, but with duplicate elements allowed.
• Stores elements in sorted order (ascending by default).
• Allows duplicate elements.
• Implemented as a self-balancing binary search tree (Red-Black Tree).
• Operations (insert, erase, search) take O(log n) time.
Declaring and Initializing a std::multiset

#include <iostream>
#include <set>

int main() {
std::multiset<int> ms1; // Empty multiset
std::multiset<int> ms2 = {10, 30, 20, 10, 40}; //
Initialization with duplicates

return 0;
}
Inserting Elements
#include <iostream>
#include <set>

int main() {
std::multiset<int> ms;

ms.insert(10);
Output:
ms.insert(20);
ms.insert(30);
ms.insert(10); // Duplicate allowed

std::cout << "Multiset elements: ";


for (int num : ms) {
std::cout << num << " ";
}
}
Methods of std::multiset
Example
#include <iostream> // erase by value: removes *all* instances of that value
#include <set> ms.erase(10); // removes all 10s
using namespace std;
// erase by iterator: removes just one occurrence
int main() { it = ms.find(20);
// Create a multiset of integers if (it != ms.end()) {
multiset<int> ms; ms.erase(it); // removes one 20
}
// insert (allows duplicates)
ms.insert(10); // lower_bound and upper_bound
ms.insert(20); cout << "Lower bound of 20: ";
ms.insert(10); // duplicate auto lb = ms.lower_bound(20);
ms.insert(30); if (lb != ms.end()) cout << *lb << endl; else cout << "No element\n";
ms.insert(20); // duplicate
cout << "Upper bound of 20: ";
// emplace auto ub = ms.upper_bound(20);
ms.emplace(25); if (ub != ms.end()) cout << *ub << endl; else cout << "No element\n";

// size and empty // Display elements (sorted, including duplicates)


cout << "Size: " << ms.size() << endl; cout << "Multiset elements: ";
cout << "Is multiset empty? " << (ms.empty() ? "Yes" : "No") << endl; for (int x : ms) {
cout << x << " ";
// count: how many times an element appears
cout << "Count of 10: " << ms.count(10) << endl;
}
cout << endl;
Output:
cout << "Count of 20: " << ms.count(20) << endl;
return 0;
// find: gets an iterator to *one* instance of the value }
auto it = ms.find(20);
if (it != ms.end()) {
cout << "Found: " << *it << endl;
}
std::multimap
• A std::multimap is an ordered associative container similar to
std::map, but with duplicate keys allowed.
• Stores key-value pairs in sorted order (ascending by default).
• Allows multiple values for the same key.
• Implemented as a self-balancing binary search tree (Red-Black Tree).
• Operations (insert, erase, search) take O(log n) time.
Declaring and Initializing a std::multimap
#include <iostream>
#include <map>

int main() {
std::multimap<int, std::string> mm; // Empty multimap

std::multimap<int, std::string> fruits = {


{1, "Apple"},
{3, "Banana"},
{2, "Cherry"},
{1, "Apricot"} // Duplicate key allowed
};

return 0;
}
Inserting Elements
#include <iostream>
#include <map>

int main() {
std::multimap<int, std::string> mm;

mm.insert({1, "Apple"}); Output:


mm.insert({2, "Banana"});
mm.insert({1, "Apricot"}); // Duplicate key

for (const auto &pair : mm) {


std::cout << pair.first << " -> " << pair.second << std::endl;
}
}
Methods of std::multimap
Example
#include <iostream>
#include <map> // count: how many entries have the key
#include <string> cout << "Count of 'Alice': " << marks.count("Alice") << endl;
using namespace std;
// equal_range: get range of elements with a given key
int main() { auto range = marks.equal_range("Bob");
// Create a multimap with string keys and int values cout << "All marks for Bob: ";
multimap<string, int> marks; for (auto itr = range.first; itr != range.second; ++itr) {
cout << itr->second << " ";
// insert pairs (allows duplicate keys) }
marks.insert({"Alice", 85}); cout << endl;
marks.insert({"Bob", 90});
marks.insert({"Alice", 88}); // duplicate key // erase: by key (removes all entries with that key)
marks.insert({"Charlie", 75}); marks.erase("Charlie");

// emplace (construct in-place) // Display all key-value pairs (keys are sorted, duplicates allowed)
marks.emplace("Bob", 93); cout << "All student marks:" << endl;
for (const auto& p : marks) { Output:
// size and empty cout << p.first << " => " << p.second << endl;
cout << "Size: " << marks.size() << endl; }
cout << "Is multimap empty? " << (marks.empty() ? "Yes" : "No") << endl;
return 0;
// find: returns iterator to the *first* element with given key }
auto it = marks.find("Bob");
if (it != marks.end()) {
cout << "Found Bob: " << it->second << endl;
}
std::unordered_set
std::unordered_multiset
• C++ provides unordered associative containers that use hash
tables for fast operations.
• std::unordered_set → Stores unique elements (no duplicates allowed).
• std::unordered_multiset → Stores duplicate elements (allows multiple
occurrences of the same value).
Example of std::unordered_set
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> us = {10, 20, 30, 10}; // Duplicates ignored
Output:
for (int num : us) {
std::cout << num << " "; // Order not guaranteed
}
}
Example of std::unordered_multiset
#include <iostream>
#include <unordered_multiset>

int main() {
std::unordered_multiset<int> ums = {10, 20, 30, 10, 20};
Output:
for (int num : ums) {
std::cout << num << " ";
}
}
std::unordered_map
std::unordered_multimap
• C++ provides unordered associative containers that store key-
value pairs in hash tables for fast operations.
• std::unordered_map → Stores unique keys, each key maps to a single
value.
• std::unordered_multimap → Stores duplicate keys, each key can have
multiple values.
Example of std::unordered_map

#include <iostream>
#include <unordered_map>

int main() {
std::unordered_map<int, std::string> um = {{1, "Apple"}, {2, "Banana"}, {3, "Cherry"}};

for (const auto &pair : um) {


std::cout << pair.first << " -> " << pair.second << std::endl;
}
}

Output:
Example of std::unordered_multimap

#include <iostream>
#include <unordered_map>

int main() {
std::unordered_multimap<int, std::string> umm = {
{1, "Apple"}, {2, "Banana"}, {1, "Apricot"}, {3, "Cherry"}
}; Output:

for (const auto &pair : umm) {


std::cout << pair.first << " -> " << pair.second << std::endl;
}
}
Working with Iterators in C++
• Iterators in C++ are used to traverse and manipulate elements in
containers (such as vectors, lists, maps, etc.). They are an
essential part of the Standard Template Library (STL), providing
a uniform interface for working with different container types.

• Iterators act like pointers that can be used to access elements,


move through the container, and perform operations on the
elements without directly manipulating the underlying data
structure.
Type of iterator

Common Operations with Iterators

1.Dereferencing the Iterator (*it): Accessing the element the iterator is pointing to.
2.Incrementing the Iterator (++it): Moving to the next element.
3.Decrementing the Iterator (--it): Moving to the previous element (available for bidirectional and random access iterators).
4.Comparison (it == end()): Checking if the iterator has reached the end of the container.
5.Accessing an element directly (it[n]): (Only for random access iterators like std::vector).
Example of using Iterator with vector
#include <iostream> // Using range-based for loop (simplified)
#include <vector> cout << "Using range-based for loop:" << endl;
using namespace std; for (auto &val : vec) {
int main() { cout << val << " ";
vector<int> vec = {1, 2, 3, 4, 5}; }
cout << endl;
// Using iterator to traverse the vector
cout << "Using iterators with vector:" << endl; return 0;
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) { }
cout << *it << " ";
}
cout << endl;

Output:
Example of Iterators with list(Bidirectional
iterator)
#include <iostream> // Using reverse iterator to traverse in reverse order
#include <list> cout << "Using reverse iterator with list:" << endl;
using namespace std; for (list<int>::reverse_iterator rit = myList.rbegin(); rit != myList.rend(); ++rit) {
cout << *rit << " ";
int main() { }
list<int> myList = {10, 20, 30, 40}; std::cout << std::endl;

// Using iterator to traverse the list return 0;


cout << "Using iterators with list:" << endl; }
for (list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
cout << *it << " ";
}
cout << endl;

Output:
Example of Iterators with std::map
(Bidirectional & Random Access Iterator)
#include <iostream>
#include <map>
using namespace std;

int main() {
map<int, std::string> myMap = {{1, "One"}, {2, "Two"}, {3, "Three"}};

// Using iterator to traverse the map


cout << "Using iterators with map:" << endl;
for (std::map<int, std::string>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}

return 0;
} Output:
Container Adaptors in C++
• C++ provides container adaptors, which are wrapper containers
that modify the interface or behavior of other containers.
• They do not implement their own data structures but instead
adapt other containers (like std::queue, std::stack, etc.) to meet
specific needs.
• There are three main container adaptors in C++:
• std::stack - Adapts containers to work as LIFO (Last In, First Out) stacks.
• std::queue - Adapts containers to work as FIFO (First In, First Out)
queues.
• std::priority_queue - Adapts containers to work as priority queues
(based on element priority).
std::stack (LIFO - Last In, First Out)
• std::stack is an adaptor container that allows access to elements in
a LIFO (Last In, First Out) order.
• It is typically used for operations that follow the push-pop
behavior, such as undo/redo operations, depth-first search, and
more.
• Internal Container:
• By default, std::stack uses std::deque as the underlying container.
However, you can customize it to use other containers like std::vector or
std::list.
Key Operations:
Example of Stack
#include <iostream>
#include <stack>

int main() {
std::stack<int> s;

s.push(10);
s.push(20);
s.push(30); Output:

std::cout << "Top: " << s.top() << std::endl; // Output: 30


s.pop();
std::cout << "Top after pop: " << s.top() << std::endl; //
Output: 20

return 0;
}
std::queue (FIFO - First In, First Out)
• std::queue is an adaptor container that implements a FIFO (First
In, First Out) data structure, which is commonly used for tasks like
task scheduling, buffering, and breadth-first search.

• Internal Container:
• std::queue uses std::deque by default but can also work with other
containers like std::list.
Key Operations:
Example of Queue
#include <iostream>
#include <queue>

int main() {
std::queue<int> q;

q.push(10);
q.push(20); Output:
q.push(30);

std::cout << "Front: " << q.front() << std::endl; // Output:


10
q.pop();
std::cout << "Front after pop: " << q.front() << std::endl;
// Output: 20

return 0;
}
std::priority_queue (Priority Queue)
• std::priority_queue is an adaptor container that implements a
priority queue, where elements are ordered based on their
priority.
• By default, the highest priority element is the first to be removed.
It's commonly used in algorithms like Dijkstra's algorithm and in
task scheduling systems.

• Internal Container:
• By default, std::priority_queue uses std::vector as the underlying
container. However, it can be adapted to use other containers like
std::deque or std::list.
Key Operations:
By default, std::priority_queue uses the greater-than operator (>) for comparisons, meaning
larger elements have higher priority. You can customize the priority by passing a custom
comparison function.
Example of Priority queue
#include <iostream>
#include <queue>
#include <vector>

int main() {
// Max-heap (default)
std::priority_queue<int> pq;

pq.push(10);
pq.push(30);
pq.push(20);
Output:
std::cout << "Top: " << pq.top() << std::endl; // Output: 30
pq.pop();
std::cout << "Top after pop: " << pq.top() << std::endl; // Output: 20

return 0;
}
Lambda Expression in C++
• A lambda expression in C++ is an anonymous function that can
be defined inline and used without the need to define a separate
function.
• It allows you to write small, concise, and efficient functions for
short-term use, making your code more readable and functional.
Syntax of Lambda Expression
Syntax:

[ capture_clause ] ( parameters ) -> return_type { function_body }

• Capture Clause ([]): Specifies which variables from the surrounding scope are available for use
inside the lambda. It can capture by value or by reference.
• Parameters (()): List of parameters (similar to function arguments).
• Return Type (-> return_type): Optional. Specifies the return type of the lambda.
• Function Body ({ }): The code that defines the functionality of the lambda expression.
Example

#include <iostream>

int main() {
auto add = [](int a, int b) { return a + b; };
std::cout << "Sum: " << add(3, 4) << std::endl; // Output: Sum: 7
return 0;
}

Output:
Example

#include <iostream>

int main() {
int x = 10;
auto printX = [x]() { std::cout << "Captured value: " << x << std::endl; }; Output:
printX(); // Output: Captured value: 10
return 0;
}
Example
#include <iostream>

int main() {
int x = 10;
auto modifyX = [&x]() { x = 20; };
modifyX();
std::cout << "Modified x: " << x << std::endl; // Output: Modified x: 20 Output:
return 0;
}
Example

#include <iostream>

int main() {
auto multiply = [](int a, int b) -> int { return a * b; };
std::cout << "Product: " << multiply(3, 4) << std::endl; // Output: Product: 12 Output:
return 0;
}

You might also like