6-2 二项式队列

二项式队列(Binomial Queue / Binomial Heap)

二项式队列(Binomial Queue),又称二项堆(Binomial Heap),是一种可合并堆(Mergeable Heap)数据结构,由一组二项树(Binomial Tree)组成。它是二叉堆(Binary Heap)的一种推广,最大的优势在于支持高效的合并操作。

二项树 Bk 是一种递归定义的树:

  • B0 是只有一个节点的树
  • Bk 由两棵 B(k-1) 连接而成:将其中一棵 B(k-1) 的根作为另一棵 B(k-1) 根的左子节点

每棵二项树 Bk 恰好有 2^k 个节点。一个包含 n 个元素的二项式队列最多有 log2(n)+1 棵二项树——对应 n 的二进制表示中每一位 1。例如,n=13 的二进制为 1101,所以二项式队列包含 B0、B2、B3 三棵树(1+4+8=13 个节点)。

二项式队列在实际中的应用包括:

  • 可合并优先队列(Mergeable Priority Queue)
  • 图算法中的高效优先队列
  • 不需要预先知道总元素数量的场景

二项树的结构

下面展示 B0 到 B3 四棵二项树的形态:

B0 (2^0 = 1 节点):

    *

B1 (2^1 = 2 节点):

    *
    |
    *

B2 (2^2 = 4 节点):

        *
       / \
      *   *
      |
      *

B3 (2^3 = 8 节点):

            *
          / | \
         *  *  *
        / \
       *   *
       |
       *

可以看到,Bk 的根节点恰好有 k 个子节点,从左到右分别是 B(k-1)、B(k-2)、...、B0。

二进制表示与二项式队列的关系:以 n=13 为例,13 的二进制为 1101。

n = 13 = 1101 (二进制)

位:    3   2   1   0
值:    1   1   0   1

对应:  B3  B2  --  B0
节点:  8 + 4 + 0 + 1 = 13

二项式队列包含: B0, B2, B3
                =  *     *       *
                       / \    / | \
                       *  *  *  *  *
                       |    / \
                       *   *   *
                           |
                           *

这就是二项式队列的精妙之处:用二进制来管理一组二项树,合并操作就相当于二进制加法。


二项树的性质

二项树具有以下重要性质:

  1. 节点数量:Bk 恰好有 2^k 个节点。这可以通过递归定义直接推出:两棵 B(k-1) 合并 = 2 * 2^(k-1) = 2^k。
  2. 树的高度:Bk 的高度为 k。每合并一次高度增加 1。
  3. 根的子节点数:Bk 的根节点恰好有 k 个子节点,从左到右分别是 B(k-1)、B(k-2)、...、B0。
  4. 深度 d 处的节点数:Bk 在深度 d 处恰好有 C(k, d) 个节点(二项式系数),这正是"二项树"名称的由来。
  5. 合并复杂度:两棵同阶的 Bk 可以在 O(1) 时间内合并成一棵 B(k+1)——只需将一棵树的根作为另一棵树根的子节点即可。
两棵 B2 合并成一棵 B3:

  B2        B2           B3
   *         *            *
  / \       / \         / | \
 *   *  +  *   *  =>   *  *  *
 |         |           / \
 *         *          *   *
                       |
                       *

一棵 B2 的根成为另一棵 B2 根的最左子节点,
形成一棵 B3(8 个节点)。

性质 5 是二项式队列高效合并的基础:合并两棵同阶二项树只需 O(1),整个队列的合并类似二进制加法,总共 O(log n)。


节点定义

二项式队列的节点需要维护四个指针:父节点(parent)、第一个子节点(child,即最左子节点)、右兄弟(sibling)。此外还需要记录节点的度数(degree),即子节点的个数。

C++ 节点定义

struct BinomialNode {
    int data;                       // key value stored in node
    int degree;                     // number of children
    BinomialNode* parent;           // pointer to parent
    BinomialNode* child;            // pointer to leftmost child
    BinomialNode* sibling;          // pointer to right sibling

    // constructor
    BinomialNode(int val) : data(val), degree(0),
                            parent(nullptr), child(nullptr), sibling(nullptr) {}
};

节点的子节点通过 child 指针找到最左子节点,然后通过 sibling 指针依次遍历其他子节点。degree 记录子节个数,也等于该节点所对应二项树的阶数 k。

C 节点定义

typedef struct BinomialNode {
    int data;                       // key value
    int degree;                     // number of children
    struct BinomialNode* parent;    // pointer to parent
    struct BinomialNode* child;     // pointer to leftmost child
    struct BinomialNode* sibling;   // pointer to right sibling
} BinomialNode;

// create a new node with given value
BinomialNode* createNode(int val) {
    BinomialNode* node = (BinomialNode*)malloc(sizeof(BinomialNode));
    node->data = val;
    node->degree = 0;
    node->parent = NULL;
    node->child = NULL;
    node->sibling = NULL;
    return node;
}

C 语言版本使用 typedefmalloc,结构与 C++ 版本一致。

Python 节点定义

class BinomialNode:
    def __init__(self, data):
        self.data = data            # key value
        self.degree = 0             # number of children
        self.parent = None          # pointer to parent
        self.child = None           # pointer to leftmost child
        self.sibling = None         # pointer to right sibling

Python 版本最为简洁,使用 None 表示空指针,degree 默认为 0。

Go 节点定义

package main

import "fmt"

// BinomialNode represents a node in the binomial heap
type BinomialNode struct {
	key     int            // key value
	degree  int            // number of children
	parent  *BinomialNode  // pointer to parent
	child   *BinomialNode  // pointer to leftmost child
	sibling *BinomialNode  // pointer to right sibling
}

// newBinomialNode creates a new node with the given key
func newBinomialNode(key int) *BinomialNode {
	return &BinomialNode{key: key}
}

Go 版本使用结构体和指针表示节点关系,nil 表示空指针。通过工厂函数 newBinomialNode 创建节点,degree 默认为 0,指针字段默认为 nil


合并操作(Merge / Union)

合并(Merge)是二项式队列的核心操作,其他操作都依赖于它。合并过程类似于二进制加法:

  • 将两个队列中的二项树按度数从小到大排列
  • 逐个处理每个度数,可能出现以下情况:
    • 只有来自一个队列的树:直接保留
    • 两棵同度数的树:合并(link)成一棵更高阶的树,产生"进位"
    • 三棵同度数的树(两棵来自输入 + 一棵进位):合并其中两棵,保留第三棵

