Skip to lab content

WEEK 12 · ADVANCED DATA STRUCTURES LAB

Collision resolution — separate chaining

B.Tech CSE & Allied Branches · II Year, I Semester · AR25C173 tested sample runs

THE SYLLABUS TASK

What you need to solve

Implement Separate Chaining and display the hash table after each insertion.
Input format & conventions

n (0–100), then n nonnegative keys. Eleven buckets, home=k%11. Each bucket is a linked list; duplicate keys are ignored. New keys are prepended, so each chain is displayed newest-first.

UNDERSTAND THE IDEA

Explanation

Separate chaining gives each table slot a list rather than only one stored value. Colliding keys become distinct nodes in that bucket.

Insertion first checks the selected chain for a duplicate, then allocates and prepends a node. Allocation failure leaves the existing chain intact.

The table can contain more keys than buckets. All chains are released after the final display or invalid input.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Choose the bucket using key % 11.
  2. Scan that bucket’s chain for an equal key.
  3. Allocate a checked node and connect it before the old head.
  4. Print every bucket and every key after the insertion attempt.
  5. Release all nodes before exiting.

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.

Insert into a selected chain

Insert into a selected chain

Open full-size flowchart ↗

C17

Complete C program

Download .c
#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
typedef struct Node { int key; struct Node *next; } Node;
Node *table[SIZE];
int insert(int key) {
    int index = key % SIZE;
    for (Node *p = table[index]; p; p = p->next) if (p->key == key) return 2;
    Node *p = malloc(sizeof *p); if (!p) return 0;
    p->key = key; p->next = table[index]; table[index] = p; return 1;
}
void display(void) {
    for (int i = 0; i < SIZE; ++i) {
        printf("%d:",i); for (Node *p = table[i]; p; p = p->next) printf(" %d",p->key);
        if (!table[i]) printf(" _");
        putchar('\n');
    }
}
void destroy(void) {
    for (int i = 0; i < SIZE; ++i) {
        Node *p = table[i]; while (p) { Node *next = p->next; free(p); p = next; }
        table[i] = NULL;
    }
}
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) { destroy(); return invalid(); }
        int result = insert(key);
        puts(result == 2 ? "Duplicate ignored." : result ? "Inserted." : "Allocation failed; table unchanged.");
        display();
    }
    destroy(); return 0;
}

Local compile: gcc -std=c17 separate-chaining.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–32Use one linked-list head per hash bucket
#define SIZE 11
typedef struct Node { int key; struct Node *next; } Node;
Node *table[SIZE];

The table has 11 bucket pointers, initially NULL in static storage. Each Node stores one key and its next node in that bucket’s chain.

Lines 33–35Choose the bucket and reject an existing key
int insert(int key) {
    int index = key % SIZE;
    for (Node *p = table[index]; p; p = p->next) if (p->key == key) return 2;

Hash by key % SIZE. Follow only that bucket’s linked list, returning status 2 if the same key is already present.

Lines 36–38Allocate and prepend a new bucket node
    Node *p = malloc(sizeof *p); if (!p) return 0;
    p->key = key; p->next = table[index]; table[index] = p; return 1;
}

Return 0 if malloc fails, leaving the existing chain untouched. Otherwise store key, link p to the old bucket head, then make p the new head and return 1. Colliding keys share a chain rather than probing other buckets.

Lines 39–45Display all bucket chains
void display(void) {
    for (int i = 0; i < SIZE; ++i) {
        printf("%d:",i); for (Node *p = table[i]; p; p = p->next) printf(" %d",p->key);
        if (!table[i]) printf(" _");
        putchar('\n');
    }
}

For each index print its label, then traverse and print its keys. An empty bucket shows an underscore. New keys appear first within their bucket because insertion prepends them.

Lines 46–51Free every chain and clear its head
void destroy(void) {
    for (int i = 0; i < SIZE; ++i) {
        Node *p = table[i]; while (p) { Node *next = p->next; free(p); p = next; }
        table[i] = NULL;
    }
}

