Skip to lab content

WEEK 10 · ADVANCED DATA STRUCTURES LAB

Binary trie — eight-bit integer keys

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

THE SYLLABUS TASK

What you need to solve

Implement a Binary Trie.
Input format & conventions

Commands: 1 value = insert; 2 value = exact search; 3 = sorted-value display; 0 = exit. Values 0–255 use eight bits; at most 100 distinct values.

UNDERSTAND THE IDEA

Explanation

A binary trie branches on bits rather than comparing whole keys. Child zero follows a zero bit and child one follows a one bit.

All values use the same eight-bit width, read from most significant bit to least significant bit. Leading zero bits are part of each path, so 5 is stored as 00000101.

Search succeeds only after following all eight bits to a terminal key. The fixed node pool can hold one root plus eight new nodes for each accepted distinct value.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Validate the value range and distinct-key capacity.
  2. Read bit positions 7 through 0.
  3. Create a missing child for each bit during insertion.
  4. Mark the final node terminal; exact search requires the complete path and terminal marker.
  5. Visit zero branches before one branches to display values in increasing numeric order.

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.

Binary trie insertion

Binary trie insertion

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 BITS 8
#define MAX_VALUES 100
#define MAX_NODES (1 + MAX_VALUES * BITS)
typedef struct Node { struct Node *child[2]; int terminal; } Node;
Node pool[MAX_NODES]; int used = 1, count = 0;
Node *root = &pool[0];
int search(int value) {
    Node *p = root;
    for (int bit = BITS - 1; bit >= 0; --bit) {
        int direction = (value >> bit) & 1;
        if (!p->child[direction]) return 0;
        p = p->child[direction];
    }
    return p->terminal;
}
void insert(int value) {
    Node *p = root;
    for (int bit = BITS - 1; bit >= 0; --bit) {
        int direction = (value >> bit) & 1;
        if (!p->child[direction]) p->child[direction] = &pool[used++];
        p = p->child[direction];
    }
    p->terminal = 1; ++count;
}
void display(const Node *p, int depth, int value) {
    if (!p) return;
    if (depth == BITS) { if (p->terminal) printf(" %d",value); return; }
    display(p->child[0],depth + 1,value * 2);
    display(p->child[1],depth + 1,value * 2 + 1);
}
/* 1 value: insert, 2 value: exact search, 3: sorted values, 0: exit.
   Values 0..255 use an explicit eight-bit width. At most 100 distinct values. */
int main(void) {
    int command, value;
    while (readInt(&command)) {
        if (!command) return 0;
        if (command == 3) { printf("Values:"); display(root,0,0); putchar('\n'); continue; }
        if ((command != 1 && command != 2) || !readInt(&value) || value < 0 || value > 255) return invalid();
        if (command == 2) puts(search(value) ? "Found." : "Not found.");
        else if (search(value)) puts("Duplicate ignored.");
        else if (count == MAX_VALUES) puts("Trie is full.");
        else { insert(value); puts("Inserted."); }
    }
    return invalid();
}

Local compile: gcc -std=c17 binary-trie.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–35Reserve an eight-level binary trie
#define BITS 8
#define MAX_VALUES 100
#define MAX_NODES (1 + MAX_VALUES * BITS)
typedef struct Node { struct Node *child[2]; int terminal; } Node;
Node pool[MAX_NODES]; int used = 1, count = 0;
Node *root = &pool[0];

Each node has child[0], child[1] and a terminal flag. A zero-initialised static pool provides root plus enough nodes for the capped number of eight-bit values. root is pool[0], used begins at 1, and count starts at zero.

Lines 36–39Choose each path link from the next bit
int search(int value) {
    Node *p = root;
    for (int bit = BITS - 1; bit >= 0; --bit) {
        int direction = (value >> bit) & 1;

Start at root and visit bit positions 7 down to 0. (value >> bit) & 1 selects child 0 or 1, preserving leading zero bits in the fixed-width representation.

Lines 40–44Fail on a missing link or confirm the terminal flag
        if (!p->child[direction]) return 0;
        p = p->child[direction];
    }
    return p->terminal;
}

If a selected child does not exist, the value is absent. Otherwise follow it. After all eight links, return terminal so the complete stored value, not just a partial path, determines success.

Lines 45–50Create missing path nodes during insertion
void insert(int value) {
    Node *p = root;
    for (int bit = BITS - 1; bit >= 0; --bit) {
        int direction = (value >> bit) & 1;
        if (!p->child[direction]) p->child[direction] = &pool[used++];
        p = p->child[direction];

Again scan the eight bits. If a required child is absent, take the next unused zero-initialised pool node. Existing prefixes are reused rather than duplicated.

Lines 51–53Mark a complete value and increase the stored count
    }
    p->terminal = 1; ++count;
}