合并两棵同阶二项树(Link):将根值较大的树作为根值较小的树的子节点。

步骤示例:合并两个二项式队列

队列 H1: [B1(含节点 3,7)]     队列 H2: [B0(含节点 5), B1(含节点 2,8)]

Step 1: 度数 0 — 只有 H2 的 B0
        结果: [B0(5)]

Step 2: 度数 1 — H1 的 B1 和 H2 的 B1,合并为 B2
        3 和 2 比较,2 更小成为根
        结果: [B0(5), B2(2,3,7,8)]

最终合并结果: B0(5) + B2(2,3,7,8) = 5 个节点

C++ 合并实现

class BinomialQueue {
private:
    BinomialNode* head;             // pointer to list of tree roots

    // link two trees of same degree: make larger root a child of smaller
    BinomialNode* linkTree(BinomialNode* t1, BinomialNode* t2) {
        // ensure t1 has the smaller root (min-heap property)
        if (t1->data > t2->data)
            std::swap(t1, t2);

        // t2 becomes leftmost child of t1
        t2->parent = t1;
        t2->sibling = t1->child;
        t1->child = t2;
        t1->degree++;

        return t1;
    }

    // merge two root lists by degree (like merge step of merge sort)
    BinomialNode* mergeRootLists(BinomialNode* h1, BinomialNode* h2) {
        if (h1 == nullptr) return h2;
        if (h2 == nullptr) return h1;

        BinomialNode* newHead = nullptr;
        BinomialNode** pos = &newHead;

        // merge by increasing degree
        while (h1 != nullptr && h2 != nullptr) {
            if (h1->degree <= h2->degree) {
                *pos = h1;
                h1 = h1->sibling;
            } else {
                *pos = h2;
                h2 = h2->sibling;
            }
            pos = &((*pos)->sibling);
        }
        *pos = (h1 != nullptr) ? h1 : h2;

        return newHead;
    }

public:
    BinomialQueue() : head(nullptr) {}

    // union two binomial queues
    void merge(BinomialQueue& other) {
        if (this == &other) return;

        BinomialNode* newHead = mergeRootLists(this->head, other.head);
        other.head = nullptr;

        if (newHead == nullptr) {
            head = nullptr;
            return;
        }

        BinomialNode* prev = nullptr;
        BinomialNode* curr = newHead;
        BinomialNode* next = curr->sibling;

        while (next != nullptr) {
            // case 1: different degrees — move forward
            // case 2: same degree but three in a row — move forward
            bool mergeNeeded = (curr->degree == next->degree) &&
                (next->sibling == nullptr || next->sibling->degree != curr->degree);

            if (!mergeNeeded) {
                // no merge, advance pointers
                prev = curr;
                curr = next;
                next = next->sibling;
            } else {
                // merge curr and next
                curr = linkTree(curr, next);
                curr->sibling = next->sibling;
                if (prev != nullptr)
                    prev->sibling = curr;
                else
                    newHead = curr;
                next = curr->sibling;
            }
        }

        head = newHead;
    }
};

mergeRootLists 按度数合并两个根链表(类似归并排序的合并步骤),然后遍历合并后的链表,将相邻同度数的树两两合并。整个过程模拟了二进制加法,时间复杂度为 O(log n)。

C 合并实现

typedef struct {
    BinomialNode* head;
} BinomialQueue;

// link two Bk trees into one B(k+1)
BinomialNode* linkTree(BinomialNode* t1, BinomialNode* t2) {
    if (t1->data > t2->data) {
        BinomialNode* temp = t1;
        t1 = t2;
        t2 = temp;
    }
    // t2 becomes child of t1
    t2->parent = t1;
    t2->sibling = t1->child;
    t1->child = t2;
    t1->degree++;
    return t1;
}

// merge two root lists sorted by degree
BinomialNode* mergeRootLists(BinomialNode* h1, BinomialNode* h2) {
    if (!h1) return h2;
    if (!h2) return h1;

    BinomialNode* newHead = NULL;
    BinomialNode** pos = &newHead;

    while (h1 && h2) {
        if (h1->degree <= h2->degree) {
            *pos = h1;
            h1 = h1->sibling;
        } else {
            *pos = h2;
            h2 = h2->sibling;
        }
        pos = &((*pos)->sibling);
    }
    *pos = h1 ? h1 : h2;
    return newHead;
}

// union two binomial queues, result stored in q1
void binomialMerge(BinomialQueue* q1, BinomialQueue* q2) {
    if (q1 == q2) return;

    BinomialNode* newHead = mergeRootLists(q1->head, q2->head);
    q2->head = NULL;

    if (!newHead) {
        q1->head = NULL;
        return;
    }

    BinomialNode* prev = NULL;
    BinomialNode* curr = newHead;
    BinomialNode* next = curr->sibling;

    while (next) {
        bool mergeNeeded = (curr->degree == next->degree) &&
            (!next->sibling || next->sibling->degree != curr->degree);

        if (!mergeNeeded) {
            prev = curr;
            curr = next;
            next = next->sibling;
        } else {
            curr = linkTree(curr, next);
            curr->sibling = next->sibling;
            if (prev)
                prev->sibling = curr;
            else
                newHead = curr;
            next = curr->sibling;
        }
    }
    q1->head = newHead;
}

C 语言版本使用结构体包装头指针,合并逻辑与 C++ 版本完全一致,结果存入第一个队列。

Python 合并实现

