Skip to lab content

WEEK 9 · ADVANCED DATA STRUCTURES LAB

Binomial min heap — insert, meld and extract

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

THE SYLLABUS TASK

What you need to solve

Implement the Binomial Heap data structure and analyze its operations.
Input format & conventions

Two heaps use IDs 1 and 2. Commands: 1 id key = insert; 2 id = extract minimum; 3 id = minimum; 4 id = structure; 5 destination source = meld and empty source; 0 = exit. At most 100 total nodes, with duplicate and signed keys allowed.

UNDERSTAND THE IDEA

Explanation

A binomial heap is a degree-ordered forest of min-heap-ordered binomial trees. A degree-k tree has 2^k nodes, and a consolidated heap has at most one root of each degree.

Melding first merges the degree-sorted root lists, then links equal-degree trees under the smaller root. Linking is analogous to carrying a bit during binary addition.

Extraction locates the smallest root, removes it, reverses its child list into increasing-degree order and melds those children back. Structure display prints every node, not just the roots.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Merge root lists in nondecreasing degree order.
  2. Consolidate equal degrees, handling a run of three equal-degree roots without breaking degree order.
  3. Insert by melding a new degree-zero singleton.
  4. Find the minimum among roots; extraction removes that root and reverses its children.
  5. Meld child roots with remaining roots, clear the source heap after explicit meld, and free nodes on exit.

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.

Meld and degree consolidation

Meld and degree consolidation

Open full-size flowchart ↗

Extract minimum from binomial roots

Extract minimum from binomial roots

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 MAX_N 100
typedef struct Node { int key, degree; struct Node *parent, *child, *sibling; } Node;
/* Merge two degree-sorted root lists; consolidation is a separate step. */
Node *mergeRoots(Node *a, Node *b) {
    Node *head = NULL, **tail = &head;
    while (a && b) {
        if (a->degree <= b->degree) { *tail = a; a = a->sibling; }
        else { *tail = b; b = b->sibling; }
        tail = &(*tail)->sibling;
    }
    *tail = a ? a : b; return head;
}
void linkTree(Node *child, Node *parent) {
    child->parent = parent; child->sibling = parent->child; parent->child = child; ++parent->degree;
}
Node *meld(Node *a, Node *b) {
    Node *head = mergeRoots(a,b); if (!head) return NULL;
    Node *previous = NULL, *current = head, *next = current->sibling;
    while (next) {
        if (current->degree != next->degree ||
            (next->sibling && next->sibling->degree == current->degree)) {
            previous = current; current = next;
        } else if (current->key <= next->key) {
            current->sibling = next->sibling; linkTree(next,current);
        } else {
            if (previous) previous->sibling = next; else head = next;
            linkTree(current,next); current = next;
        }
        next = current->sibling;
    }
    return head;
}
int insert(Node **heap, int key) {
    Node *p = malloc(sizeof *p); if (!p) return 0;
    p->key = key; p->degree = 0; p->parent = p->child = p->sibling = NULL;
    *heap = meld(*heap,p); return 1;
}
Node *minimum(Node *heap) {
    Node *best = heap;
    for (Node *p = heap; p; p = p->sibling) if (p->key < best->key) best = p;
    return best;
}
int extractMin(Node **heap, int *value) {
    if (!*heap) return 0;
    Node *best = minimum(*heap), *previous = NULL, *p = *heap;
    while (p != best) { previous = p; p = p->sibling; }
    if (previous) previous->sibling = best->sibling; else *heap = best->sibling;
    Node *children = NULL; p = best->child;
    while (p) {
        Node *next = p->sibling; p->parent = NULL; p->sibling = children; children = p; p = next;
    }
    *value = best->key; free(best); *heap = meld(*heap,children); return 1;
}
void display(const Node *p, int depth) {
    while (p) {
        printf("Depth %d: key=%d degree=%d\n",depth,p->key,p->degree);
        display(p->child,depth + 1); p = p->sibling;
    }
}
void destroy(Node *p) {
    while (p) { Node *next = p->sibling; destroy(p->child); free(p); p = next; }
}
/* Two heaps, IDs 1 and 2. Commands:
   1 id key: insert, 2 id: extract min, 3 id: minimum, 4 id: structure,
   5 destination source: meld and empty source, 0: exit. Total capacity 100. */
