THE SYLLABUS TASK
What you need to solve
Implement Double Hashing and display the hash table after each insertion.
n (0–100), then n nonnegative keys. Prime size 11; h1=k%11, h2=1+k%10; probe=(h1+i×h2)%11. Duplicates are ignored.
UNDERSTAND THE IDEA
Explanation
Double hashing lets the key choose both a starting slot and a step. Keys that share one initial index may therefore follow different paths.
The step lies from 1 through 10. Since the table size 11 is prime, every permitted step is relatively prime to it, so the sequence can reach all slots.
The program reports a bounded failure only after 11 attempts. Empty slots are tracked separately from stored data.
TURN THE IDEA INTO STEPS
Step-by-step algorithm
- Compute h1 and the nonzero h2.
- For attempts i=0…10, compute (h1+i×h2)%11.
- Check for an existing equal key.
- Store in the first empty probed slot.
- If no slot is free, report failure and show the table.
SEE THE CONTROL FLOW
Flowcharts
Diamonds show decisions; labeled arrows select a branch. Returning arrows repeat work. Helper-function steps group the operations explained above and in the code. These charts focus on the named core operation. On a phone, scroll within the diagram or open the full-size version.
Bounded probing: h1+i*h2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
int ch; size_t used = 0; int fits = 1;
while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
if (ch == EOF) return 0;
do {
if (ch == 0) fits = 0;
if (used + 1 < capacity) text[used++] = (char)ch;
else fits = 0;
ch = getchar();
} while (ch != EOF && !isspace((unsigned char)ch));
text[used] = '\0';
return fits;
}
int readInt(int *value) {
char token[64], *end;
if (!readToken(token, sizeof token)) return 0;
errno = 0; long result = strtol(token, &end, 10);
if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;
*value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }
#define SIZE 11
int table[SIZE], occupied[SIZE];
int insert(int key) {
int base = key % SIZE;
int step = 1 + key % (SIZE - 1);
for (int i = 0; i < SIZE; ++i) {
int index = (base + i * step) % SIZE;
if (occupied[index] && table[index] == key) return 2;
if (!occupied[index]) { occupied[index] = 1; table[index] = key; return 1; }
}
return 0;
}
void display(void) {
printf("Table:");
for (int i = 0; i < SIZE; ++i) {
if (occupied[i]) printf(" %d:%d",i,table[i]); else printf(" %d:_",i);
}
putchar('\n');
}
/* n (0..100), then n nonnegative keys. Display after every insertion attempt. */
int main(void) {
int n, key; if (!readInt(&n) || n < 0 || n > 100) return invalid();
if (!n) display();
for (int i = 0; i < n; ++i) {
if (!readInt(&key) || key < 0) return invalid();
int result = insert(key);
if (result == 2) puts("Duplicate ignored.");
else if (!result) puts("Insertion failed: probe sequence has no free slot.");
else puts("Inserted.");
display();
}
return 0;
}
Local compile: gcc -std=c17 double-hashing.c -o program -lm
Open in C compiler transfers the source. Copy the sample input into the compiler’s input box separately before running.
UNDERSTAND THE CODE
Line-by-line explanation
Follow the exact source above, from input handling through the algorithm and its result. Line numbers count blank lines; related statements are grouped into steps. Start with main to follow execution, then open the functions it calls.
Start with main → · Algorithm and data structure →
Lines 1–5Include input, conversion and validation declarations
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>stdio.h supplies terminal input/output. stdlib.h declares conversion and allocation functions. string.h supplies string and memory utilities. ctype.h supplies isspace, and errno.h supplies conversion error reporting. Headers declare facilities; including them does not execute an operation.
Lines 7–9Define a complete-token reader and initialise its state
/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
int ch; size_t used = 0; int fits = 1;readToken receives a writable buffer and its capacity. ch is int so it can represent EOF as well as input characters. used counts stored characters, and fits starts as a success flag. One slot must remain for the null terminator.
Lines 10–11Skip leading whitespace and recognise end-of-file
while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
if (ch == EOF) return 0;Read until a non-whitespace character appears. The unsigned-char cast supplies an allowed value to isspace. Return 0 if EOF arrives before a token begins.
Lines 12–15Collect a token without silently truncating it
do {
if (ch == 0) fits = 0;
if (used + 1 < capacity) text[used++] = (char)ch;
else fits = 0;The do body handles the character already read. An embedded zero byte marks the token invalid. Store a character only while there is room for it and a null terminator; otherwise clear fits. Continue reading even after the buffer fills.
Lines 16–20Consume the full token and terminate the buffer
ch = getchar();
} while (ch != EOF && !isspace((unsigned char)ch));
text[used] = '\0';
return fits;
}Read through the next whitespace or EOF, add a null character after the stored prefix, then return fits. A too-long token returns 0 rather than pretending that its stored prefix is the complete token.
Lines 21–23Wrap token input as a checked integer read
int readInt(int *value) {
char token[64], *end;
if (!readToken(token, sizeof token)) return 0;value is an output address. A 64-slot local buffer receives the token; a failed token read returns 0 before attempting conversion.
Lines 24–25Convert base-ten text and check the whole result
errno = 0; long result = strtol(token, &end, 10);
if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;Reset errno before strtol. &end receives the stopping address. Reject conversion errors, no converted digits, leftover characters, or values outside -1,000,000 to 1,000,000. Thus 12x is not accepted as the integer 12.
Lines 26–28Return the accepted value or a standard error status
*value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }Store a validated integer through *value and return 1 from readInt. invalid is a separate helper that prints the error and returns 1 as main’s failure status; the same number has different roles in these two function contracts.
Lines 30–31Keep occupancy separate from stored integer values
#define SIZE 11
int table[SIZE], occupied[SIZE];The table has 11 slots. Global occupied flags start at zero, so key 0 is a valid stored key rather than an empty-slot sentinel.
Lines 32–33Start insertion at the primary hash index
int insert(int key) {
int base = key % SIZE;base = key % SIZE gives an index from 0 through 10 for the nonnegative input keys. The helper returns 2 for duplicate, 1 for insertion and 0 when no probed slot is available.
Line 34Choose a nonzero second-hash step
int step = 1 + key % (SIZE - 1);step lies from 1 through 10. Because SIZE is the prime 11, every such step is relatively prime to the size, so this probe sequence can reach every slot.
Lines 35–36Compute at most eleven candidate positions
for (int i = 0; i < SIZE; ++i) {
int index = (base + i * step) % SIZE;(base + i*step) % SIZE uses the key-specific second-hash stride rather than a constant step of one. Bounding the loop by SIZE guarantees termination.
Lines 37–38Detect a duplicate or fill an empty slot
if (occupied[index] && table[index] == key) return 2;
if (!occupied[index]) { occupied[index] = 1; table[index] = key; return 1; }If the occupied slot already stores key, return 2. If it is empty, set occupied, store key and return 1. No deletion operation is provided, so there are no tombstones to handle.
Lines 39–41Report that this probe sequence found no free position
}
return 0;
}After all attempts, return 0. The full-cycle probe rule here has examined every slot.
Lines 42–48Display every physical slot and its occupancy
void display(void) {
printf("Table:");
for (int i = 0; i < SIZE; ++i) {
if (occupied[i]) printf(" %d:%d",i,table[i]); else printf(" %d:_",i);
}
putchar('\n');
}Print index:key for occupied entries and index:_ for empty ones, then a newline. The separate occupancy array makes zero keys display correctly.
Lines 49–52Read an insertion count and display an initially empty table if needed
/* n (0..100), then n nonnegative keys. Display after every insertion attempt. */
int main(void) {
int n, key; if (!readInt(&n) || n < 0 || n > 100) return invalid();
if (!n) display();Accept n from 0 to 100. For n = 0, display the empty table even though the insertion loop will have no iterations.
Lines 53–55Read one nonnegative key and attempt insertion
for (int i = 0; i < n; ++i) {
if (!readInt(&key) || key < 0) return invalid();
int result = insert(key);Validate the key before computing its remainder. Save the helper’s status so the next branch can distinguish a duplicate from probe failure.
Lines 56–60Report the attempt and display the resulting table
if (result == 2) puts("Duplicate ignored.");
else if (!result) puts("Insertion failed: probe sequence has no free slot.");
else puts("Inserted.");
display();
}Print Duplicate ignored, the probe-failure message, or Inserted according to the result. Always show the table after this attempt, even if the key was not added.
Lines 61–62Finish after all requested attempts
return 0;
}Return success after processing n valid key tokens. An insertion failure is reported as an operation result rather than a malformed-input failure.
FOLLOW THE VALUES
Dry run
- For 23, h1=1 and h2=4, so it occupies 1.
- For 34, h1=1 and h2=5; after collision it occupies 6.
- For 45, h1=1 and h2=6; after collision it occupies 7.
- The same home index therefore produces different probe paths.
CHECK THE BEHAVIOUR
Sample input & output
These outputs were captured by running the exact C source shown above. Commands use the stated input format; the programs omit repeated menu prompts so the same sample can be pasted and run.
Sample 1
4
23 34 45 27
Inserted.
Table: 0:_ 1:23 2:_ 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:_ 1:23 2:_ 3:_ 4:_ 5:_ 6:34 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:_ 1:23 2:_ 3:_ 4:_ 5:_ 6:34 7:45 8:_ 9:_ 10:_
Inserted.
Table: 0:_ 1:23 2:_ 3:_ 4:_ 5:27 6:34 7:45 8:_ 9:_ 10:_
Sample 2
4
0 11 0 22
Inserted.
Table: 0:0 1:_ 2:_ 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Duplicate ignored.
Table: 0:0 1:_ 2:11 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Sample 3
12
0 11 22 33 44 55 66 77 88 99 110 121
Inserted.
Table: 0:0 1:_ 2:_ 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:_ 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:_ 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:_ 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:_ 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:55 7:_ 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:55 7:66 8:_ 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:55 7:66 8:77 9:_ 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:55 7:66 8:77 9:88 10:_
Inserted.
Table: 0:0 1:_ 2:11 3:22 4:33 5:44 6:55 7:66 8:77 9:88 10:99
Inserted.
Table: 0:0 1:110 2:11 3:22 4:33 5:44 6:55 7:66 8:77 9:88 10:99
Insertion failed: probe sequence has no free slot.
Table: 0:0 1:110 2:11 3:22 4:33 5:44 6:55 7:66 8:77 9:88 10:99
Common mistakes
- The second hash must never produce zero.
- A step sharing a common divisor with the table size may visit only part of the table.
- Do not reuse the primary hash as a zero-permitting step.
WHY THIS GROWTH RATE?
Time and space complexity
Worst insertion O(m); expected O(1) under suitable load/hash assumptions; display O(m); table space O(m).
A step relatively prime to m cycles through all m indexes before repeating. Thus at most m probes suffice to locate a free slot or establish exhaustion.
Expected constant insertion needs reasonable distribution and a load bounded away from one. Full-table insertion is still O(m).
Printing the full table costs O(m) per attempt, making q displayed attempts O(qm) overall. The supplied demonstration uses fixed m=11.
Big-O describes growth in the stated input quantity. The educational limits keep examples safe; complexity discussions state which bounds depend on a generalized input size. Arithmetic on the accepted native numeric types is treated as constant-time.
PREPARE FOR YOUR LAB VIVA
Related viva questions & answers
Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.
Why is h2 defined as 1 + key % 10?
It produces steps from 1 to 10, excluding zero. A zero step would repeatedly inspect only the home slot.
Why does table size 11 make every chosen step suitable?
Eleven is prime, so each step 1 through 10 is relatively prime to it. Modular stepping therefore reaches all eleven slots before repeating.
How can two keys with the same h1 avoid identical probe paths?
Their h2 values can differ. They begin at the same home but move through different indexes after a collision, reducing systematic sharing of one path.
What would happen with a size-10 table and step 2?
The sequence would visit only five indexes of one parity before repeating. It could fail despite empty slots in the other parity group.
Does double hashing prevent collisions entirely?
No. Both initial positions and later probes can still collide. It changes the traversal path used to find a free slot, rather than making keys map uniquely.
Why can a failed insertion here be associated with a full table?
The nonzero step is relatively prime to the table size, so all slots are examined. If none is empty and no duplicate matches, the table has no available slot.