class BinomialQueue:
    def __init__(self):
        self.head = None             # list of tree roots

    def _link_tree(self, t1, t2):
        """Link two Bk trees into one B(k+1), smaller root on top."""
        if t1.data > t2.data:
            t1, t2 = t2, t1
        # t2 becomes leftmost child of t1
        t2.parent = t1
        t2.sibling = t1.child
        t1.child = t2
        t1.degree += 1
        return t1

    def _merge_root_lists(self, h1, h2):
        """Merge two root lists by ascending degree."""
        if not h1:
            return h2
        if not h2:
            return h1

        # use dummy node to simplify insertion
        dummy = BinomialNode(0)
        tail = dummy

        while h1 and h2:
            if h1.degree <= h2.degree:
                tail.sibling = h1
                h1 = h1.sibling
            else:
                tail.sibling = h2
                h2 = h2.sibling
            tail = tail.sibling

        tail.sibling = h1 if h1 else h2
        return dummy.sibling

    def merge(self, other):
        """Union this queue with another."""
        if self is other:
            return

        new_head = self._merge_root_lists(self.head, other.head)
        other.head = None

        if not new_head:
            self.head = None
            return

        prev = None
        curr = new_head
        nxt = curr.sibling

        while nxt:
            merge_needed = (curr.degree == nxt.degree) and \
                (nxt.sibling is None or nxt.sibling.degree != curr.degree)

            if not merge_needed:
                prev = curr
                curr = nxt
                nxt = nxt.sibling
            else:
                curr = self._link_tree(curr, nxt)
                curr.sibling = nxt.sibling
                if prev:
                    prev.sibling = curr
                else:
                    new_head = curr
                nxt = curr.sibling

        self.head = new_head

Python 版本使用虚拟头节点(dummy node)简化根链表的合并,逻辑清晰易读。三个版本的核心算法完全一致:先按度数归并根链表,再扫描合并同度数的树。

Go 合并实现

// BinomialHeap represents a binomial heap
type BinomialHeap struct {
	roots []*BinomialNode
}

// linkTree merges two Bk trees into one B(k+1), smaller root on top
func linkTree(t1, t2 *BinomialNode) *BinomialNode {
	if t1.key > t2.key {
		t1, t2 = t2, t1
	}
	// t2 becomes leftmost child of t1
	t2.parent = t1
	t2.sibling = t1.child
	t1.child = t2
	t1.degree++
	return t1
}

// mergeRootLists merges two root slices by ascending degree
func mergeRootLists(h1, h2 []*BinomialNode) []*BinomialNode {
	result := make([]*BinomialNode, 0, len(h1)+len(h2))
	i, j := 0, 0
	for i < len(h1) && j < len(h2) {
		if h1[i].degree <= h2[j].degree {
			result = append(result, h1[i])
			i++
		} else {
			result = append(result, h2[j])
			j++
		}
	}
	result = append(result, h1[i:]...)
	result = append(result, h2[j:]...)
	return result
}

// Merge unions this heap with another
func (h *BinomialHeap) Merge(other *BinomialHeap) {
	if h == other {
		return
	}

	merged := mergeRootLists(h.roots, other.roots)
	other.roots = nil

	if len(merged) == 0 {
		h.roots = nil
		return
	}

	// Scan and merge adjacent trees of same degree
	var newRoots []*BinomialNode
	i := 0
	for i < len(merged) {
		if i+1 < len(merged) && merged[i].degree == merged[i+1].degree {
			// Check if there's a third one of same degree
			if i+2 < len(merged) && merged[i].degree == merged[i+2].degree {
				// Three in a row: merge first two, keep third
				merged[i] = linkTree(merged[i], merged[i+1])
				// Remove merged[i+1], shift left
				merged = append(merged[:i+1], merged[i+2:]...)
			} else {
				// Two in a row: merge them
				merged[i] = linkTree(merged[i], merged[i+1])
				merged = append(merged[:i+1], merged[i+2:]...)
			}
			// Don't advance i, the merged tree might match the next one
		} else {
			newRoots = append(newRoots, merged[i])
			i++
		}
	}
	h.roots = newRoots
}

Go 版本使用切片 []*BinomialNode 存储根节点列表,合并时按度数归并排序,然后扫描合并同度数的树。由于使用切片而非链表,通过索引访问更直观。


插入操作

插入(Insert)操作非常简单:创建一个只含单个节点 B0 的二项式队列,然后将其与现有队列合并。这等价于在二进制数的最低位加 1。

插入 5 到已有队列 [B1(3,7), B2(2,8,10,12)]:

Step 1: 创建 B0(5)
Step 2: 合并 [B0(5)] 和 [B1, B2]

度数 0: B0(5) — 直接保留
度数 1: B1 — 直接保留
度数 2: B2 — 直接保留

结果: [B0(5), B1(3,7), B2(2,8,10,12)]

如果接着插入 4:
创建 B0(4), 合并时 B0(4) 和 B0(5) 合并为 B1(4,5)
新 B1 和原来的 B1(3,7) 合并为 B2(3,4,5,7)
新 B2 和原来的 B2 合并为 B3...

这就是"进位"的效果,和二进制加法一模一样。

单次插入的摊还时间复杂度为 O(1),最坏情况为 O(log n)。

C++ 插入实现

void insert(int val) {
    BinomialQueue single;
    single.head = new BinomialNode(val);
    merge(single);
}

创建一个只含一个节点的临时队列,调用 merge 完成插入,代码极其简洁。

C 插入实现

void binomialInsert(BinomialQueue* q, int val) {
    BinomialQueue single;
    single.head = createNode(val);
    binomialMerge(q, &single);
}

C 语言版本同样创建临时队列后合并。

Python 插入实现

def insert(self, val):
    """Insert a value by creating a single-node queue and merging."""
    single = BinomialQueue()
    single.head = BinomialNode(val)
    self.merge(single)

三个版本的插入操作都是同一个思路:构造 B0 再合并。

Go 插入实现

// Insert adds a value to the heap by creating a single-node heap and merging
func (h *BinomialHeap) Insert(val int) {
	single := &BinomialHeap{
		roots: []*BinomialNode{newBinomialNode(val)},
	}
	h.Merge(single)
}

Go 版本创建一个只包含单个节点的 BinomialHeap,然后调用 Merge 完成插入,逻辑简洁。


查找最小值(Find Min)

查找最小值(Find Min)需要遍历根链表中的所有根节点(最多 log2(n)+1 个),找到值最小的那个。虽然不是 O(1),但由于树的数量很少,效率仍然很高。

C++ 查找最小值实现

BinomialNode* findMin() {
    if (head == nullptr) return nullptr;

    BinomialNode* minNode = head;
    BinomialNode* curr = head->sibling;

    while (curr != nullptr) {
        if (curr->data < minNode->data)
            minNode = curr;
        curr = curr->sibling;
    }

    return minNode;
}