For each bucket save the next link before freeing the current node, then continue. After a chain is released set its table entry to NULL.

Lines 52–54Read the attempt count and support an empty report
int main(void) {
    int n, key; if (!readInt(&n) || n < 0 || n > 100) return invalid();
    if (!n) display();

Require n from 0 to 100. If no insertion is requested, display the empty bucket structure once.

Lines 55–59Validate, insert and report every key
    for (int i = 0; i < n; ++i) {
        if (!readInt(&key) || key < 0) { destroy(); return invalid(); }
        int result = insert(key);
        puts(result == 2 ? "Duplicate ignored." : result ? "Inserted." : "Allocation failed; table unchanged.");
        display();

A bad key triggers destroy before returning invalid input. A valid key receives duplicate, success or allocation-failure text, followed by a complete table display.

Lines 60–62Release all allocated nodes after the final display
    }
    destroy(); return 0;
}

Destroy the chains and return success. Fixed bucket pointers do not automatically free the heap nodes they identify.

FOLLOW THE VALUES

Dry run

  1. Insert 23 into bucket 1: its chain is [23].
  2. Insert 34 into bucket 1: its chain becomes [34,23].
  3. Insert 45 into bucket 1: its chain becomes [45,34,23].
  4. Insert 34 again: it is ignored, leaving that chain unchanged.

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

INPUT
4
23 34 45 27
OUTPUT
Inserted.
0: _
1: 23
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Inserted.
0: _
1: 34 23
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Inserted.
0: _
1: 45 34 23
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Inserted.
0: _
1: 45 34 23
2: _
3: _
4: _
5: 27
6: _
7: _
8: _
9: _
10: _

Sample 2

INPUT
4
0 11 0 22
OUTPUT
Inserted.
0: 0
1: _
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Inserted.
0: 11 0
1: _
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Duplicate ignored.
0: 11 0
1: _
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _
Inserted.
0: 22 11 0
1: _
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _

Sample 3

INPUT
0
OUTPUT
0: _
1: _
2: _
3: _
4: _
5: _
6: _
7: _
8: _
9: _
10: _

Common mistakes

  • This code checks duplicates, so its insertion is not always just one constant-time pointer update.
  • A chain is not limited to one key per bucket.
  • Free every chain on normal and malformed-input exits.

WHY THIS GROWTH RATE?

Time and space complexity

Insertion/search expected O(1+α), worst O(n); each full display O(m+n); storage O(m+n).

Here α=n/m is average chain length. Under reasonably uniform hashing, the duplicate-check scan visits O(1+α) nodes; inserting a known-new node at the head alone is O(1).

If all keys hash to one bucket, that scan can examine n nodes. This implementation therefore cannot promise unconditional constant insertion.

Display visits m buckets and all n current nodes. The repeated displays add their cumulative sizes to runtime. Bucket heads and linked nodes use O(m+n) total storage.

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

6 focused questions

Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.

Why can separate chaining store more keys than table slots?

Each slot holds a list head, and the list can contain multiple nodes. Bucket count limits initial locations rather than the total number of stored keys.

Why are newly inserted colliding keys printed first in their chain?

Insertion prepends each new node to the existing head. That constant-time link update makes the displayed order newest-first within a bucket.

What is the chaining load factor α?

It is the number of stored keys divided by the number of buckets. It estimates average chain length and can exceed one without making the table structurally full.

Why does duplicate prevention change chaining insertion cost?

Before prepending, the program scans the selected chain to ensure the key is absent. That scan depends on chain length even though the final link update is constant time.

What remains unchanged if allocating a chain node fails?

The old head and all existing links remain untouched. The program reports failure and displays the original table rather than losing any stored key.

Why is displaying a chained table O(m+n) rather than only O(m)?

The display visits every bucket and also walks every node in its chains. Empty buckets contribute m work, while stored keys contribute n additional work.