int main(void) {
    Node *heaps[2] = {NULL,NULL}; int counts[2] = {0,0}, command, id, key, status = 1;
    while (readInt(&command)) {
        if (!command) { status = 0; break; }
        if (!readInt(&id) || id < 1 || id > 2) break;
        --id;
        if (command == 1) {
            if (!readInt(&key)) break;
            if (counts[0] + counts[1] == MAX_N) puts("Heaps are full.");
            else if (insert(&heaps[id],key)) { ++counts[id]; puts("Inserted."); }
            else puts("Allocation failed; heap unchanged.");
        } else if (command == 2) {
            if (extractMin(&heaps[id],&key)) { --counts[id]; printf("Extracted: %d\n",key); }
            else puts("Empty heap.");
        } else if (command == 3) {
            Node *p = minimum(heaps[id]); if (p) printf("Minimum: %d\n",p->key); else puts("Empty heap.");
        } else if (command == 4) {
            if (heaps[id]) display(heaps[id],0); else puts("Empty heap.");
        } else if (command == 5) {
            int source; if (!readInt(&source) || source < 1 || source > 2 || source - 1 == id) break;
            --source; heaps[id] = meld(heaps[id],heaps[source]); heaps[source] = NULL;
            counts[id] += counts[source]; counts[source] = 0; puts("Meld complete; source empty.");
        } else break;
    }
    destroy(heaps[0]); destroy(heaps[1]); return status ? invalid() : 0;
}

Local compile: gcc -std=c17 binomial-heap.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–31Represent a forest of min-ordered binomial trees
#define MAX_N 100
typedef struct Node { int key, degree; struct Node *parent, *child, *sibling; } Node;

Each node stores key and degree, plus parent, first-child and sibling links. The driver limits the two heaps together to 100 keys. This is a min-heap example, unlike the separate max-oriented binary heap.

Lines 32–34Prepare to merge root lists sorted by degree
/* Merge two degree-sorted root lists; consolidation is a separate step. */
Node *mergeRoots(Node *a, Node *b) {
    Node *head = NULL, **tail = &head;

head starts empty and tail points to the pointer slot where the next root should be linked. A pointer-to-pointer allows the same assignment to fill either head or a later sibling link.

Lines 35–41Merge degree order without yet combining equal degrees
    while (a && b) {
        if (a->degree <= b->degree) { *tail = a; a = a->sibling; }
        else { *tail = b; b = b->sibling; }
        tail = &(*tail)->sibling;
    }
    *tail = a ? a : b; return head;
}

Choose the root with the smaller degree, advance that list, then move tail to the selected root’s sibling slot. Attach the remaining list when one runs out. Equal-degree consolidation is deliberately a separate step.

Lines 42–44Link one tree below another root
void linkTree(Node *child, Node *parent) {
    child->parent = parent; child->sibling = parent->child; parent->child = child; ++parent->degree;
}

Make child identify its new parent, insert it at the front of the parent’s child list, and increment the parent’s degree. The caller chooses the smaller-key root as parent to preserve min-heap order.

Lines 45–47Start melding with a merged degree-sorted root list
Node *meld(Node *a, Node *b) {
    Node *head = mergeRoots(a,b); if (!head) return NULL;
    Node *previous = NULL, *current = head, *next = current->sibling;

Return NULL for two empty inputs. Otherwise previous, current and next identify neighbouring roots so consolidation can repair the root-list links.

Lines 48–51Skip degrees that cannot yet be linked
    while (next) {
        if (current->degree != next->degree ||
            (next->sibling && next->sibling->degree == current->degree)) {
            previous = current; current = next;

Different degrees remain separate. When three consecutive roots have the same degree, advance first rather than linking the first pair immediately; this handles the carry-like consolidation order correctly.

Lines 52–53Keep current as parent when its key is smaller
        } else if (current->key <= next->key) {
            current->sibling = next->sibling; linkTree(next,current);

Remove next from the root list and link it under current. The merged tree now has one higher degree; later iterations may need to combine it again.

Lines 54–57Promote next when it has the smaller key
        } else {
            if (previous) previous->sibling = next; else head = next;
            linkTree(current,next); current = next;
        }

Reconnect previous, or the heap head, to next. Link current under next and make next the new current root. This preserves the root list while choosing the min-ordered parent.

Lines 58–61Continue from the current root’s new neighbour
        next = current->sibling;
    }
    return head;
}

