Skip to content

Commit 8cfbcb0

Browse files
authored
Create README.md
1 parent 2fd4efe commit 8cfbcb0

File tree

1 file changed

+332
-0
lines changed
  • 20 - Hashmap Data Structure Problems/01 - Example

1 file changed

+332
-0
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
<h1 align='center'>Hashmap - Data Structure - Example</h1>
2+
3+
A **Hashmap** is a data structure that stores key-value pairs and provides efficient methods for insertion, deletion, and search operations based on keys. The key-value pair concept is essential for many real-world applications such as caching, databases, counting occurrences, and more.
4+
5+
### Key Concepts of Hashmaps
6+
7+
- **Key-Value Pair:** Each entry in a hashmap consists of a unique key and an associated value.
8+
- **Hash Function:** The key is passed through a hash function, which maps it to an index in an array. This index is where the value will be stored.
9+
- **Collision:** When two keys hash to the same index, a collision occurs. Several strategies (like chaining or open addressing) are used to handle this.
10+
11+
### Why Use Hashmaps?
12+
13+
- **Efficient Searching:** Hashmaps allow for quick lookups. Instead of searching through a list or array, the hashmap provides direct access to the value associated with a key using its hash.
14+
- **Insertion and Deletion:** Hashmaps allow for fast insertion and deletion of key-value pairs. This makes them ideal for situations where you need to frequently add, remove, or update data.
15+
- **No Duplicates for Keys:** Keys in a hashmap are unique, meaning no two entries can have the same key, but they can have the same value.
16+
17+
18+
### Hashmap Operations in C++
19+
20+
In the provided C++ code, we use **`unordered_map`** from the C++ Standard Library, which is an implementation of a hashmap. Here’s a breakdown of the code:
21+
22+
```cpp
23+
#include <iostream>
24+
#include <map>
25+
#include <unordered_map>
26+
using namespace std;
27+
28+
int main() {
29+
// Creation of Unordered Map
30+
unordered_map<string, int> um;
31+
32+
// Insertion (Multiple Approaches)
33+
um.insert(make_pair("Hello", 1)); // Approach 1
34+
um.insert({"World", 2}); // Approach 2
35+
um["NewWorld"] = 3; // Approach 3
36+
um["World"] = 4; // Approach 4
37+
38+
cout << "-- Searching --" << endl;
39+
// Searching
40+
cout << "Value of 'World' Key : " << um["World"] << endl; // Access value using the key
41+
cout << "Value of 'World' Key : " << um.at("NewWorld") << endl; // at() with exception if key doesn't exist
42+
cout << "Value of 'unknown' Key : " << um["unknown"] << endl; // Default value if key doesn't exist (0)
43+
44+
// Updation
45+
um["World"] = 5;
46+
47+
cout << "-- Checking Size --" << endl;
48+
// Size of hashmap
49+
cout << "Size of unordered_map : " << um.size() << endl;
50+
51+
cout << "-- Check Presence --" << endl;
52+
// Check if a key exists in the hashmap
53+
cout << "Is 'World' Key Present : " << um.count("World") << endl; // True if key exists
54+
cout << "Is 'Unknown' Key Present : " << um.count("Unknown") << endl; // False if key doesn't exist
55+
56+
// Erase
57+
um.erase("World");
58+
cout << "Size of unordered_map after erase: " << um.size() << endl;
59+
60+
cout << "-- For Loop Iteration --" << endl;
61+
// Iterating over all key-value pairs
62+
for (auto i : um) cout << "Key: " << i.first << ", Value: " << i.second << endl;
63+
64+
cout << "-- Iterators --" << endl;
65+
// Using iterator to iterate
66+
unordered_map<string, int>::iterator it = um.begin();
67+
while (it != um.end()) {
68+
cout << "Key: " << it->first << ", Value: " << it->second << endl;
69+
it++;
70+
}
71+
72+
return 0;
73+
}
74+
```
75+
76+
#### Example Output
77+
Here is an example output for the provided C++ code using `unordered_map`:
78+
79+
```text
80+
-- Searching --
81+
Value of 'World' Key : 4
82+
Value of 'NewWorld' Key : 3
83+
Value of 'unknown' Key : 0
84+
-- Checking Size --
85+
Size of unordered_map : 3
86+
-- Check Presence --
87+
Is 'World' Key Present : 1
88+
Is 'Unknown' Key Present : 0
89+
Size of unordered_map after erase: 2
90+
-- For Loop Iteration --
91+
Key: Hello, Value: 1
92+
Key: NewWorld, Value: 3
93+
-- Iterators --
94+
Key: Hello, Value: 1
95+
Key: NewWorld, Value: 3
96+
```
97+
98+
### Explanation of Output:
99+
100+
1. **Searching**:
101+
- For the key `"World"`, the value `4` is returned.
102+
- For the key `"NewWorld"`, the value `3` is returned.
103+
- For the key `"unknown"`, since it doesn't exist in the map, it returns the default value `0` (because of the use of `[]` operator).
104+
105+
2. **Updation**:
106+
- The value for the `"World"` key is updated to `5`.
107+
108+
3. **Checking Size**:
109+
- The size of the `unordered_map` is `3` after inserting the keys `"Hello"`, `"World"`, and `"NewWorld"`.
110+
111+
4. **Check Presence**:
112+
- The key `"World"` exists in the map, so `count("World")` returns `1`.
113+
- The key `"Unknown"` does not exist, so `count("Unknown")` returns `0`.
114+
115+
5. **Erase**:
116+
- The key `"World"` is erased, so the new size of the map is `2`.
117+
118+
6. **Iteration (For Loop)**:
119+
- Iterates over all remaining key-value pairs in the unordered_map and prints them out:
120+
- `"Hello" : 1`
121+
- `"NewWorld" : 3`
122+
123+
7. **Iteration (Iterator)**:
124+
- Uses an iterator to iterate over the `unordered_map` and prints the key-value pairs:
125+
- `"Hello" : 1`
126+
- `"NewWorld" : 3`
127+
128+
---
129+
### Time and Space Complexities of Hashmaps
130+
131+
#### 1. **Time Complexity:**
132+
133+
- **Insertion:** `O(1)` average case, since the hash function directly computes the index.
134+
- Worst case: `O(n)` if many keys hash to the same index (collisions), but this is rare with a good hash function and load factor management.
135+
- **Search:** `O(1)` average case.
136+
- Worst case: `O(n)` if collisions occur, but it's also rare.
137+
- **Deletion:** `O(1)` average case.
138+
- Worst case: `O(n)` in case of a large number of collisions.
139+
140+
In the worst case, where collisions are frequent, the time complexity can degrade to `O(n)` because the hashmap becomes a linked list (if chaining is used).
141+
142+
#### 2. **Space Complexity:**
143+
- **Space Complexity:** `O(n)`, where `n` is the number of key-value pairs stored in the hashmap. This is because each key-value pair is stored in memory, and additional space is required for internal storage (array of buckets).
144+
- The space complexity can also be influenced by the load factor and the number of buckets in the hashmap. A high load factor may require resizing the hashmap, which could temporarily use more space.
145+
146+
147+
### Why is a Hashmap Better than Other Data Structures?
148+
149+
- **Arrays:** Arrays require searching through every element (`O(n)`) for a key or value. Hashmaps offer `O(1)` search time in the average case.
150+
- **Linked Lists:** Linked lists also have an `O(n)` search time for a given value. Hashmaps provide direct access based on the key.
151+
- **Trees:** Binary search trees (BST) can provide `O(log n)` search times. However, hashmaps are usually faster for lookups, as they offer `O(1)` time in the average case.
152+
- **Dictionaries (Python):** Similar to hashmaps, Python dictionaries are implemented using hashmaps under the hood, offering efficient key-value pair storage.
153+
154+
---
155+
### Explain of Hash Table
156+
A **Hash Table** (or **Hash Map**) is a data structure that stores key-value pairs and enables efficient lookups, insertions, and deletions. It operates based on a **hash function**, which computes an index (or "hash") from a key, and stores the corresponding value in the array at that index. The primary strength of a hash table lies in its ability to provide average **constant time complexity** (`O(1)`) for operations like searching, insertion, and deletion.
157+
158+
#### Basic Components of a Hash Table
159+
1. **Keys**: Unique identifiers used to access the values.
160+
2. **Values**: The data associated with each key.
161+
3. **Hash Function**: A function that computes an index (hash) for each key, based on the key's value.
162+
4. **Buckets**: The array where the key-value pairs are stored, with each index in the array representing a bucket.
163+
5. **Collisions**: When two keys hash to the same index, a collision occurs. This is managed using techniques like **chaining** or **open addressing**.
164+
165+
#### How Hash Table Works
166+
167+
1. **Inserting Elements**:
168+
- The hash function computes an index from the key.
169+
- The key-value pair is stored at the corresponding index (bucket).
170+
- If the index is already occupied (collision), the collision resolution strategy is used.
171+
172+
2. **Searching for Elements**:
173+
- The hash function computes the index for the given key.
174+
- If the key exists at that index, the value is returned.
175+
- If the key is not found at the computed index, the collision resolution method checks other buckets.
176+
177+
3. **Deleting Elements**:
178+
- The hash function computes the index for the key.
179+
- If the key is found, it is deleted.
180+
- If the key is not found at the index, the collision resolution method checks other buckets.
181+
182+
###- Collision Resolution Strategies
183+
184+
1. **Chaining**:
185+
- Each bucket in the hash table is a list (or another container).
186+
- When a collision occurs, the key-value pair is simply added to the list at the corresponding index.
187+
- **Advantages**: Simple to implement, and it allows for an arbitrary number of elements per bucket.
188+
- **Disadvantages**: If many elements hash to the same index, the performance degrades to `O(n)` for search, insertion, and deletion.
189+
190+
**Example of Chaining:**
191+
Suppose we have a hash table with 5 buckets, and we use a hash function that maps keys to indices as follows:
192+
193+
| Key | Hash Function | Bucket Index |
194+
|----|----|----|
195+
| "apple"| 2 | 2 |
196+
| "banana"| 3 | 3 |
197+
| "cherry"| 2 | 2 |
198+
199+
After inserting the above key-value pairs into the hash table, the buckets will look like this:
200+
201+
```
202+
Bucket 0: []
203+
Bucket 1: []
204+
Bucket 2: [("apple", value1), ("cherry", value2)]
205+
Bucket 3: [("banana", value3)]
206+
Bucket 4: []
207+
```
208+
209+
2. **Open Addressing**:
210+
- When a collision occurs, the hash table searches for the next available bucket using a probe sequence (like linear probing, quadratic probing, or double hashing).
211+
- **Advantages**: More memory-efficient than chaining, as it uses a single array to store all elements.
212+
- **Disadvantages**: Performance can degrade if the table becomes too full or if there are many collisions.
213+
214+
**Example of Linear Probing (Open Addressing)**:
215+
Suppose we have a hash table with 5 buckets, and we use linear probing for collision resolution. Our keys are "apple," "banana," and "cherry."
216+
217+
```
218+
Bucket 0: ["apple"]
219+
Bucket 1: [empty]
220+
Bucket 2: [empty]
221+
Bucket 3: ["banana"]
222+
Bucket 4: ["cherry"]
223+
```
224+
225+
If "banana" hashes to index 3 but is already occupied, we look at the next index (4). If that index is also full, we continue searching linearly for the next available slot.
226+
227+
#### Time and Space Complexity of Hash Tables
228+
229+
##### 1. **Average Case Time Complexity**:
230+
- **Insertions**: `O(1)`
231+
- **Search**: `O(1)`
232+
- **Deletion**: `O(1)`
233+
- These operations are constant time because the hash function directly maps keys to their respective buckets.
234+
235+
#### 2. **Worst Case Time Complexity**:
236+
- In case of many collisions (e.g., when all keys hash to the same bucket), the time complexity can degrade to `O(n)` for operations like search, insert, and delete, especially in the case of chaining where elements are stored in a list.
237+
238+
#### 3. **Space Complexity**:
239+
- The space complexity is **O(n)**, where `n` is the number of key-value pairs in the hash table. The space complexity depends on the number of buckets in the hash table and how well the hash function distributes keys across the buckets.
240+
241+
#### Why Use Hash Tables?
242+
243+
1. **Efficiency**:
244+
- Hash tables provide **average O(1)** time complexity for common operations such as **insertions, lookups, and deletions**.
245+
- This makes them ideal for scenarios where fast access to data is required.
246+
247+
2. **Key-Value Mapping**:
248+
- Hash tables allow you to efficiently map unique keys to specific values, which is useful in many real-world applications such as caching, indexing, and database management.
249+
250+
3. **Avoiding Linear Search**:
251+
- Without hash tables, we often need to perform a **linear search** through a list to find a specific element, which can be slow (O(n) time complexity). Hash tables provide a much faster alternative.
252+
253+
4. **Dynamic Resizing**:
254+
- Many hash table implementations dynamically resize when the load factor (the ratio of the number of elements to the number of buckets) becomes too high. This helps maintain efficient performance as the number of stored elements grows.
255+
256+
#### Example Use Cases for Hash Tables
257+
258+
1. **Caching**:
259+
- **Web Caching**: A hash table can store recently accessed web pages, where the key is the URL and the value is the content of the page.
260+
261+
2. **Dictionary Implementations**:
262+
- **Word Lookup**: In a dictionary application, hash tables can be used to store words (keys) and their definitions (values), allowing for fast lookups.
263+
264+
3. **Counting Elements**:
265+
- Hash tables are commonly used for counting occurrences of elements in a collection, like counting word frequency in a text document.
266+
267+
#### Code Example: Basic Hash Table in C++
268+
269+
```cpp
270+
#include <iostream>
271+
#include <unordered_map>
272+
using namespace std;
273+
274+
int main() {
275+
unordered_map<string, int> hashTable;
276+
277+
// Inserting key-value pairs
278+
hashTable["apple"] = 5;
279+
hashTable["banana"] = 3;
280+
hashTable["orange"] = 4;
281+
282+
// Searching for a value using key
283+
cout << "apple: " << hashTable["apple"] << endl; // Output: 5
284+
285+
// Checking if a key exists
286+
if (hashTable.find("banana") != hashTable.end()) {
287+
cout << "banana found" << endl; // Output: banana found
288+
}
289+
290+
// Updating the value for an existing key
291+
hashTable["banana"] = 7;
292+
cout << "banana: " << hashTable["banana"] << endl; // Output: 7
293+
294+
// Deleting a key-value pair
295+
hashTable.erase("orange");
296+
297+
// Iterating through hash table
298+
cout << "Hash table contents:" << endl;
299+
for (auto it = hashTable.begin(); it != hashTable.end(); ++it) {
300+
cout << it->first << ": " << it->second << endl;
301+
}
302+
303+
return 0;
304+
}
305+
```
306+
307+
#### Output:
308+
309+
```
310+
apple: 5
311+
banana found
312+
banana: 7
313+
Hash table contents:
314+
apple: 5
315+
banana: 7
316+
```
317+
318+
319+
320+
#### Summary:
321+
- A **hash table** is a data structure that stores key-value pairs, with efficient **O(1)** time complexity for insertions, lookups, and deletions on average.
322+
- The **hash function** is used to map keys to an index in the underlying array (buckets).
323+
- **Collisions** are handled using techniques like **chaining** or **open addressing**.
324+
- Hash tables are **memory-efficient** and provide fast access to data, making them ideal for scenarios requiring quick lookups, like caching and dictionary implementations.
325+
326+
---
327+
### Why Use `unordered_map` (Hashmap) in C++?
328+
329+
- **Efficient Search Operations:** Hashmaps offer constant time complexity for insertions, deletions, and lookups in the average case, which makes them more efficient than other data structures like arrays or linked lists.
330+
- **No Sorting Required:** `unordered_map` doesn't maintain any order of keys, but if the order isn't important, this is an advantage as it allows faster performance for key operations.
331+
332+
In summary, hashmaps are crucial in scenarios where fast lookups, insertions, and deletions are needed. They are particularly useful for caching, counting occurrences of items, and associating unique keys with specific values in an efficient manner.

0 commit comments

Comments
 (0)