随机可合并优先队列与凸多联骨牌重建算法
随机可合并优先队列
1. 引言
在优先队列的实现中,传统方法主要分为两类。一类是为保证单个操作的最坏情况效率,多数数据结构需要存储与队列节点相关的额外平衡信息,如左偏树、松弛堆、Brodal 队列等;另一类则是通过在某些操作期间调整结构而非持续维持平衡来实现良好的摊还性能,如斜堆、配对堆等。实验表明,后一种方法在实践中更有前景,但它不能应用于实时程序,因为实时程序中每个操作的最坏情况运行时间至关重要。
本文提出一种随机化方法来实现高效的可合并优先队列。该数据结构支持的操作包括:
-
MakeQueue
:返回一个空的优先队列。
-
FindMin(Q)
:返回优先队列 Q 中的最小项。
-
DeleteMin(Q)
:删除并返回优先队列 Q 中的最小项。
-
Insert(Q, e)
:将项 e 插入优先队列 Q。
-
Meld(Q1, Q2)
:返回由不相交的优先队列 Q1 和 Q2 合并而成的优先队列。
-
DecreaseKey(Q, e, e0)
:若 e0 ≤ e 且已知 e 在 Q 中的位置,则将优先队列 Q 中的项 e 替换为 e0。
-
Delete(Q, e)
:若已知 e 在 Q 中的位置,则从优先队列 Q 中删除项 e(最后两个操作有时被视为可选)。
2. 随机堆
随机堆的底层数据结构是一棵二叉树,每个节点包含一个项,满足堆属性:若 x 和 y 是节点且 x 是 y 的父节点,则
item(x) ≤ item(y)
,通过树的根节点访问堆。
以下是可合并优先队列操作的实现:
-
MakeQueue
:返回一个空树。
-
FindMin
:返回根节点中保存的项。
-
Meld
:合并两个非空树的伪代码如下:
heap function Meld(heap Q1; Q2)
if Q1 = null then return Q2
if Q2 = null then return Q1
if item(Q1) > item(Q2) then Q1 ↔ Q2
if toss coin = heads then left(Q1) := Meld(left(Q1); Q2)
else right(Q1) := Meld(right(Q1); Q2)
return Q1
-
Insert:创建一个包含项 e 的单节点并与堆 Q 合并。 -
DeleteMin:合并根节点的左子树和右子树,并返回(旧)根节点中保存的项。 -
DecreaseKEY和Delete:需要每个节点有父指针。对于DecreaseKEY,将以 x 为根的树从 Q 中分离,相应调整 x 处的项,然后将 Q 与以 x 为根的堆合并;对于Delete,将以 x 为根的树从堆 Q 中分离,对以 x 为根的堆执行DeleteMin,最后将结果堆与 Q 合并。
3. 效率分析
由于所有非常数时间操作都基于
Meld
定义,因此只需分析合并两个随机堆的复杂度。
定义随机变量
hQ
为从根到外部节点的随机路径的长度(边的数量)。
-
引理 1
:合并两个随机堆 Q1 和 Q2 需要时间
O(hQ1 + hQ2)。 -
定理 1
:设 Q 是具有 n 个内部节点的任意二叉树。
-
(a) 期望值
EhQ ≤ log(n + 1)。 -
(b) 对于任何常数 c > 0,
Pr[hQ > (c + 1) log n] < 1 / nc。
-
(a) 期望值
-
推论 1
:在具有 n 个节点的随机堆上,任何可合并优先队列操作的期望时间为
O(log n)。此外,对于每个常数 ε > 0,存在一个常数 c > 0,使得每个操作的时间至多为 c log n 的概率超过1 - n - ε。
4. 实验
为了测量随机堆在实践中的性能,我们进行了一些测试。测试方法如下:
1. 对于固定的 n,依次创建树:
- 从几乎排序的排列(与 <1, …, n> 相差 n/2 个转置)。
- 从随机排列。
- 从几乎逆序的排列(与
相差 n/2 个转置)。
2. 计算 h 的值以及合并两个这样的树(都包含键 1, …, n)时遍历的路径总长度。由于即使从固定排列也可能得到不同的树,因此对每个 n 值的结果进行 100 次测试并取平均值。
实验结果如下表所示(每个显示的值是表达式
c log(n + 1)
中的因子 c):
| n | 几乎排序排列 - h | 几乎排序排列 - meld | 随机排列 - h | 随机排列 - meld | 几乎逆序排列 - h | 几乎逆序排列 - meld |
| — | — | — | — | — | — | — |
| 50 | 0.85 | 1.34 | 0.79 | 1.28 | 0.63 | 0.79 |
| 500 | 0.80 | 1.39 | 0.76 | 1.31 | 0.50 | 0.65 |
| 5000 | 0.78 | 1.41 | 0.74 | 1.32 | 0.40 | 0.49 |
| 15000 | 0.77 | 1.42 | 0.73 | 1.32 | 0.36 | 0.41 |
从实验结果可以看出,对于从随机排列获得的树,h 的值仅为完全树值的 3/4。此外,合并两个这样的树时遍历的路径总长度比引理 1 中估计的 h 的两倍小约 15%。
5. 结论
随机堆可以通过类似于已知的 d 元堆的方式进行扩展,以增加其灵活性。对于具有至多 n 个节点的随机 d 堆,操作复杂度估计如下:
-
MakeQueue
,
FindMin
:O(1)
-
Meld
,
DecreaseKey
:
O(logd n)
-
Insert
:
O(d + logd n)
(需要将新节点中的 d 个指针初始化为 null)
-
DeleteMin
,
Delete
:
O(d logd n)
(需要合并 O(d) 个堆)
随机堆是一种非常简单的随机化数据结构,能够以高概率在对数时间内支持所有可合并优先队列操作。实验表明,操作复杂度的常数因子实际上比理论分析得出的还要小。其简单性、灵活性和小内存开销使其成为具有最坏情况性能保证的可合并优先队列的实用选择。
凸多联骨牌从水平和垂直投影重建算法
1. 引言
1.1 问题定义
设 R 是一个 m × n 的矩阵,包含“0”和“1”。设 S 是包含“1”的单元格集合。定义
hi(S)
为 S 的第 i 行中包含“1”的单元格数量,
vj(S)
为 S 的第 j 列中包含“1”的单元格数量,分别称为 S 的第 i 行投影和第 j 列投影。
我们考虑集合 S 的不同属性:
- p:S 是一个多联骨牌,即 S 是一个连通的有限集。
- v:S 的每一列都是连通集,即 R 中不存在两个不同“1”之间包含“0”的列。
- h:S 的每一行都是连通集,即 R 中不存在两个不同“1”之间包含“0”的行。
集合 S 属于类 (x)(S ∈ (x))当且仅当它满足属性 x。问题是给定两个指定的向量
H = (h1, h2, ..., hm) ∈ {1, ..., n}m
和
V = (v1, v2, ..., vn) ∈ {1, ..., m}n
,检查对 (H, V) 在类 (x) 中是否可满足,即是否存在至少一个集合 S ∈ (x) 使得
hi(S) = hi
(i = 1, …, m)且
vj(S) = vj
(j = 1, …, n)。若 S 满足 (p, v, h),则定义 S 为凸多联骨牌。
1.2 先前工作
早期,Ryser、Chang 和 Wang 研究了在无任何条件的集合类中满足 (H, V) 的 S 的存在性,表明决策问题可以在 O(mn) 时间内解决,并开发了一些从 (H, V) 重建 S 的算法。
Woeginger 证明了在水平和垂直凸集类 (h, v) 以及多联骨牌类 (p) 中的重建问题是 NP 完全问题。Barcucci 等人表明在列凸多联骨牌类 (p, v)、行凸多联骨牌类 (p, h) 以及具有连通列 (v) 或行 (h) 的集合类中,重建问题也是 NP 完全问题。因此,只有当单元格集满足所有三个属性 (p, h, v) 时,问题才能在多项式时间内解决。
之前有一种算法可以在多项式时间内确定满足一对指定向量 (H, V) 的凸多联骨牌 (p, v, h) 的存在性,其主要思想是构造一些“0”和“1”的初始位置,并对每个这样的位置执行填充操作,该算法的复杂度为
O(m4n4)
。
2. 凸多联骨牌的一些属性
假设矩阵 R 中 n ≤ m,若 n > m 则交换列和行。同时假设
∑mj = 1 hj = ∑ni = 1 vi
,否则不存在解。
设
<n1, n2>
为上排(第一行)中“1”的位置,
<s1, s2>
为下排(第 m 行)中“1”的位置,这些单元格称为“脚”的位置。引入以下符号:
-
Hk = ∑kj = 1 hj
-
Vk = ∑ki = 1 vi
3. 新算法
本文提出一种新算法,用于从水平和垂直投影重建凸多联骨牌,该算法的复杂度至多为
O(min(m, n)2 · mn log mn)
。具体如下:
- 在第 2 节描述了凸多联骨牌的一些属性。
- 在第 3 节展示了一种新的填充操作过程,其复杂度仅为
O(mn log mn)
。
- 在第 4 节描述了一种新的初始位置的想法,可得到正确的解决方案。
综上所述,随机可合并优先队列和凸多联骨牌重建算法都在各自领域有着重要的意义和应用价值。随机堆以其简单性、灵活性和良好的性能成为可合并优先队列的实用选择,而新的凸多联骨牌重建算法则为相关问题的解决提供了更高效的方法。
3.1 新的填充操作过程
新的填充操作过程是整个算法的核心部分,它的复杂度仅为
O(mn log mn)
,相较于之前的算法有了显著的提升。具体步骤如下:
1.
初始化
:根据之前定义的“脚”的位置和相关符号,对矩阵进行初步的标记和准备。
2.
遍历矩阵
:从矩阵的左上角开始,逐行逐列地遍历矩阵中的每个单元格。
3.
判断条件
:对于每个单元格,根据其所在行和列的投影信息,以及当前矩阵的填充状态,判断该单元格是否可以填充为“1”。
4.
填充操作
:如果满足填充条件,则将该单元格填充为“1”,并更新相关的投影信息和矩阵状态。
5.
回溯调整
:如果在填充过程中发现无法继续满足投影条件,则进行回溯操作,调整之前的填充选择,重新尝试。
3.2 新的初始位置想法
新的初始位置的选择对于算法的正确性和效率至关重要。具体思路如下:
1.
分析投影信息
:仔细研究给定的水平和垂直投影向量
H
和
V
,找出其中的关键信息和规律。
2.
确定关键位置
:根据投影信息,确定一些关键的“脚”的位置,这些位置将作为算法的起始点。
3.
构建初始矩阵
:基于关键位置,构建一个初始的矩阵,其中包含一些预先确定的“0”和“1”的位置。
4.
逐步扩展
:从初始矩阵开始,逐步扩展矩阵的填充范围,根据投影条件和凸多联骨牌的属性进行填充。
4. 复杂度分析
新算法的复杂度至多为
O(min(m, n)2 · mn log mn)
,下面对其进行详细分析:
-
填充操作复杂度
:新的填充操作过程的复杂度为
O(mn log mn)
,这是因为在遍历矩阵的过程中,每次判断和填充操作都需要考虑当前的投影信息,而投影信息的更新和判断需要一定的时间复杂度。
-
初始位置复杂度
:确定新的初始位置的复杂度主要取决于对投影信息的分析和关键位置的确定,这部分复杂度相对较低,通常为
O(min(m, n)2)
。
-
综合复杂度
:将填充操作复杂度和初始位置复杂度相乘,得到整个算法的复杂度为
O(min(m, n)2 · mn log mn)
。
5. 总结与展望
5.1 总结
本文介绍了两种重要的算法:随机可合并优先队列和凸多联骨牌从水平和垂直投影重建算法。随机堆以其简单性、灵活性和小内存开销,成为具有最坏情况性能保证的可合并优先队列的实用选择。而新的凸多联骨牌重建算法通过引入新的填充操作过程和初始位置想法,将算法复杂度从
O(m4n4)
降低到了
O(min(m, n)2 · mn log mn)
,大大提高了算法的效率。
5.2 展望
未来的研究可以从以下几个方面展开:
-
随机堆的进一步优化
:探索是否可以通过进一步改进随机化方法,降低随机堆操作的复杂度,同时保持数据结构的简单性。
-
凸多联骨牌重建算法的扩展
:研究如何将该算法应用到更复杂的场景中,例如处理非凸多联骨牌的重建问题。
-
算法的并行化实现
:考虑将这两种算法进行并行化实现,以进一步提高算法的执行效率,适应大规模数据处理的需求。
下面通过一个 mermaid 流程图来总结凸多联骨牌重建算法的整体流程:
graph TD;
A[开始] --> B[初始化矩阵和投影信息];
B --> C[确定新的初始位置];
C --> D[执行新的填充操作过程];
D --> E{是否满足投影条件};
E -- 是 --> F[输出结果矩阵];
E -- 否 --> G[回溯调整];
G --> D;
F --> H[结束];
通过以上的研究和分析,我们可以看到这两种算法在理论和实践中都具有重要的价值,未来的研究有望进一步推动它们的发展和应用。
相关表格回顾
为了更清晰地展示相关信息,下面再次列出随机堆实验结果的表格:
| n | 几乎排序排列 - h | 几乎排序排列 - meld | 随机排列 - h | 随机排列 - meld | 几乎逆序排列 - h | 几乎逆序排列 - meld |
| — | — | — | — | — | — | — |
| 50 | 0.85 | 1.34 | 0.79 | 1.28 | 0.63 | 0.79 |
| 500 | 0.80 | 1.39 | 0.76 | 1.31 | 0.50 | 0.65 |
| 5000 | 0.78 | 1.41 | 0.74 | 1.32 | 0.40 | 0.49 |
| 15000 | 0.77 | 1.42 | 0.73 | 1.32 | 0.36 | 0.41 |
同时,列出随机 d 堆操作复杂度的表格:
| 操作 | 复杂度 |
| — | — |
|
MakeQueue
,
FindMin
| O(1) |
|
Meld
,
DecreaseKey
|
O(logd n)
|
|
Insert
|
O(d + logd n)
|
|
DeleteMin
,
Delete
|
O(d logd n)
|
通过这些表格,我们可以更直观地比较不同情况下的性能和复杂度,为实际应用提供参考。
90

被折叠的 条评论
为什么被折叠?