从根链表头部开始遍历,逐一比较所有根节点的值。由于根链表最多有 log2(n)+1 个节点,时间复杂度为 O(log n)。

C 查找最小值实现

BinomialNode* binomialFindMin(BinomialQueue* q) {
    if (!q->head) return NULL;

    BinomialNode* minNode = q->head;
    BinomialNode* curr = q->head->sibling;

    while (curr) {
        if (curr->data < minNode->data)
            minNode = curr;
        curr = curr->sibling;
    }
    return minNode;
}

Python 查找最小值实现

def find_min(self):
    """Find the node with minimum key, O(log n)."""
    if not self.head:
        return None

    min_node = self.head
    curr = self.head.sibling
    while curr:
        if curr.data < min_node.data:
            min_node = curr
        curr = curr.sibling
    return min_node

三个版本的查找最小值逻辑完全一致:线性扫描根链表,找出最小根节点。

Go 查找最小值实现

// FindMin returns the node with minimum key, O(log n)
func (h *BinomialHeap) FindMin() *BinomialNode {
	if len(h.roots) == 0 {
		return nil
	}

	minNode := h.roots[0]
	for i := 1; i < len(h.roots); i++ {
		if h.roots[i].key < minNode.key {
			minNode = h.roots[i]
		}
	}
	return minNode
}

Go 版本遍历根节点切片,逐一比较找出最小根节点。由于使用切片而非链表,通过索引遍历更加简洁。


提取最小值(Extract Min)

提取最小值(Extract Min)是最复杂的操作,分为以下几步:

  1. 在根链表中找到最小值的根节点
  2. 从根链表中移除该根节点
  3. 将该根节点的所有子节点逆序(因为子节点按度数从大到小排列,需要变为从小到大)
  4. 将逆序后的子节点链表作为一个新的二项式队列
  5. 将新队列与原队列合并
提取最小值示例:

原始队列: [B0(3), B1(5,8), B2(1,...)]

Step 1: 找到最小根 1(B2 的根)
Step 2: 从根链表移除 B2: [B0(3), B1(5,8)]
Step 3: B2 根的子节点是 B1, B0,逆序后: [B0, B1]
Step 4: 子节点形成新队列
Step 5: 合并 [B0(3), B1(5,8)] 和 [B0(...), B1(...)]

C++ 提取最小值实现

int extractMin() {
    if (head == nullptr) throw std::runtime_error("Empty queue");

    // step 1: find min and its predecessor
    BinomialNode* minNode = head;
    BinomialNode* minPrev = nullptr;
    BinomialNode* curr = head;
    BinomialNode* prev = nullptr;

    while (curr->sibling != nullptr) {
        if (curr->sibling->data < minNode->data) {
            minPrev = curr;
            minNode = curr->sibling;
        }
        prev = curr;
        curr = curr->sibling;
    }

    // also check head itself
    if (head->data <= minNode->data && minPrev == nullptr && prev != nullptr) {
        minNode = head;
        minPrev = nullptr;
    }

    // step 2: remove min node from root list
    int minVal = minNode->data;
    if (minPrev == nullptr) {
        head = minNode->sibling;
    } else {
        minPrev->sibling = minNode->sibling;
    }

    // step 3: reverse min node's children to form new queue
    BinomialNode* child = minNode->child;
    BinomialNode* revList = nullptr;
    while (child != nullptr) {
        BinomialNode* next = child->sibling;
        child->sibling = revList;
        child->parent = nullptr;
        revList = child;
        child = next;
    }

    // step 4 & 5: merge reversed children with remaining queue
    BinomialQueue childQueue;
    childQueue.head = revList;
    merge(childQueue);

    delete minNode;
    return minVal;
}

提取最小值的关键在于子节点链表的逆序操作。因为二项树根的子节点按度数从大到小排列(B(k-1), B(k-2), ..., B0),而根链表需要按度数从小到大排列,所以必须逆序后再合并。时间复杂度为 O(log n)。

C 提取最小值实现

int binomialExtractMin(BinomialQueue* q) {
    if (!q->head) return -1;  // error: empty queue

    // find min root and its predecessor
    BinomialNode *minNode = q->head, *minPrev = NULL;
    BinomialNode *curr = q->head, *prev = NULL;

    while (curr) {
        if (curr->data < minNode->data) {
            minNode = curr;
            minPrev = prev;
        }
        prev = curr;
        curr = curr->sibling;
    }

    // remove min from root list
    int minVal = minNode->data;
    if (minPrev)
        minPrev->sibling = minNode->sibling;
    else
        q->head = minNode->sibling;

    // reverse children list
    BinomialNode* child = minNode->child;
    BinomialNode* revList = NULL;
    while (child) {
        BinomialNode* next = child->sibling;
        child->sibling = revList;
        child->parent = NULL;
        revList = child;
        child = next;
    }

    free(minNode);

    // merge children back
    BinomialQueue childQ;
    childQ.head = revList;
    binomialMerge(q, &childQ);

    return minVal;
}

Python 提取最小值实现

def extract_min(self):
    """Remove and return the minimum key."""
    if not self.head:
        return None

    # find min root and its predecessor
    min_node = self.head
    min_prev = None
    prev = None
    curr = self.head

    while curr:
        if curr.data < min_node.data:
            min_node = curr
            min_prev = prev
        prev = curr
        curr = curr.sibling

    # remove min from root list
    if min_prev:
        min_prev.sibling = min_node.sibling
    else:
        self.head = min_node.sibling

    # reverse children to form new queue
    child = min_node.child
    rev_list = None
    while child:
        nxt = child.sibling
        child.sibling = rev_list
        child.parent = None
        rev_list = child
        child = nxt

    # merge children back
    child_queue = BinomialQueue()
    child_queue.head = rev_list
    self.merge(child_queue)

    return min_node.data

三个版本的提取最小值都遵循相同的步骤:找最小根、移除、逆转子节点链表、合并。

Go 提取最小值实现

