Lab8-Hash
Lab8-Hash
Lab 6
• Deterministic: The same input should always produce the same output.
• Uniform Distribution: The function should map inputs as evenly as possible over the
output range.
• Low Collision Rate: Different inputs should rarely map to the same output.
For a hash function h and a key k, the hash value (or hash code) is calculated as:
1. Division Method: h(k) = k mod m, where m is the size of the hash table.
3. Universal Hashing: A family of hash functions chosen randomly to ensure good average-
case performance.
Figure 1: A simple hash table with string keys and integer values
The main advantage of hash tables is their efficiency—they provide constant-time average-case
performance O(1) for basic operations like insertion, deletion, and lookup, regardless of the number
of elements stored.
1. Separate Chaining: Each bucket holds a linked list of all key-value pairs that hash to the
same index.
lookup time = O(1 + α) (2)
where α is the load factor (number of elements divided by the number of buckets).
2. Open Addressing: All elements are stored directly in the hash table array. When a collision
occurs, we probe for an empty slot according to some probing sequence:
• Linear Probing: h(k, i) = (h(k) + i) mod m, where i is the probe sequence number.
• Quadratic Probing: h(k, i) = (h(k) + c1 i + c2 i2 ) mod m, where c1 , c2 are constants.
• Double Hashing: h(k, i) = (h1 (k) + i · h2 (k)) mod m, using two different hash func-
tions.
• Load Factor (α): The ratio of the number of elements to the table size.
n
α= (3)
m
• Time Complexity:
1.5 Applications
Hash tables are widely used in various applications:
In this lab, you will implement various hash functions and hash table operations, exploring
the trade-offs between different collision resolution strategies and analyzing their performance
characteristics.
2. The function should return an integer index within the range [0, tableSize-1]
3. Use modular arithmetic to ensure the hash value fits within the table size
Example Input/Output
1. Input:
2. Expected output:
1 Hash values for different keys ( table size = 10) :
2 Key : apple , Hash value : 0
3 Key : banana , Hash value : 9
4 Key : cherry , Hash value : 3
5 Key : date , Hash value : 4
6 Key : elderberry , Hash value : 2
13 int main () {
14 std :: string keys [] = { " apple " , " banana " , " cherry " , " date " , " elderberry " };
15 int tableSize = 10;
16
17 std :: cout << " Hash values for different keys ( table size = " << tableSize
<< " ) : " << std :: endl ;
18 for ( const auto & key : keys ) {
19 std :: cout << " Key : " << key << " , Hash value : " << hashFunction ( key ,
tableSize ) << std :: endl ;
20 }
21
22 return 0;
23 }
Furthermore, you can leverage some hash function to improve the hashing performance:
1. Polynomial Hash Function: A polynomial hash function considers character positions, mak-
ing it more sensitive to character order
1 int i m p r o v e d H a s h F u n c t i o n 1 ( const std :: string & key , int tableSize ) {
2 long hash = 0;
3 const int p = 31; // Prime number
4 long p_pow = 1;
5
11 return hash ;
12 }
2. djb2 Hash Function: A well-known string hash function with good distribution properties
1 int i m p r o v e d H a s h F u n c t i o n 2 ( const std :: string & key , int tableSize ) {
2 unsigned long hash = 5381;
3
3. FNV-1a Hash Function: Fast with good distribution and low collision rates
1 int i m p r o v e d H a s h F u n c t i o n 3 ( const std :: string & key , int tableSize ) {
2 const unsigned int fnv_prime = 16777619;
3 unsigned int hash = 2166136261;
4
1. Implement the detectCollisions() function that identifies which indices have collisions
3. Print all indices that have more than one key (collisions)
Example Input/Output
1. Input:
• Input keys: ”cat”, ”dog”, ”rat”, ”pig”, ”owl”, ”fox”, ”hen”, ”ant”, ”bee”
• Table size: 7
2. Expected output:
1 Detecting collisions for table size 7:
2 Collision at index 4:
3 - " cat "
4 - " fox "
5 Collision at index 5:
6 - " rat "
7 - " pig "
8 Collision at index 6:
9 - " dog "
10 - " bee "
14 void detectCollisions ( const std :: vector < std :: string >& keys , int tableSize ) {
15 // TODO : Create a vector to track how many keys hash to each index
16 // Print out all indices that have more than one key ( collisions )
17 // For each collision , print the keys that collided
18 }
19
20 int main () {
21 std :: vector < std :: string > keys = { " cat " , " dog " , " rat " , " pig " , " owl " , " fox " ,
" hen " , " ant " , " bee " };
22 int tableSize = 7;
23
24 std :: cout << " Detecting collisions for table size " << tableSize << " : " <<
std :: endl ;
25 detectCollisions ( keys , tableSize ) ;
26
27 return 0;
28 }
1. Use Prime Number Table Sizes: Prime numbers help distribute hash values more evenly
1 // Choose a prime number close to but larger than your expected number of
elements
2 int betterTableSize = 11; // Instead of 10
2. Universal Hashing: Using different hash functions randomly from a carefully designed family
1 int u n i v e r s a l H a s h F u n c t i o n ( const std :: string & key , int tableSize , int a ,
int b , int p ) {
2 // p is a prime larger than the largest possible character value
3 // a and b are random integers between 1 and p -1
4 long hash = 0;
5 for ( char c : key ) {
6 hash = ( hash * a + static_cast < int >( c ) ) % p ;
7 }
8 return ( hash % tableSize ) ;
9 }
2. Implement the insert method that handles collisions using linear probing
3. Implement the search method that can find values even after collision resolution
4. The hash table should store key-value pairs where keys are strings and values are integers
Example Input/Output
1. Input:
• Insert ("apple", 5); Insert ("banana", 8); Insert ("cherry", 3); Insert ("date",
12); Insert ("grape", 10); Insert ("lemon", 7)
• Search for "banana"; Search for "kiwi"
2. Expected output:
1 Hash Table Contents :
2 0: apple -> 5
3 1: lemon -> 7
4 2: Empty
5 3: cherry -> 3
6 4: date -> 12
7 5: Empty
8 6: Empty
9 7: grape -> 10
10 8: Empty
11 9: banana -> 8
12
13 Lookup Operations :
14 Found banana with value 8
15 Could not find kiwi
6 class HashTable {
7 private :
8 struct Entry {
9 std :: string key ;
10 int value ;
11 bool isOccupied ;
12
27 public :
28 HashTable ( int tableSize ) : size ( tableSize ) {
29 table . resize ( size ) ;
30 }
31
64 int main () {
65 HashTable ht (10) ;
66
74 std :: cout << " Hash Table Contents : " << std :: endl ;
75 ht . print () ;
76
77 std :: cout << " \ nLookup Operations : " << std :: endl ;
78 int value ;
79 if ( ht . search ( " banana " , value ) ) {
80 std :: cout << " Found banana with value " << value << std :: endl ;
81 } else {
82 std :: cout << " Could not find banana " << std :: endl ;
83 }
84
91 return 0;
92 }
Furthermore, you can leverage some techniques to improve the hashing performance:
1. Adding Deletion Capability: Add a method to delete entries while properly handling the
probe sequence
1 bool remove ( const std :: string & key ) {
2 // Find the key using linear probing
3 // Mark the slot as deleted but not empty ( tombstone )
4 // This requires adding a " isDeleted " flag to the Entry struct
5 // Return true if successful , false if key not found
6 }
2. Load Factor Tracking: Add functionality to monitor and maintain an efficient load factor
1 float getLoadFactor () {
2 int occupiedCount = 0;
3 for ( const auto & entry : table ) {
4 if ( entry . isOccupied ) {
5 occupiedCount ++;
6 }
7 }
8 return static_cast < float >( occupiedCount ) / size ;
9 }
10
11 void rehash () {
12 // Implement a rehashing mechanism when load factor exceeds threshold
13 // Create a new table with larger size
14 // Re - insert all elements from the old table
15 }
3. Quadratic Probing
1 // In insert method :
2 int i = 0;
3 int index = ( initialHash + i * i ) % size ; // Use quadratic sequence instead
of linear
2. Implement the insert method that handles collisions using quadratic probing
3. Implement the search method that can find values even after collision resolution
4. Ensure the table returns false if it becomes more than 70% full
Example Input/Output
1. Input:
2. Expected output:
1 Inserted apple
2 Inserted banana
3 Inserted cherry
4 Inserted date
5 Inserted grape
6 Inserted lemon
7 Inserted orange
8 Inserted pear
9 Inserted fig
10
6 class HashTable {
7 private :
8 struct Entry {
9 std :: string key ;
10 int value ;
11 bool isOccupied ;
12 bool isDeleted ; // For handling deletions
13
14 Entry () : key ( " " ) , value (0) , isOccupied ( false ) , isDeleted ( false ) {}
15 };
16
27 }
28
29 public :
30 HashTable ( int tableSize ) : size ( tableSize ) , count (0) {
31 table . resize ( size ) ;
32 }
33
65 int main () {
66 HashTable ht (13) ; // Using a prime number for table size is recommended
68 std :: vector < std :: pair < std :: string , int > > data = {
69 { " apple " , 5} , { " banana " , 8} , { " cherry " , 3} ,
70 { " date " , 12} , { " grape " , 10} , { " lemon " , 7} ,
71 { " orange " , 9} , { " pear " , 4} , { " fig " , 6}
72 };
73
82 std :: cout << " \ nHash Table Contents : " << std :: endl ;
83 ht . print () ;
84
85 return 0;
86 }
Furthermore, you can leverage some techniques to improve the hashing performance:
1. Double Hashing: Combine quadratic probing with a second hash function for better distri-
bution
1 int secondHash ( const std :: string & key ) {
2 int hash = 0;
3 for ( char c : key ) {
4 hash = hash * 17 + c ;
5 }
6 return 1 + ( hash % ( size - 1) ) ; // Ensure result is between 1 and size
-1
7 }
8
2. Automatic Rehashing: Implement a rehashing mechanism when the load factor exceeds a
threshold
1 void rehash () {
2 std :: vector < Entry > oldTable = table ;
3 int oldSize = size ;
4
3. Prime-Sized Tables: Always use prime numbers for the table size to ensure optimal distri-
bution with quadratic probing
1 // Recall your simple check prime function in your Fundamental class
2 bool isPrime ( int n ) {
3 if ( n <= 1) return false ;
4 if ( n <= 3) return true ;
5 if ( n % 2 == 0 || n % 3 == 0) return false ;
6
16 int prime = n ;
17 bool found = false ;
18
19 while (! found ) {
20 prime ++;
21 if ( isPrime ( prime ) ) found = true ;
22 }
23
24 return prime ;
25 }
3. Implement the insert() method that handles collisions using double hashing
4. Implement the search() method that can find values after collision resolution
5. The hash table should store key-value pairs where keys are strings and values are integers
Example Input/Output
1. Input:
2. Expected output:
1 Inserted cat
2 Inserted dog
3 Inserted bird
4 Inserted fish
5 Inserted lion
6 Inserted tiger
7 Inserted bear
8 Inserted wolf
9 Inserted fox
10 Inserted deer
11
6 class HashTable {
7 private :
8 struct Entry {
9 std :: string key ;
10 int value ;
11 bool isOccupied ;
12
39 public :
40 HashTable ( int tableSize ) : size ( tableSize ) , count (0) {
41 table . resize ( size ) ;
42 }
43
61 void print () {
62 for ( int i = 0; i < size ; i ++) {
63 if ( table [ i ]. isOccupied ) {
64 std :: cout << i << " : " << table [ i ]. key << " -> " << table [ i ].
value << std :: endl ;
65 } else {
66 std :: cout << i << " : Empty " << std :: endl ;
67 }
68 }
69 }
70 };
71
72 int main () {
73 HashTable ht (13) ; // Using a prime number for table size
74
75 std :: vector < std :: pair < std :: string , int > > data = {
76 { " cat " , 1} , { " dog " , 2} , { " bird " , 3} , { " fish " , 4} ,
77 { " lion " , 5} , { " tiger " , 6} , { " bear " , 7} , { " wolf " , 8} ,
78 { " fox " , 9} , { " deer " , 10}
79 };
80
89 std :: cout << " \ nHash Table Contents : " << std :: endl ;
90 ht . print () ;
91
92 int value ;
93 if ( ht . search ( " tiger " , value ) ) {
94 std :: cout << " \ nFound tiger with value " << value << std :: endl ;
95 } else {
96 std :: cout << " \ nCould not find tiger " << std :: endl ;
97 }
98
99 return 0;
100 }
Furthermore, you can leverage some techniques to improve the hashing performance:
1. Better Secondary Hash Function: A popular secondary hash function is to use a prime
number smaller than the table size: PRIME - (key % PRIME) where PRIME is a prime smaller
than the TABLE SIZE.
2. Load Factor Monitoring: Add functionality to track and respond to the hash table’s load
factor
3. Handling Deletion: When deleting elements, it’s important to place what is called a “tomb-
stone” at the location rather than simply marking it as empty. This allows the search se-
quence to continue past deleted elements. This can be implemented by adding an isDeleted
flag to the Entry structure. Please check this link for more information.
2. The function should take a key, a constant A (between 0 and 1), and the table size as
parameters
3. Test the function with different values of A to observe how the distribution changes
4. Analyze the distribution of hash values for different keys and constants
Example Input/Output
1. Input:
• Keys: 123, 456, 789, 101, 202, 303, 404, 505, 606, 707
• Constant A: 0.6180339887 (golden ratio minus 1)
• Table size: 10
2. Expected output:
18 int main () {
19 std :: vector < int > keys = {123 , 456 , 789 , 101 , 202 , 303 , 404 , 505 , 606 , 707};
20 double A = 0.6180339887; // ( sqrt (5) - 1) / 2 , a popular choice
21 int tableSize = 10;
22
23 std :: cout << " Hash values using multiplication method ( table size = " <<
tableSize << " ) : " << std :: endl ;
24 for ( int key : keys ) {
25 std :: cout << " Key : " << key << " , Hash : " << universalHash ( key , A ,
tableSize ) << std :: endl ;
26 }
27
31 std :: cout << " \ nHash distribution with different values of A : " << std :: endl
;
32 for ( double a : AValues ) {
33 std :: cout << " A = " << a << " : " ;
34 for ( int key : keys ) {
35 std :: cout << universalHash ( key , a , tableSize ) << " " ;
36 }
37 std :: cout << std :: endl ;
38 }
39
40 return 0;
41 }
Theoretical Background
The multiplication method involves choosing a table size m that is a power of 2 and a constant
A that is a random-looking real number. Knuth suggests using A = 0.5*(sqrt(5) - 1), which
is the golden ratio minus 1. This value of A helps ensure a good distribution of hash values.
Multiplicative hashing works by setting the hash index from the fractional part of multiplying
the key by a large real number. For computational efficiency, this is typically done using fixed-point
arithmetic rather than floating-point operations.
The choice of A as related to the golden ratio is interesting because repeated multiplication by
this value minimizes gaps in the hash space, creating a well-distributed sequence of values.
• Average case time complexity: O(1) for insert, search, and delete operations
• Worst case (when all keys hash to the same bucket): O(n) where n is the number of elements
3. Implement the insert method to add new key-value pairs or update existing ones
6. The hash table should store key-value pairs where keys are strings and values are integers
Example Input/Output
1. Input:
2. Expected output:
1 Hash Table Contents :
2 Bucket 0: ( cherry , 3) ( peach , 15)
3 Bucket 1: ( date , 12) ( kiwi , 11)
4 Bucket 2: ( mango , 2)
5 Bucket 3: ( grape , 10)
6 Bucket 4: ( apple , 5) ( lemon , 7)
7 Bucket 5: ( banana , 8) ( orange , 9)
8 Bucket 6: ( pear , 4) ( fig , 6)
9
10 Lookup Operations :
11 Found orange with value 9
12
6 class HashTable {
7 private :
8 struct Node {
9 std :: string key ;
10 int value ;
11 Node * next ;
12
13 Node ( const std :: string & k , int v ) : key ( k ) , value ( v ) , next ( nullptr ) {}
14 };
15
27 public :
28 HashTable ( int tableSize ) : size ( tableSize ) {
29 table . resize ( size , nullptr ) ;
30 }
31
32 ~ HashTable () {
33 // TODO : Free all allocated memory to prevent memory leaks
34 }
35
42 }
43
62 void print () {
63 for ( int i = 0; i < size ; i ++) {
64 std :: cout << " Bucket " << i << " : " ;
65 Node * current = table [ i ];
66 if (! current ) {
67 std :: cout << " Empty " ;
68 }
69 while ( current ) {
70 std :: cout << " ( " << current - > key << " , " << current - > value << "
) ";
71 current = current - > next ;
72 }
73 std :: cout << std :: endl ;
74 }
75 }
76 };
77
78 int main () {
79 HashTable ht (7) ;
80
81 std :: vector < std :: pair < std :: string , int > > data = {
82 { " apple " , 5} , { " banana " , 8} , { " cherry " , 3} , { " date " , 12} ,
83 { " grape " , 10} , { " lemon " , 7} , { " orange " , 9} , { " pear " , 4} ,
84 { " fig " , 6} , { " kiwi " , 11} , { " mango " , 2} , { " peach " , 15}
85 };
86
91 std :: cout << " Hash Table Contents : " << std :: endl ;
92 ht . print () ;
93
94 std :: cout << " \ nLookup Operations : " << std :: endl ;
95 int value ;
96 if ( ht . search ( " orange " , value ) ) {
97 std :: cout << " Found orange with value " << value << std :: endl ;
98 } else {
99 std :: cout << " Could not find orange " << std :: endl ;
100 }
101
102 std :: cout << " \ nAfter removing ’ banana ’ and ’ fig ’: " << std :: endl ;
103 ht . remove ( " banana " ) ;
104 ht . remove ( " fig " ) ;
105 ht . print () ;
106
107 return 0;
108 }
3. Insert random keys until reaching different load factor thresholds (e.g., 0.1, 0.2, ..., 0.9)
4. For each load factor threshold, perform a fixed number of lookup operations
5. Measure and report the average number of probes needed for operations
Example Input/Output
1. Expected output:
1 Performance Analysis of Hash Table with Different Load Factors
2 --------------------------------------------------------------
3 Table size : 1000
4 Operations per load factor : 1000
5
6 Load Factor | Avg Probes ( Insert ) | Avg Probes ( Search ) | Avg Probes (
Failed Search )
7 ---------------------------------------------------------------------------
9 class HashTable {
10 private :
11 struct Entry {
12 std :: string key ;
13 int value ;
14 bool isOccupied ;
15
32 public :
33 HashTable ( int tableSize ) : size ( tableSize ) , count (0) , probeCount (0) {
34 table . resize ( size ) ;
35 }
36
60 i ++;
61 }
62
80 i ++;
81 }
82
90 void resetProbeCount () {
91 probeCount = 0;
92 }
93
97 };
98
120 // TODO : Implement a function to analyze how load factor affects performance
121 void ana lyzeL oadFac tor () {
122 // 1. Create a hash table with a large size ( e . g . , 1000)
123 // 2. Insert random keys until reaching different load factors ( e . g . , 0.1 ,
0.2 , ... , 0.9)
124 // 3. For each load factor , perform a fixed number of random lookups
125 // 4. Measure the average number of probes needed for successful and
unsuccessful lookups
126 // 5. Print out the results and draw conclusions
127 }
128
1. Complete the implementation of a cuckoo hash table class with two tables
2. Implement the insert() method that handles collisions using the cuckoo algorithm
3. Implement the search() method that looks for keys in both tables
4. Implement the rehash() method to resize the table when cycles are detected
5. The hash table should store key-value pairs where keys are strings and values are integers
Example Input/Output
2. Expected output:
1 Inserted apple
2 Inserted banana
3 Inserted cherry
4 Inserted date
5 Inserted grape
6 Inserted lemon
7 Inserted orange
8 Inserted pear
9
20 8: pear -> 4
21 9: Empty
22
23 Table 2:
24 0: grape -> 10
25 1: Empty
26 2: apple -> 5
27 3: Empty
28 4: cherry -> 3
29 5: Empty
30 6: lemon -> 7
31 7: Empty
32 8: Empty
33 9: Empty
34
7 class CuckooHashTable {
8 private :
9 struct Entry {
10 std :: string key ;
11 int value ;
12 bool isOccupied ;
13
41 public :
42 CuckooHashTable ( int tableSize ) : size ( tableSize ) , count (0) , maxLoop (
tableSize ) {
43 table1 . resize ( size ) ;
44 table2 . resize ( size ) ;
45 }
46
64
81 void print () {
82 std :: cout << " Table 1: " << std :: endl ;
83 for ( int i = 0; i < size ; i ++) {
84 if ( table1 [ i ]. isOccupied ) {
85 std :: cout << i << " : " << table1 [ i ]. key << " -> " << table1 [ i ].
value << std :: endl ;
86 } else {
87 std :: cout << i << " : Empty " << std :: endl ;
88 }
89 }
90
104
105 std :: vector < std :: pair < std :: string , int > > data = {
106 { " apple " , 5} , { " banana " , 8} , { " cherry " , 3} , { " date " , 12} ,
107 { " grape " , 10} , { " lemon " , 7} , { " orange " , 9} , { " pear " , 4}
108 };
109
124 std :: cout << " \ nCuckoo Hash Table Contents : " << std :: endl ;
125 ht . print () ;
126
127 std :: cout << " \ nCurrent load factor : " << ht . getLoadFactor () << std :: endl ;
128
136 return 0;
137 }
2. Implement the build() function to construct the hash table from a fixed set of key-value
pairs
4. Ensure the hash table uses O(n) space overall, where n is the number of elements
8 class PerfectHashTable {
9 private :
10 struct SecondaryTable {
11 std :: vector < std :: pair < std :: string , int > > entries ;
12 int size ;
13 double a ; // Universal hash function parameter
14
27 }
28 };
29
41 public :
42 PerfectHashTable ( int tableSize ) : size ( tableSize ) {
43 // Initialize with nullptrs to create secondary tables only when needed
44 primaryTable . resize ( size , nullptr ) ;
45 }
46
47 ~ PerfectHashTable () {
48 // Free all secondary tables
49 for ( SecondaryTable * table : primaryTable ) {
50 delete table ;
51 }
52 }
53
54 // TODO : Implement the build function to construct the perfect hash table
55 void build ( const std :: vector < std :: pair < std :: string , int > >& data ) {
56 // 1. Distribute items into buckets using the primary hash function
57 // 2. For each non - empty bucket , create a secondary table with size = (
number of items ) ^2
58 // 3. Choose a proper hash function for each secondary table to avoid
collisions
59 // 4. Insert items into secondary tables
60 }
61
71 void print () {
72 for ( int i = 0; i < size ; i ++) {
73 std :: cout << " Primary bucket " << i << " : " ;
74 if (! primaryTable [ i ]) {
75 std :: cout << " Empty " << std :: endl ;
76 continue ;
77 }
78
79 std :: cout << " Secondary table size = " << primaryTable [ i ] - > size <<
std :: endl ;
80 for ( int j = 0; j < primaryTable [ i ] - > size ; j ++) {
81 if ( primaryTable [ i ] - > entries [ j ]. second != -1) {
82 std :: cout << " " << j << " : " << primaryTable [ i ] - > entries [
j ]. first
83 << " -> " << primaryTable [ i ] - > entries [ j ]. second
<< std :: endl ;
84 }
85 }
86 }
87 }
88 };
89
90 int main () {
91 std :: vector < std :: pair < std :: string , int > > data = {
92 { " apple " , 5} , { " banana " , 8} , { " cherry " , 3} , { " date " , 12} ,
93 { " grape " , 10} , { " lemon " , 7} , { " orange " , 9} , { " pear " , 4} ,
94 { " fig " , 6} , { " kiwi " , 11}
95 };
96
100 std :: cout << " Perfect Hash Table Contents : " << std :: endl ;
101 pht . print () ;
102
105 std :: cout << " \ nFound date with value " << value << std :: endl ;
106 } else {
107 std :: cout << " \ nCould not find date " << std :: endl ;
108 }
109
116 return 0;
117 }
Performance analysis:
1) Time Complexity
• Build: O(n) expected time, where n is the number of elements. While we need to try multiple
hash functions until finding a collision-free one, the expected number of trials is constant.
• Search: O(1) worst-case time, since we make exactly two hash function evaluations and array
accesses.
2) Space Complexity
• O(n) expected space overall. Although each secondary table is sized quadratically to the
number of elements it contains, the total space across all secondary tables is O(n) in expec-
tation when using a good primary hash function.
Advantages Disadvantages
Guaranteed O(1) worst-case lookup time Not suitable for dynamic datasets (requires
rebuilding when data changes)
No need to handle collisions during lookup Higher space overhead compared to some
other hashing schemes
Good for static datasets that are queried fre- Complex implementation compared to sim-
quently pler hashing methods
Regulations
Please follow these regulations:
• After completing assignment, check your submission before and after uploading to Moodle.
• You can use <vector> or any libraries that are not in the prohibited libraries listed above.
Your source code must be contributed in the form of a compressed file and named your sub-
mission according to the format StudentID.zip. Here is a detail of the directory organization:
StudentID
Exercise 1.cpp
Exercise 2.cpp
Exercise 3.cpp
Exercise 4.cpp
Exercise 5.cpp
Exercise 6.cpp
Exercise 7.cpp
Exercise 8.cpp
Exercise 9.cpp
Exercise 10.cpp
The end.