斯坦福 算法1 第五周笔记

来自斯坦福网站的Algorithms: Design and Analysis,与目前coursera上的版本内容没有变化,不过时间安排略有不同。

1. DIJKSTRA’S SHORTEST-PATH ALGORITHM

1.1 Basics

引入了一个新的问题,单元最短路径问题。输入是有向图G=(V,E),其中每个边有权重,需要计算某个点v起始点s的最短路径。输出这条路径的长度。(假设1:存在这样一条路径。假设2:权重非负。)

之前的BFS也可以计算最短路径,但是对应的是无权重的图。在有权重的图中使用dijkstra算法。

算法流程思路大致如下:将图中的点分为两个部分,一个是目前已经遍历过的集合X,剩下的作为一个集合V-X。每次从V-X中添加一个点w到集合X中,对应就是选择一条tail v在X中而head w在V-X中的一条边(v,w)。选择这条边的标准是,让距离A[w] = A[v]+ l v w l_{vw} lvw最短。直到遍历了所有的点为止:
在这里插入图片描述
如果图中的边权重非负的条件无法满足则算法不适用,也无法通过将权重同时加一个值来修正。

1.2 算法正确性

算法正确性的命题为:对于问题定义的图中所有的点v,算法得到的A[v]都表示起始点s到达点v的最短路径长度。其中A[s] = 0。

使用归纳法来进行证明。假设对于目前的集合X中的点,得到的结果A对应的都是正确的结果。那么只需要证明下一个加入X的点 w ∗ w^{*} w,得到的结果 A [ w ∗ ] = A [ v ∗ ] + l v ∗ w ∗ A[w^{*}] = A[v^{*}]+l_{v^{*}w^{*}} A[w]=A[v]+lvw也是s到达 w ∗ w^{*} w最短的路径长度。

因此需要证明对于任意一条从s到达 w ∗ w^{*} w的路径,其长度都大于等于 A [ w ∗ ] = A [ v ∗ ] + l v ∗ w ∗ A[w^{*}] = A[v^{*}]+l_{v^{*}w^{*}} A[w]=A[v]+lvw。因为此时s属于集合X而点 w ∗ w^{*} w不属于,因此它们之间的路径必然经过X与V-X之间的某条边,因此可以将路径表示为(s->y->z-> w ∗ w^{*} w),其中 y ∈ X , z ∈ V − X y \in X, z \in V-X yX,zVX
在这里插入图片描述
于是这条路径的长度为 L ( y ) + l y z + L ( z w ∗ ) ≥ L ( y ) + l y z = A [ y ] + l y z L(y)+l_{yz}+L(zw^{*}) \geq L(y)+l_{yz} = A[y] +l_{yz} L(y)+lyz+L(zw)L(y)+lyz=A[y]+lyz ,而dijkstra算法得到的结果正是 A [ y ] + l y z A[y]+l_{yz} A[y]+lyz的最小值。(其中L(y)表示s到y的最短长度,根据归纳法的假设,当前算法得到的X中的点的结果A[y]表示的就是最短路径的长度)。于是算法正确性得证。

1.3 算法的实现与复杂度

如果按照上述思路直接实现,其算法复杂度应该是 Θ ( m n ) \Theta(mn) Θ(mn)的。因为需要遍历n-1个点,而每个点在选择时都需要遍历X与V-X的所有交叉边。