// ExtractMin removes and returns the minimum key from the heap
func (h *BinomialHeap) ExtractMin() (int, bool) {
	if len(h.roots) == 0 {
		return 0, false
	}

	// Find the index of the minimum root
	minIdx := 0
	for i := 1; i < len(h.roots); i++ {
		if h.roots[i].key < h.roots[minIdx].key {
			minIdx = i
		}
	}

	minVal := h.roots[minIdx].key

	// Collect children of the min root
	child := h.roots[minIdx].child
	var children []*BinomialNode
	for child != nil {
		next := child.sibling
		child.parent = nil
		child.sibling = nil
		children = append([]*BinomialNode{child}, children...) // prepend to reverse order
		child = next
	}

	// Remove min root from slice
	h.roots = append(h.roots[:minIdx], h.roots[minIdx+1:]...)

	// Merge children back as a separate heap
	if len(children) > 0 {
		childHeap := &BinomialHeap{roots: children}
		h.Merge(childHeap)
	}

	return minVal, true
}

Go 版本通过切片索引定位并移除最小根节点。子节点收集时使用 prepend 方式插入切片头部,实现逆序效果。Go 的切片操作 append(h.roots[:minIdx], h.roots[minIdx+1:]...) 简洁地完成了元素移除。


完整实现

下面给出 C++、C、Python、Go 四种语言的完整二项式队列实现,包含插入、查找最小值、提取最小值等所有操作。

C++ 完整实现

#include <iostream>
#include <algorithm>
#include <stdexcept>

struct BinomialNode {
    int data;
    int degree;
    BinomialNode* parent;
    BinomialNode* child;
    BinomialNode* sibling;

    BinomialNode(int val) : data(val), degree(0),
                            parent(nullptr), child(nullptr), sibling(nullptr) {}
};

class BinomialQueue {
private:
    BinomialNode* head;

    // link two Bk trees into one B(k+1)
    BinomialNode* linkTree(BinomialNode* t1, BinomialNode* t2) {
        if (t1->data > t2->data) std::swap(t1, t2);
        t2->parent = t1;
        t2->sibling = t1->child;
        t1->child = t2;
        t1->degree++;
        return t1;
    }

    // merge two root lists sorted by degree
    BinomialNode* mergeRootLists(BinomialNode* h1, BinomialNode* h2) {
        if (!h1) return h2;
        if (!h2) return h1;

        BinomialNode* newHead = nullptr;
        BinomialNode** pos = &newHead;

        while (h1 && h2) {
            if (h1->degree <= h2->degree) {
                *pos = h1; h1 = h1->sibling;
            } else {
                *pos = h2; h2 = h2->sibling;
            }
            pos = &((*pos)->sibling);
        }
        *pos = h1 ? h1 : h2;
        return newHead;
    }

    // print a binomial tree (preorder)
    void printTree(BinomialNode* root, int level = 0) {
        if (!root) return;
        for (int i = 0; i < level; i++) std::cout << "  ";
        std::cout << root->data << std::endl;
        printTree(root->child, level + 1);
        printTree(root->sibling, level);
    }

public:
    BinomialQueue() : head(nullptr) {}

    // merge with another queue
    void merge(BinomialQueue& other) {
        if (this == &other) return;

        BinomialNode* newHead = mergeRootLists(head, other.head);
        other.head = nullptr;
        if (!newHead) { head = nullptr; return; }

        BinomialNode *prev = nullptr, *curr = newHead, *next = curr->sibling;

        while (next) {
            bool mergeNeeded = (curr->degree == next->degree) &&
                (!next->sibling || next->sibling->degree != curr->degree);

            if (!mergeNeeded) {
                prev = curr; curr = next; next = next->sibling;
            } else {
                curr = linkTree(curr, next);
                curr->sibling = next->sibling;
                if (prev) prev->sibling = curr;
                else newHead = curr;
                next = curr->sibling;
            }
        }
        head = newHead;
    }

    // insert a value
    void insert(int val) {
        BinomialQueue single;
        single.head = new BinomialNode(val);
        merge(single);
    }

    // find minimum key
    BinomialNode* findMin() {
        if (!head) return nullptr;
        BinomialNode* minNode = head;
        BinomialNode* curr = head->sibling;
        while (curr) {
            if (curr->data < minNode->data) minNode = curr;
            curr = curr->sibling;
        }
        return minNode;
    }

    // extract minimum key
    int extractMin() {
        if (!head) throw std::runtime_error("Empty queue");

        // find min and its predecessor
        BinomialNode *minNode = head, *minPrev = nullptr;
        BinomialNode *curr = head, *prev = nullptr;
        while (curr) {
            if (curr->data < minNode->data) {
                minNode = curr; minPrev = prev;
            }
            prev = curr; curr = curr->sibling;
        }

        // remove min from root list
        int minVal = minNode->data;
        if (minPrev) minPrev->sibling = minNode->sibling;
        else head = minNode->sibling;

        // reverse children
        BinomialNode* child = minNode->child;
        BinomialNode* revList = nullptr;
        while (child) {
            BinomialNode* next = child->sibling;
            child->sibling = revList;
            child->parent = nullptr;
            revList = child;
            child = next;
        }

        BinomialQueue childQueue;
        childQueue.head = revList;
        merge(childQueue);

        delete minNode;
        return minVal;
    }

    // check if empty
    bool isEmpty() { return head == nullptr; }

    // print the entire queue
    void print() {
        if (!head) {
            std::cout << "(empty)" << std::endl;
            return;
        }
        BinomialNode* curr = head;
        int treeNum = 0;
        while (curr) {
            std::cout << "B" << curr->degree << ": ";
            printTree(curr);
            curr = curr->sibling;
            treeNum++;
        }
    }
};

int main() {
    BinomialQueue bq;

    // insert values
    std::cout << "Inserting: 10, 20, 5, 15, 30, 3, 8" << std::endl;
    int values[] = {10, 20, 5, 15, 30, 3, 8};
    for (int v : values) {
        bq.insert(v);
        std::cout << "  Inserted " << v << std::endl;
    }

    // find min
    BinomialNode* minNode = bq.findMin();
    std::cout << "\nMinimum: " << minNode->data << std::endl;

    // extract min
    std::cout << "\nExtract min: " << bq.extractMin() << std::endl;
    minNode = bq.findMin();
    std::cout << "New minimum: " << minNode->data << std::endl;

    // extract more
    std::cout << "\nExtract min: " << bq.extractMin() << std::endl;
    minNode = bq.findMin();
    std::cout << "New minimum: " << minNode->data << std::endl;

    // extract all remaining
    std::cout << "\nExtracting all remaining:" << std::endl;
    while (!bq.isEmpty()) {
        std::cout << "  Extracted: " << bq.extractMin() << std::endl;
    }

    return 0;
}

