算法设计与分析基础(十三):贪心法
逐步给出解的各部分,在每一步“贪婪地” 选择最好的部分解,但不顾及这样选择对整体的影响,因此一般得到的不是最优解。但对许多问题它能产生整体最优解。如单源最短路经问题,最小生成树问题等。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。
最小生成树的唯一性:
如果加权连通图的每一个权重都是唯一的,它的最小生成树也是唯一的。
Prim算法
算法思想:以图上的顶点为出发点,逐次选择最小生成树的顶点,策略为每次总是选择 距离最小生成树顶点集最近的顶点。
算法描述:
无向连通带权图G = (V,E,W)使得W(T)最小的生成树T
基本思想:
设G=(V, E)是连通带权图,V={1, 2, …, n}。
首先置 S={1}
然后,只要 S 是 V 的真子集,就作如下的贪心选择:选取满足条件 i∈S,j∈V-S,且 i 与 j 之间的最小边
将顶点j 添加到S中。
这个过程一直进行到 S=V 时为止。在这个过程中选取到的所有边恰好构成G的一棵最小生成树。
效率分析:
取决于表示图的数据结构,以及表示V-S相连的边的优先队列的数据结构
可用的数据结构: 图–权重矩阵;优先队列—无序数组
运行时间属于Θ(|V|2)
图—邻接链表;优先队列—最小堆:
运行时间O((|E|+|V|)log|V|)
Kruskal 最小生成树算法
算法思想:以图上的边为出发点逐次选择最小生成树的边,贪心策略的原则为,所选的当前最小边与已有的边不构成回路。
图示:
算法描述:
两种算法的对比
运行时间:
若采用高效的无交集和的并、查找算法 则kruskal算法的运行时间取决于对图中边的权重排序的时间O(|E|log|E|)。
- Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。
- Prim_heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。
贪心算法与动态规划算法的差异
-
动态规划算法通常以自底向上的方式解各子问题
-
而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问
题简化为规模更小的子问题。
单源最短路径-- Dijkstra算法
给定带权图G =(V,E),其中每条边的权是非负实数。给定V中的一个顶点,称为源。
目标:计算从 源 到所有其他各顶点的最短路径长度。这里路径的长度是指路径上各边权之和。这个问题通常称为单源最短路径问题。
适用条件&范围:
- 单源最短路径(从源点s到其它所有顶点v);
- 有向图&无向图 (无向图可以看作(u,v), (v,u)同属于边集E的有向图)
- 所有边权非负(任取(i,j)∈E都有Wij≥0);
算法基本思想:
-
设置顶点集合 S 并不断地作贪心选择来扩充这个集合。
-
一个顶点属于集合 S 当且仅当从源到该顶点的最短路径长度已知。初始时,S 中仅含有源。
-
设 u 是 G 的某一个顶点,把从 源 到 u 且中间只经过S中顶点的路称为从源到 u 的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。
-
Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。
-
一旦S包含了所有V中顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。
实现步骤:
- 选中指定的起始点,列出此顶点到其他的顶点的权值,不相邻的为无穷大
- 从以上权值中选出最小值,此最小值就是起始点到对应顶点的最短路径,并标记这个对应顶点
- 将起始点到其他未标记的顶点的直接距离与起始点到刚才标记顶点加上标记顶点到其他顶点距离的和比较,如果后者小,则更新对应的权值。
- 重复2、3步骤, 直到求出所有点的最短距离 。
例子:
算法要点:
- 在集合S 外选定一个节点,它是距离源点最近的节点,选点的方法要考虑S中每个点延伸出的结点
- 一个节点一旦被确定是离源点为当前最近的点, 该点被加入集合 S
- 该点一旦被加入集合S,算法将马上更新由该点导致的其他节点的当前最短距离
哈夫曼编码
编码要求
出现频率高的字符:编码短;出现频率低的字符:编码长
前缀码:所有的比特串都不是另一个字符比特串的前缀
**存储空间比较:**假设各字符出现次数:A 30次,B 25次, C 20次,D 10次,E 10次,F 5次
编码后的平均长度:
编码长度 x 出现次数 之和
定长编码:330+325+320+310+310+35=300 bit
字符 | 编码 |
---|---|
A | 000 |
B | 001 |
C | 010 |
D | 011 |
E | 100 |
F | 101 |
变长编码:230+225+220+310+410+45=240 bit
字符 | 编码 |
---|---|
A | 00 |
B | 01 |
C | 10 |
D | 110 |
E | 1110 |
F | 1111 |
哈夫曼编码算法用字符在文件中出现的频率表来建立一个用0,1串表示各字符的最优表示方式。
考虑将字符和二叉树的叶子联系起来形成前缀码
- 关键字位于叶子节点,且关键字具有出现频率
- 使对这些关键字编码后的平均长度最小
- 让概率小的先编码(先编码的,码的长度 较长)
编码过程:
-
初始化n个字符单节点的树,每个字符具有概率,记为权重
-
重复下面的步骤直到剩下一棵单独的树
找到两个权重最小的树,把他们作为新树中的左右子树。并把其权重之和作为新的权重,记录在新树的根中。
-
左子树边标0,右子树边标1,从根节点到叶子节点的路径就是哈夫曼编码
实例:
结果不唯一!