Refresh next after every possible link. When no next root remains, return the consolidated head.

Lines 62–66Insert a one-node tree by melding
int insert(Node **heap, int key) {
    Node *p = malloc(sizeof *p); if (!p) return 0;
    p->key = key; p->degree = 0; p->parent = p->child = p->sibling = NULL;
    *heap = meld(*heap,p); return 1;
}

Allocate and initialise a degree-zero node with no links. On failure return 0 without altering the heap. On success meld it into the caller’s root list and store the returned head through Node **heap.

Lines 67–71Find the minimum among roots
Node *minimum(Node *heap) {
    Node *best = heap;
    for (Node *p = heap; p; p = p->sibling) if (p->key < best->key) best = p;
    return best;
}

The minimum of each min-ordered tree is its root, so inspect only the root sibling list. For an empty heap the loop runs zero times and returns NULL; no null best pointer is dereferenced.

Lines 72–76Find and unlink the minimum root for extraction
int extractMin(Node **heap, int *value) {
    if (!*heap) return 0;
    Node *best = minimum(*heap), *previous = NULL, *p = *heap;
    while (p != best) { previous = p; p = p->sibling; }
    if (previous) previous->sibling = best->sibling; else *heap = best->sibling;

Reject an empty heap. Locate the best root and its predecessor, then bypass it in the root list or update the heap head when it was first.

Lines 77–80Reverse the removed root’s children into a root list
    Node *children = NULL; p = best->child;
    while (p) {
        Node *next = p->sibling; p->parent = NULL; p->sibling = children; children = p; p = next;
    }

Detach each child from its old parent, clear its parent link and prepend it to children. The reversal changes descending child-degree order into the ascending root-degree order required by meld.

Lines 81–82Save the result, free the root and meld its children back
    *value = best->key; free(best); *heap = meld(*heap,children); return 1;
}

Copy best->key before free. Meld the remaining roots with the reversed child roots and retain the new head. Return success after the output value is assigned.

Lines 83–88Display a forest without changing it
void display(const Node *p, int depth) {
    while (p) {
        printf("Depth %d: key=%d degree=%d\n",depth,p->key,p->degree);
        display(p->child,depth + 1); p = p->sibling;
    }
}

Print each node’s key and degree, recurse into its children one depth lower, then advance to its sibling. Depth describes structure, not sorted key order.

Lines 89–91Release all siblings and their descendants
void destroy(Node *p) {
    while (p) { Node *next = p->sibling; destroy(p->child); free(p); p = next; }
}

Save the next sibling before recursively destroying children and freeing the current node. This prevents reading a next link from released storage.

Lines 92–96Create two independently owned heap states
/* Two heaps, IDs 1 and 2. Commands:
   1 id key: insert, 2 id: extract min, 3 id: minimum, 4 id: structure,
   5 destination source: meld and empty source, 0: exit. Total capacity 100. */
int main(void) {
    Node *heaps[2] = {NULL,NULL}; int counts[2] = {0,0}, command, id, key, status = 1;

The command comment specifies heap IDs 1 and 2 and a combined capacity. Both root lists and counts begin empty. status starts as failure until explicit command 0 is read.

Lines 97–100Read a command and translate the heap ID
    while (readInt(&command)) {
        if (!command) { status = 0; break; }
        if (!readInt(&id) || id < 1 || id > 2) break;
        --id;

Command 0 records success and leaves the loop. Other commands require ID 1 or 2; subtract one only after validation to obtain array index 0 or 1.

Lines 101–108Enforce total capacity and update counts after successful changes
        if (command == 1) {
            if (!readInt(&key)) break;
            if (counts[0] + counts[1] == MAX_N) puts("Heaps are full.");
            else if (insert(&heaps[id],key)) { ++counts[id]; puts("Inserted."); }
            else puts("Allocation failed; heap unchanged.");
        } else if (command == 2) {
            if (extractMin(&heaps[id],&key)) { --counts[id]; printf("Extracted: %d\n",key); }
            else puts("Empty heap.");

Insertion reads a key, checks the combined count and increments only on success. Extraction decrements only after extractMin returns an item. Empty or failed operations do not change the count.

Lines 109–112Inspect the minimum or display one heap
        } else if (command == 3) {
            Node *p = minimum(heaps[id]); if (p) printf("Minimum: %d\n",p->key); else puts("Empty heap.");
        } else if (command == 4) {
            if (heaps[id]) display(heaps[id],0); else puts("Empty heap.");

minimum may return NULL, so inspect p before printing its key. Structure display likewise checks whether the selected heap is empty.

Lines 113–116Transfer ownership when melding the two heaps
        } else if (command == 5) {
            int source; if (!readInt(&source) || source < 1 || source > 2 || source - 1 == id) break;
            --source; heaps[id] = meld(heaps[id],heaps[source]); heaps[source] = NULL;
            counts[id] += counts[source]; counts[source] = 0; puts("Meld complete; source empty.");

Validate a distinct source heap ID, convert it to an index, meld into the destination and set the source head to NULL. Transfer its count too. Emptying the source prevents both heap variables from owning the same nodes.

Lines 117–120Destroy both final forests on every driver exit
        } else break;
    }
    destroy(heaps[0]); destroy(heaps[1]); return status ? invalid() : 0;
}

