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
= * * *
/ \ / | \
* * * * *
| / \
* * *
|
*
这就是二项式队列的精妙之处:用二进制来管理一组二项树,合并操作就相当于二进制加法。
二项树的性质
二项树具有以下重要性质:
- 节点数量:Bk 恰好有 2^k 个节点。这可以通过递归定义直接推出:两棵 B(k-1) 合并 = 2 * 2^(k-1) = 2^k。
- 树的高度:Bk 的高度为 k。每合并一次高度增加 1。
- 根的子节点数:Bk 的根节点恰好有 k 个子节点,从左到右分别是 B(k-1)、B(k-2)、...、B0。
- 深度 d 处的节点数:Bk 在深度 d 处恰好有 C(k, d) 个节点(二项式系数),这正是"二项树"名称的由来。
- 合并复杂度:两棵同阶的 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 语言版本使用 typedef 和 malloc,结构与 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)是最复杂的操作,分为以下几步:
- 在根链表中找到最小值的根节点
- 从根链表中移除该根节点
- 将该根节点的所有子节点逆序(因为子节点按度数从大到小排列,需要变为从小到大)
- 将逆序后的子节点链表作为一个新的二项式队列
- 将新队列与原队列合并
提取最小值示例:
原始队列: [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 封装所有操作。ExtractMin 和 FindMin 返回值加布尔值表示是否成功。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 等算法中,当需要处理多个优先队列的合并时,二项式队列比二叉堆更高效
- 事件模拟:多个事件队列合并为一个全局队列
- 外部排序:多路归并时,多个已排序的子序列可以用二项式队列高效管理
- 斐波那契堆的基础:斐波那契堆是二项式队列的扩展,支持更优秀的摊还时间复杂度

浙公网安备 33010602011771号