Something About Dijkstra (4)

斐波那契堆

斐波那契堆是一种相对松散的堆结构。它的存储结构并不是一棵树,而是一个森林,并且每棵树都是一个符合堆结构的多叉树。
它的特点是只在删除掉顶点以后整理堆的结构。并且通过做标记的形式保持堆的平衡。

具体操作

Insert

直接插入对应结点,松散排列。

DeleteMin

删除顶点后先直接将所有子节点作为一棵树直接放进堆,然后进行整理。
整理的方式是,将rank(即根节点的孩子数)相同的树合并为一棵树。

//别问我为什么没做成 gif ,因为没有 PS ,而且懒。

去掉顶点并直接将子节点作为独立的树:

开始合并操作:

将树的根节点按照rank依次放进一个指针数组:

发现已经有相同rank的根节点则进行合并:

不断合并直到rank唯一:

继续:

合并:

到这里,整个堆的根节点都放进数组,整理完成。

DecreaseKey

此过程需要用到之前定义的 mark 属性,它表示这一结点是否已经被删除过子节点。通过这一标记来尽量保证树的平衡,避免出现“链”的结构。

具体操作如下:

首先减小某一顶点的值,然后观察其是否是小于父节点:
  若无父节点,则看是否要更新 min 指针;
  若依然大于父节点,则不做修改;
  若小于父节点,则剪断改分支,并尝试对父节点做标记;
    若父节点已经有标记,则剪断父节点并递归对其父节点做标记,直到可以做标记或已经是根节点为止。

将46减小为29:

小于父节点,无需修改

将29减小为15:

小于父节点,剪断,并做标记:

24被标记:

将35减小为5:

小于父节点,剪断,并做标记:

父节点已经被标记一次了,所以剪断父节点,并对其父节点做标记:

发现其父节点也已经被标记了,所以再次剪断父节点并对其父节点做标记:

由于父节点是根节点,所以不再做标记:

时间复杂度分析

符号 含义
n 节点数
rank(x) 结点 x 的孩子数
rank(H) 堆 H 的最大 rank
trees(H) 堆 H 中树的数量
marks(H) 堆 H 中已标记的点数

定义一个势函数: Φ(H) = trees(H) + 2 marks(H)

  • Insert:

    • 时间复杂度: O(1)
    • 势函数变化: + 1
    • 均摊时间复杂度: O(1)
  • Delete Min:

    • O(rank(H)) + O(trees(H))

      • O(rank(H))将最小值的孩子合并到根节点列表中
      • O(rank(H)) + O(trees(H)) 更新最小值
      • O(rank(H)) + O(trees(H)) 巩固森林
    • 势函数变化:O(rank(H)) - trees(H)

      • trees(H’) ≤ rank(H) + 1 因为没有两个树有相同的rank
      • △Φ(H) ≤ rank(H) + 1 - trees(H)
    • 均摊时间复杂度: O(rank(H))

  • Decrease Key:

    • O(c) (c 表示剪断次数)

      • O(1) 改变权值
      • O(1) 剪断并放到根节点列表
    • 势函数变化:O(1) - c

      • trees(H’) = trees(H) + c
      • marks(H’) ≤ marks(H) - c + 2
      • △Φ ≤ c + 2 (-c + 2) = 4 - c
    • 均摊时间复杂度: O(1)

可以证明,rank(H) ≤ log Φ (|V|)(其中Φ表示(1 + √5) / 2 ≈ 1.618),由于证明过程有点复杂这里不再说明。
所以最终复杂度为O( ( 1 + log Φ (|V|) )|V| + |E| )