An unsupported command or failed read leaves failure status. After the loop destroy both heap lists and return either the recorded success or the standard error.

FOLLOW THE VALUES

Dry run

  1. Insert 10 then 20: two degree-zero trees link into a degree-one tree rooted at 10.
  2. Insert 5: root list now has degrees 0 and 1.
  3. Insert 30: carry-style linking forms one degree-two tree rooted at 5.
  4. Extract 5: its children are reversed and consolidated with remaining roots; the next minimum is 10.

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 1 10
1 1 20
1 1 5
1 1 30
4 1
3 1
2 1
3 1
0
OUTPUT
Inserted.
Inserted.
Inserted.
Inserted.
Depth 0: key=5 degree=2
Depth 1: key=10 degree=1
Depth 2: key=20 degree=0
Depth 1: key=30 degree=0
Minimum: 5
Extracted: 5
Minimum: 10

Sample 2

INPUT
1 1 7
1 2 3
1 2 9
5 1 2
3 1
3 2
2 1
2 1
2 1
0
OUTPUT
Inserted.
Inserted.
Inserted.
Meld complete; source empty.
Minimum: 3
Empty heap.
Extracted: 3
Extracted: 7
Extracted: 9

Sample 3

INPUT
2 1
1 1 -2
1 1 -2
2 1
2 1
2 1
0
OUTPUT
Empty heap.
Inserted.
Inserted.
Extracted: -2
Extracted: -2
Empty heap.

Common mistakes

  • Merging sorted root lists alone does not create a valid consolidated binomial heap.
  • Reverse extracted children before passing them to a degree-sorted meld.
  • A source heap must relinquish ownership after meld to avoid freeing shared nodes twice.

WHY THIS GROWTH RATE?

Time and space complexity

Insert/meld/find-min/extract-min O(log n) for this implementation; display O(n); storage O(n).

At most one tree exists per degree, and a degree-k tree requires 2^k nodes. Therefore the root list contains O(log n) roots.

Meld traverses and consolidates those root lists. Insertion invokes meld rather than using an optimized amortized constant-time variant. Minimum scans the roots without a cached minimum pointer.

Extraction scans roots, reverses O(log n) children and melds them back. Stored nodes use O(n) space; the recursive structure display/cleanup follows tree depth O(log n).

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 a valid binomial heap have at most one root of each degree?

Equal-degree trees are linked into a tree of the next degree. Consolidation continues this carrying process until the remaining degrees are distinct.

Why does linking two degree-k trees produce degree k+1?

One root becomes a new child of the other, increasing its degree by one. The two equal-size trees combine from 2^k nodes to 2^(k+1) nodes.

Why is scanning only the roots sufficient to find the minimum?

Every tree obeys min-heap order, so its root is no larger than any descendant. The global minimum must therefore occur among the roots.

Why are the extracted root’s children reversed?

Children were prepended during linking, so they are stored in decreasing degree order. Meld expects increasing degree order; reversal restores that prerequisite.

Why does consolidation handle three consecutive equal degrees specially?

Linking the first pair immediately could place a higher-degree tree before the third equal-degree root. Skipping that pair temporarily lets the algorithm preserve the root-list order during the carry.

Why is this page’s insertion bound O(log n) rather than an amortized O(1) claim?

Every insertion calls the general meld routine, which can scan logarithmically many roots. More specialized binomial-heap insertion analyses do not automatically describe this implementation.