After the path loop, set terminal and increment count. The driver checks duplicates and capacity before calling this helper, so it counts only a new distinct value.

Lines 54–59Reconstruct values by traversing zero links before one links
void display(const Node *p, int depth, int value) {
    if (!p) return;
    if (depth == BITS) { if (p->terminal) printf(" %d",value); return; }
    display(p->child[0],depth + 1,value * 2);
    display(p->child[1],depth + 1,value * 2 + 1);
}

At depth eight, print only terminal values. Otherwise append a zero bit using value * 2 or a one bit using value * 2 + 1. Zero-first traversal produces ascending numeric order.

Lines 60–66Read commands and display without a value operand
/* 1 value: insert, 2 value: exact search, 3: sorted values, 0: exit.
   Values 0..255 use an explicit eight-bit width. At most 100 distinct values. */
int main(void) {
    int command, value;
    while (readInt(&command)) {
        if (!command) return 0;
        if (command == 3) { printf("Values:"); display(root,0,0); putchar('\n'); continue; }

The interface accepts only 0–255 values and at most 100 distinct entries. Exit returns success. Display starts at depth zero and value zero, then continues to the next command.

Lines 67–71Validate values, distinguish search from insertion and enforce limits
        if ((command != 1 && command != 2) || !readInt(&value) || value < 0 || value > 255) return invalid();
        if (command == 2) puts(search(value) ? "Found." : "Not found.");
        else if (search(value)) puts("Duplicate ignored.");
        else if (count == MAX_VALUES) puts("Trie is full.");
        else { insert(value); puts("Inserted."); }

Reject unsupported commands or values outside the eight-bit range. Searches do not change the trie. Insertions ignore an existing terminal value and reject the capacity limit before creating a path.

Lines 72–74Require explicit normal exit
    }
    return invalid();
}

If input ends without command 0, report incomplete input. The static pool needs no per-node free operation.

FOLLOW THE VALUES

Dry run

  1. Insert 5: create path 0,0,0,0,0,1,0,1.
  2. Insert 10: its path shares the initial four zero branches before diverging.
  3. Search 5 follows all eight bits and finds a terminal.
  4. Search 6 diverges at its suffix and returns Not found.

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
1 5
1 10
2 5
2 6
3
0
OUTPUT
Inserted.
Inserted.
Found.
Not found.
Values: 5 10

Sample 2

INPUT
1 0
1 255
1 0
3
2 255
0
OUTPUT
Inserted.
Inserted.
Duplicate ignored.
Values: 0 255
Found.

Sample 3

INPUT
2 1
3
0
OUTPUT
Not found.
Values:

Common mistakes

  • Do not shift an ordinary narrow signed int by 31 for a portable teaching example.
  • Use one fixed key width so leading zeros are handled consistently.
  • Binary-trie branches are not BST less-than/greater-than branches.

WHY THIS GROWTH RATE?

Time and space complexity

Insert/search O(w), with w=8 here; storage O(kw) for k keys; display O(number of allocated nodes).

Each insert or search examines exactly w bit positions unless a missing branch stops a search early. With this fixed eight-bit width, those operations are O(1) for the accepted domain.

A new value can allocate at most w nodes, so k values require no more than 1+kw nodes. Shared prefixes reduce the actual usage.

Display traverses the allocated prefix tree. In a general wider-key implementation the width remains an explicit factor; it should not be confused with the number of stored keys.

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 are leading zero bits retained in this binary trie?

Every key must follow an eight-level path. Retaining them gives all accepted values the same representation and avoids ambiguous variable-length integer encodings.

What chooses child zero or child one during insertion?

The extracted bit (value >> bit) & 1 supplies the branch index. The choice depends on that individual bit, not on comparing the whole value with a node key.

Can two different values share trie nodes?

Yes. Equal leading bits share a prefix path until their first differing bit. This reduces allocated nodes without merging the distinct terminal keys.

Why does traversing zero before one produce numeric order here?

All representations have equal width and are visited from most significant bit downward. At the first differing bit, a zero branch contains smaller values than the one branch.

Why does the node pool reserve one plus 100 times eight nodes?

There is one root, and each of 100 accepted distinct keys could add at most eight nodes. That upper bound is sufficient even when sharing is minimal.

How does this implementation improve on the manual’s binary-trie demonstration?

It supplies exact lookup and sorted-value display, so stored paths can be observed and tested. The manual only inserted two numbers and printed a confirmation.