运行该程序将输出:

Inserting: 10, 20, 5, 15, 30, 3, 8
  Inserted 10
  Inserted 20
  Inserted 5
  Inserted 15
  Inserted 30
  Inserted 3
  Inserted 8

Minimum: 3

Extract min: 3
New minimum: 5

Extract min: 5
New minimum: 8

Extracting all remaining:
  Extracted: 8
  Extracted: 10
  Extracted: 15
  Extracted: 20
  Extracted: 30

可以看到,提取最小值后,队列自动重整,新的最小值始终可以正确找到。所有值按升序被依次提取出来。

C 完整实现

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct BinomialNode {
    int data;
    int degree;
    struct BinomialNode* parent;
    struct BinomialNode* child;
    struct BinomialNode* sibling;
} BinomialNode;

typedef struct {
    BinomialNode* head;
} BinomialQueue;

// create a new node
BinomialNode* createNode(int val) {
    BinomialNode* node = (BinomialNode*)malloc(sizeof(BinomialNode));
    node->data = val;
    node->degree = 0;
    node->parent = NULL;
    node->child = NULL;
    node->sibling = NULL;
    return node;
}

// link two Bk trees into one B(k+1)
BinomialNode* linkTree(BinomialNode* t1, BinomialNode* t2) {
    if (t1->data > t2->data) {
        BinomialNode* temp = t1; t1 = t2; t2 = temp;
    }
    t2->parent = t1;
    t2->sibling = t1->child;
    t1->child = t2;
    t1->degree++;
    return t1;
}

// merge two root lists by degree
BinomialNode* mergeRootLists(BinomialNode* h1, BinomialNode* h2) {
    if (!h1) return h2;
    if (!h2) return h1;

    BinomialNode* newHead = NULL;
    BinomialNode** pos = &newHead;

    while (h1 && h2) {
        if (h1->degree <= h2->degree) {
            *pos = h1; h1 = h1->sibling;
        } else {
            *pos = h2; h2 = h2->sibling;
        }
        pos = &((*pos)->sibling);
    }
    *pos = h1 ? h1 : h2;
    return newHead;
}

// union two binomial queues
void binomialMerge(BinomialQueue* q1, BinomialQueue* q2) {
    if (q1 == q2) return;

    BinomialNode* newHead = mergeRootLists(q1->head, q2->head);
    q2->head = NULL;
    if (!newHead) { q1->head = NULL; return; }

    BinomialNode *prev = NULL, *curr = newHead, *next = curr->sibling;
    while (next) {
        bool mergeNeeded = (curr->degree == next->degree) &&
            (!next->sibling || next->sibling->degree != curr->degree);
        if (!mergeNeeded) {
            prev = curr; curr = next; next = next->sibling;
        } else {
            curr = linkTree(curr, next);
            curr->sibling = next->sibling;
            if (prev) prev->sibling = curr;
            else newHead = curr;
            next = curr->sibling;
        }
    }
    q1->head = newHead;
}

// insert a value
void binomialInsert(BinomialQueue* q, int val) {
    BinomialQueue single;
    single.head = createNode(val);
    binomialMerge(q, &single);
}

// find minimum key
BinomialNode* binomialFindMin(BinomialQueue* q) {
    if (!q->head) return NULL;
    BinomialNode* minNode = q->head;
    BinomialNode* curr = q->head->sibling;
    while (curr) {
        if (curr->data < minNode->data) minNode = curr;
        curr = curr->sibling;
    }
    return minNode;
}

// extract minimum key
int binomialExtractMin(BinomialQueue* q) {
    if (!q->head) return -1;

    // find min root
    BinomialNode *minNode = q->head, *minPrev = NULL;
    BinomialNode *curr = q->head, *prev = NULL;
    while (curr) {
        if (curr->data < minNode->data) {
            minNode = curr; minPrev = prev;
        }
        prev = curr; curr = curr->sibling;
    }

    // remove from root list
    int minVal = minNode->data;
    if (minPrev) minPrev->sibling = minNode->sibling;
    else q->head = minNode->sibling;

    // reverse children
    BinomialNode* child = minNode->child;
    BinomialNode* revList = NULL;
    while (child) {
        BinomialNode* next = child->sibling;
        child->sibling = revList;
        child->parent = NULL;
        revList = child;
        child = next;
    }
    free(minNode);

    // merge children back
    BinomialQueue childQ;
    childQ.head = revList;
    binomialMerge(q, &childQ);

    return minVal;
}

// check if empty
bool isEmpty(BinomialQueue* q) { return q->head == NULL; }

int main() {
    BinomialQueue bq = {NULL};

    printf("Inserting: 10, 20, 5, 15, 30, 3, 8\n");
    int values[] = {10, 20, 5, 15, 30, 3, 8};
    for (int i = 0; i < 7; i++) {
        binomialInsert(&bq, values[i]);
        printf("  Inserted %d\n", values[i]);
    }

    BinomialNode* minNode = binomialFindMin(&bq);
    printf("\nMinimum: %d\n", minNode->data);

    printf("\nExtract min: %d\n", binomialExtractMin(&bq));
    minNode = binomialFindMin(&bq);
    printf("New minimum: %d\n", minNode->data);

    printf("\nExtract min: %d\n", binomialExtractMin(&bq));
    minNode = binomialFindMin(&bq);
    printf("New minimum: %d\n", minNode->data);

    printf("\nExtracting all remaining:\n");
    while (!isEmpty(&bq)) {
        printf("  Extracted: %d\n", binomialExtractMin(&bq));
    }

    return 0;
}

运行该程序将输出:

Inserting: 10, 20, 5, 15, 30, 3, 8
  Inserted 10
  Inserted 20
  Inserted 5
  Inserted 15
  Inserted 30
  Inserted 3
  Inserted 8

Minimum: 3

Extract min: 3
New minimum: 5

Extract min: 5
New minimum: 8

Extracting all remaining:
  Extracted: 8
  Extracted: 10
  Extracted: 15
  Extracted: 20
  Extracted: 30