使用堆结构来实现能够提高算法的效率。堆提供插入删除以及得到最小(或最大)节点的操作,能够很好的用在这个算法中。考虑到X与V-X之间的交叉边一直在变化,因此堆中的元素选择V-X中的每个结点(invariant#1)。而堆结构需要每个节点有个key,这里每个结点v的key[v]表示当前已知到达点v的最短路径长度(invariant#2),正无穷表示未知路径。

满足这两点之后在选择下一个节点 w ∗ w^{*} w时就可以直接得到结果。而为了保持invariant#2在 w ∗ w^{*} w在堆中被删除之后依然成立,需要更新所有 w ∗ w^{*} w指向的V-X的结点在堆中的key值。需要使用堆的删除与插入操作。
在这里插入图片描述
因此需要进行的堆操作的数量是 O ( n + m ) O(n+m) O(n+m)级别的,即每次插入删除每个结点一次,而每个结点删除后更新剩余结点key值遍历其出度边,每个边是不重复的。而每个堆操作复杂度是 l o g ( n ) log(n) log(n),因此总的复杂度是 O ( m l o g n ) O(mlogn) O(mlogn)的,与排序相同。

2. 堆

2.1 堆的操作与应用

堆是一个对拥有key的objects的容器。它支持插入( O ( l o g n ) O(logn) O(logn)),删除( O ( l o g n ) O(logn) O(logn)),取最小(最大)值( O ( l o g n ) O(logn) O(logn)),堆化(n batch插入, O ( n ) O(n) O(n))。

它的应用有:

  1. 堆排序。将所有待排序数字插入堆中,然后不断取最小值。
  2. 事件管理。利用堆做一个优先队列,为事件先后排序。
  3. 数字流的中位数。用最大堆保留较小的前一半,用最小堆保留后一半。因此中位数必然有两个堆的顶部得到。每次插入也可以在常数时间达到平衡。
  4. 实现dijkstra算法。

2.2 堆的实现

堆有一些特点。在概念上可以将堆想象为一个树,而且尽量是一个完全树。堆中的每个元素的key都比它的孩子结点的key要小(最小堆)。而树的根节点的key是最小的。

可以使用数组来实现堆。假设index从1开始,那么对于每个结点i,它的父节点必然是i/2(取整)。且孩子结点比然是2i和2i+1。
在这里插入图片描述
插入操作,首先将要插入的值放在最后一层最后面。然后不断地判断其与父节点的大小关系,若其小于父节点,则交换其与父节点的位置(bubble-up)。此过程复杂度为 O ( l o g n ) O(logn) O(logn)

取最小值操作。首先将根节点删除。然后将最后一层最后一个结点放到根节点的位置。判断其与任一孩子结点关系,若大于较小的孩子结点则与其交换位置(bubble-down),同样是 O ( l o g n ) O(logn) O(logn)的。

3. BALANCED BINARY SEARCH TREES

3.1 BST操作

已排序的数组支持如下操作:
在这里插入图片描述
但是一个排序数组是一个静态的结构,不能够支持动态的变化。而一个平衡搜索树能够比排序数组支持更多的操作,且能够支持动态变化的数据。而且其每个操作都相对较快:
在这里插入图片描述
其中也有一些堆与哈希表同样支持的操作。

3.2 Basics

对于一个搜索树来说,每个结点都有一个key,而且每个结点有指向左右孩子结点以及父亲结点的三个指针。同时每个结点的key满足大于左边孩子表示的树中所有结点的key,小于右边所有结点的key。

对于一个普通的搜索树来说,树的高度必然在 l o g 2 n 到 n log_{2}n到n log2nn之间。其搜索操作可以利用key的大于左边小于右边的特性轻松实现,返回对应结点或null。而插入操作与搜索操作类似,不过是先搜索一个不存在的key,然后将对应的null指针指向这个新的key对应的结点。

取最小值操作是从root开始不断取左边孩子结点直到null。取最大值类似。

计算当前结点的前一个结点的predecessor操作:1.若其左子树非空,取左子树中的最大值。2.否则取父亲结点,直到当前结点是父亲结点的右子树为止,返回这个父亲结点。successor操作与其相反。这些操作的复杂度都是 Θ ( l o g h e i g h t ) \Theta(log height) Θ(logheight)
在这里插入图片描述
中序遍历输出一个从小到大排序的值,复杂度O(n)。删除操作。若目标结点无子节点直接删除,若有一个子节点则将其子节点上移至目标节点位置。若目标结点有两个子节点,则先得到目标结点的predecessor(左子树中最大值,最多只有一个孩子),替换目标节点与predecessor的位置,然后删掉predecessor位置的结点。

选择操作,即选择搜索树中第k大的数。首先需要将每个结点加入一个属性,即表示其左右左子树加本身结点一共的结点数量(size(x) = size(y)+size(z)+1,yz是x的子节点)。
在这里插入图片描述
rank操作类似于选择操作的逆操作。也不难实现。

3.3 红黑树

我们直到搜索树中的很多操作复杂度都与高度有关。因此一个尽量平衡(即左右子树高度相差不多)的搜索树是我们想要的。有很多类型的平衡树,这里讲红黑树。

红黑树有4条特点:1.每个点是红色或黑色 2. 根节点是黑色 3. 一条边不能连着两个红色的结点 4. 从root开始到最后的null指针经历的黑色结点个数相同。

只要满足了红黑树的几个要求,那么树的高度就会有保证,即 ≤ 2 l o g 2 ( n + 1 ) \leq 2log_{2}(n+1) 2log2(n+1)。可以证明如下:

  • 首先考虑普通的没有颜色的完全树,假设其有k层,因为其完全,因此节点数为 2 k − 1 2^{k}-1 2k1
  • 因此如果root到null最短路径长度为k,那么树的结点数量 n ≥ 2 k − 1 n \geq 2^{k}-1 n2k1,即 k ≤ l o g 2 ( n + 1 ) k \leq log_{2}(n+1) klog2(n+1)
  • 因此对于一个红黑树,每条root到null最多有 k ≤ l o g 2 ( n + 1 ) k \leq log_{2}(n+1) klog2(n+1)个黑色结点(先不考虑红色)。
  • 从第4个特征知道,每个root-NULL的路径上都有 k ≤ l o g 2 ( n + 1 ) k \leq log_{2}(n+1) klog2(n+1)个黑色结点。
  • 从第3个特征知道,树的高度最多是 2 l o g 2 ( n + 1 ) 2log_{2}(n+1) 2log2(n+1),得证。
3.4 旋转

所有的平衡树中都会有旋转这样的操作。常用的左旋与右旋如图所示:
在这里插入图片描述
在这里插入图片描述

3.5 插入红黑树

一般来说插入一个结点到红黑树大致的思想是先像普通搜索树一样插入,然后通过改变结点的颜色以及旋转操作使其满足红黑树的4个特征。

分为几个步骤:
在这里插入图片描述
其中最后一步分为两种情况:

第一种情况,即w的另一个孩子z也是红色的或者另一个为空,这时需要重新染色来解决:
在这里插入图片描述
将w染红,y和z染黑,则满足了条件4。但是染色之后如果w的父亲是黑色当然就结束了,不过如果w的父亲结点是红色,那么依然需要解决。但是新的问题与原始的x和y同红色相同,这就相当于将一根线的两个红色不断往上推(一直是第一种情况,若上推过程遇到第二种情况,看下面),当推到根节点的时候,只需要将根节点染黑,就得到了一个新的红黑树。

第二种情况,y的父亲结点w的另一个孩子z是黑色的。
在这里插入图片描述
这里需要先利用右旋,将y变成x的右结点。之后再做旋,将w变成x的左结点。之后将y变黑即可。结果如下:
在这里插入图片描述
如果x变成了根节点,那么染黑即可。如果x的父节点是黑则结束,如果是红则对应第一种或第二种继续上推即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值