C 语言版本使用结构体和函数指针实现,逻辑与 C++ 版本一致,需要手动管理内存(malloc / free)。

Python 完整实现

class BinomialNode:
    def __init__(self, data):
        self.data = data
        self.degree = 0
        self.parent = None
        self.child = None
        self.sibling = None


class BinomialQueue:
    def __init__(self):
        self.head = None

    def _link_tree(self, t1, t2):
        """Link two Bk trees into one B(k+1)."""
        if t1.data > t2.data:
            t1, t2 = t2, t1
        t2.parent = t1
        t2.sibling = t1.child
        t1.child = t2
        t1.degree += 1
        return t1

    def _merge_root_lists(self, h1, h2):
        """Merge two root lists sorted by degree."""
        if not h1:
            return h2
        if not h2:
            return h1

        dummy = BinomialNode(0)
        tail = dummy

        while h1 and h2:
            if h1.degree <= h2.degree:
                tail.sibling = h1
                h1 = h1.sibling
            else:
                tail.sibling = h2
                h2 = h2.sibling
            tail = tail.sibling

        tail.sibling = h1 if h1 else h2
        return dummy.sibling

    def merge(self, other):
        """Union this queue with another."""
        if self is other:
            return

        new_head = self._merge_root_lists(self.head, other.head)
        other.head = None

        if not new_head:
            self.head = None
            return

        prev = None
        curr = new_head
        nxt = curr.sibling

        while nxt:
            merge_needed = (curr.degree == nxt.degree) and \
                (nxt.sibling is None or nxt.sibling.degree != curr.degree)

            if not merge_needed:
                prev = curr
                curr = nxt
                nxt = nxt.sibling
            else:
                curr = self._link_tree(curr, nxt)
                curr.sibling = nxt.sibling
                if prev:
                    prev.sibling = curr
                else:
                    new_head = curr
                nxt = curr.sibling

        self.head = new_head

    def insert(self, val):
        """Insert a value into the queue."""
        single = BinomialQueue()
        single.head = BinomialNode(val)
        self.merge(single)

    def find_min(self):
        """Find the minimum key, O(log n)."""
        if not self.head:
            return None

        min_node = self.head
        curr = self.head.sibling
        while curr:
            if curr.data < min_node.data:
                min_node = curr
            curr = curr.sibling
        return min_node

    def extract_min(self):
        """Remove and return the minimum key."""
        if not self.head:
            return None

        # find min root and predecessor
        min_node = self.head
        min_prev = None
        prev = None
        curr = self.head

        while curr:
            if curr.data < min_node.data:
                min_node = curr
                min_prev = prev
            prev = curr
            curr = curr.sibling

        # remove from root list
        if min_prev:
            min_prev.sibling = min_node.sibling
        else:
            self.head = min_node.sibling

        # reverse children
        child = min_node.child
        rev_list = None
        while child:
            nxt = child.sibling
            child.sibling = rev_list
            child.parent = None
            rev_list = child
            child = nxt

        # merge children back
        child_queue = BinomialQueue()
        child_queue.head = rev_list
        self.merge(child_queue)

        return min_node.data

    def is_empty(self):
        """Check if queue is empty."""
        return self.head is None


if __name__ == "__main__":
    bq = BinomialQueue()

    print("Inserting: 10, 20, 5, 15, 30, 3, 8")
    for v in [10, 20, 5, 15, 30, 3, 8]:
        bq.insert(v)
        print(f"  Inserted {v}")

    min_node = bq.find_min()
    print(f"\nMinimum: {min_node.data}")

    print(f"\nExtract min: {bq.extract_min()}")
    min_node = bq.find_min()
    print(f"New minimum: {min_node.data}")

    print(f"\nExtract min: {bq.extract_min()}")
    min_node = bq.find_min()
    print(f"New minimum: {min_node.data}")

    print("\nExtracting all remaining:")
    while not bq.is_empty():
        print(f"  Extracted: {bq.extract_min()}")

运行该程序将输出:

Inserting: 10, 20, 5, 15, 30, 3, 8
  Inserted 10
  Inserted 20
  Inserted 5
  Inserted 15
  Inserted 30
  Inserted 3
  Inserted 8

Minimum: 3

Extract min: 3
New minimum: 5

Extract min: 5
New minimum: 8

Extracting all remaining:
  Extracted: 8
  Extracted: 10
  Extracted: 15
  Extracted: 20
  Extracted: 30

Python 版本最为简洁直观,使用 None 表示空指针,无需手动内存管理。三个版本的输出结果完全一致,验证了算法实现的正确性。

Go 完整实现

package main

import "fmt"

// BinomialNode represents a node in the binomial heap
type BinomialNode struct {
	key     int
	degree  int
	parent  *BinomialNode
	child   *BinomialNode
	sibling *BinomialNode
}

// newBinomialNode creates a new node with the given key
func newBinomialNode(key int) *BinomialNode {
	return &BinomialNode{key: key}
}

// BinomialHeap represents a binomial heap (min-heap)
type BinomialHeap struct {
	roots []*BinomialNode
}

// linkTree merges two Bk trees into one B(k+1), smaller root on top
func linkTree(t1, t2 *BinomialNode) *BinomialNode {
	if t1.key > t2.key {
		t1, t2 = t2, t1
	}
	t2.parent = t1
	t2.sibling = t1.child
	t1.child = t2
	t1.degree++
	return t1
}

// mergeRootLists merges two root slices by ascending degree
func mergeRootLists(h1, h2 []*BinomialNode) []*BinomialNode {
	result := make([]*BinomialNode, 0, len(h1)+len(h2))
	i, j := 0, 0
	for i < len(h1) && j < len(h2) {
		if h1[i].degree <= h2[j].degree {
			result = append(result, h1[i])
			i++
		} else {
			result = append(result, h2[j])
			j++
		}
	}
	result = append(result, h1[i:]...)
	result = append(result, h2[j:]...)
	return result
}

// Merge unions this heap with another
func (h *BinomialHeap) Merge(other *BinomialHeap) {
	if h == other {
		return
	}

	merged := mergeRootLists(h.roots, other.roots)
	other.roots = nil

	if len(merged) == 0 {
		h.roots = nil
		return
	}

	// Scan and merge adjacent trees of same degree (like binary addition)
	var newRoots []*BinomialNode
	i := 0
	for i < len(merged) {
		if i+1 < len(merged) && merged[i].degree == merged[i+1].degree {
			if i+2 < len(merged) && merged[i].degree == merged[i+2].degree {
				// Three in a row: merge first two, keep third for next round
				merged[i] = linkTree(merged[i], merged[i+1])
				merged = append(merged[:i+1], merged[i+2:]...)
			} else {
				// Two in a row: merge them
				merged[i] = linkTree(merged[i], merged[i+1])
				merged = append(merged[:i+1], merged[i+2:]...)
			}
		} else {
			newRoots = append(newRoots, merged[i])
			i++
		}
	}
	h.roots = newRoots
}

// Insert adds a value to the heap
func (h *BinomialHeap) Insert(val int) {
	single := &BinomialHeap{
		roots: []*BinomialNode{newBinomialNode(val)},
	}
	h.Merge(single)
}

// FindMin returns the node with minimum key, O(log n)
func (h *BinomialHeap) FindMin() *BinomialNode {
	if len(h.roots) == 0 {
		return nil
	}
	minNode := h.roots[0]
	for i := 1; i < len(h.roots); i++ {
		if h.roots[i].key < minNode.key {
			minNode = h.roots[i]
		}
	}
	return minNode
}

// ExtractMin removes and returns the minimum key from the heap
func (h *BinomialHeap) ExtractMin() (int, bool) {
	if len(h.roots) == 0 {
		return 0, false
	}

	// Find the index of the minimum root
	minIdx := 0
	for i := 1; i < len(h.roots); i++ {
		if h.roots[i].key < h.roots[minIdx].key {
			minIdx = i
		}
	}

	minVal := h.roots[minIdx].key

	// Collect children of the min root (reverse order by prepending)
	child := h.roots[minIdx].child
	var children []*BinomialNode
	for child != nil {
		next := child.sibling
		child.parent = nil
		child.sibling = nil
		children = append([]*BinomialNode{child}, children...)
		child = next
	}

	// Remove min root from slice
	h.roots = append(h.roots[:minIdx], h.roots[minIdx+1:]...)

	// Merge children back as a separate heap
	if len(children) > 0 {
		childHeap := &BinomialHeap{roots: children}
		h.Merge(childHeap)
	}

	return minVal, true
}

// IsEmpty checks if the heap is empty
func (h *BinomialHeap) IsEmpty() bool {
	return len(h.roots) == 0
}

func main() {
	bq := &BinomialHeap{}

	fmt.Println("Inserting: 10, 20, 5, 15, 30, 3, 8")
	values := []int{10, 20, 5, 15, 30, 3, 8}
	for _, v := range values {
		bq.Insert(v)
		fmt.Printf("  Inserted %d\n", v)
	}

	minNode := bq.FindMin()
	fmt.Printf("\nMinimum: %d\n", minNode.key)

	val, _ := bq.ExtractMin()
	fmt.Printf("\nExtract min: %d\n", val)
	minNode = bq.FindMin()
	fmt.Printf("New minimum: %d\n", minNode.key)

	val, _ = bq.ExtractMin()
	fmt.Printf("\nExtract min: %d\n", val)
	minNode = bq.FindMin()
	fmt.Printf("New minimum: %d\n", minNode.key)

	fmt.Println("\nExtracting all remaining:")
	for !bq.IsEmpty() {
		val, _ = bq.ExtractMin()
		fmt.Printf("  Extracted: %d\n", val)
	}
}

Go 版本使用切片 []*BinomialNode 存储根节点列表,结构体 BinomialHeap 封装所有操作。ExtractMinFindMin 返回值加布尔值表示是否成功。Go 的垃圾回收机制免去了手动内存管理的负担。

运行该程序将输出:

Inserting: 10, 20, 5, 15, 30, 3, 8
  Inserted 10
  Inserted 20
  Inserted 5
  Inserted 15
  Inserted 30
  Inserted 3
  Inserted 8

Minimum: 3

Extract min: 3
New minimum: 5

Extract min: 5
New minimum: 8

Extracting all remaining:
  Extracted: 8
  Extracted: 10
  Extracted: 15
  Extracted: 20
  Extracted: 30

四个版本的输出结果完全一致,验证了算法实现的正确性。


二项式队列的性质

时间复杂度

操作 二项式队列 二叉堆
插入(Insert) O(log n) 最坏,O(1) 摊还 O(log n)
查找最小值(Find Min) O(log n) O(1)
提取最小值(Extract Min) O(log n) O(log n)
合并(Merge) O(log n) O(n)
删除(Delete) O(log n) O(log n)
降低键值(Decrease Key) O(log n) O(log n)

与二叉堆的对比

二项式队列最大的优势在于合并操作:二叉堆的合并需要 O(n) 时间,而二项式队列只需 O(log n)。这使得二项式队列在以下场景中更有优势:

  • 需要频繁合并的优先队列:例如多路归并排序、图算法中的多源最短路径
  • 无法预先确定元素总数的场景:二项式队列不需要预先分配固定大小的数组
  • 需要支持所有优先队列操作的可合并堆:二项式队列是斐波那契堆(Fibonacci Heap)的基础

二叉堆的优势

  • 实现简单,常数因子小
  • 查找最小值 O(1)(二项式队列是 O(log n))
  • 可以用数组存储,缓存友好
  • 对于不需要合并操作的场景,二叉堆通常是更好的选择

空间复杂度

二项式队列的空间复杂度为 O(n),每个节点额外需要三个指针(parent、child、sibling)和一个 degree 字段。相比之下,二叉堆用数组存储,没有指针开销。

应用场景

  • 可合并优先队列(Mergeable Priority Queue):二项式队列的核心应用,支持高效的堆合并
  • 图算法:在 Dijkstra、Prim 等算法中,当需要处理多个优先队列的合并时,二项式队列比二叉堆更高效
  • 事件模拟:多个事件队列合并为一个全局队列
  • 外部排序:多路归并时,多个已排序的子序列可以用二项式队列高效管理
  • 斐波那契堆的基础:斐波那契堆是二项式队列的扩展,支持更优秀的摊还时间复杂度
posted @ 2026-04-17 02:17  游翔  阅读(7)  评论(0)    收藏  举报