标题: 算法笔记, 第十日:贪心算法
性别:未知-离线 Maxwell

代王
监管使
枢密直学士
山南西道节度使

Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27
柱国(正二品)
组别 诸侯
级别 征东将军
好贴 4
功绩 1845
帖子 5799
编号 622
注册 2004-7-7


发表于 2005-4-19 08:39 资料 文集 短消息 只看该作者
set是集合吧,不是二叉树。set的存储结构是树,不过恐怕真用到树的地方set是不够的。


顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-19 12:21 资料 短消息 只看该作者
第九日:动态规划
今天开始来点有意思的
分治思想将问题分解为独立的子问题
解决子问题,并将答案合并就可以得到总问题的解
但是,很多情况下,分解开的子问题之间并不是相互独立的
动态规划方法利用了这种相关性,给出了一种通用的解决方案

动态规划可以解决这样一类问题:
1、如果问题的解决是需要分阶段的(可以分解为子问题)
2、每个子问题的解决都依赖于之前阶段子问题的解决,也就是说,存在子子问题,子问题和子子问题之间存在相关性,子问题共享子子问题的解
3、每个子子问题只出现一次,或者说,只有一个答案,存在最优解。这样,我们就可以建一个表,把它们记下来
用空间换取时间,这个道理说出来,大家都能理解
可是真正第一个提出问题并想到这种解决方法的人,无疑是天才

动态规划通常用于解决“最优问题”
这类问题通常存在很多解决方案,每个方案都对应一个值
我们希望求出得到最优(最大、最小)值的那个解决方案

动态规划算法的解决步骤可以采取以下四步
1、刻画最优解的结构
2、以递归的方法求最优解
3、回推,求得最优解的值

举一个矩阵连乘的例子
有一个矩阵序列(A_1,A_2,...,A_n)
如果我们希望计算矩阵的乘积A_1A_2...A_n
矩阵乘法仅定义了两两相乘的规则
一个p*q的矩阵和一个q*r的矩阵相乘,最终获得一个p*r的矩阵
算法如下:
MATRIX-MULTIPLY(A,B )
1 if columns[A] <> rows[B] //只有第一个矩阵的列数和第二个矩阵的行数相同,两个矩阵才可以相乘
2   then error "矩阵不相容"
3   else for i <- 1 to rows[A]
4           do for j <- 1 to columns[B]
5                 do C[i,j] <- 0
6                    for k <- 1 to columns[A]
7                       do C[i,j] <- C[i,j] + A[i,k] * B[k,j]
8        return C

可以看出,为了计算两个矩阵的乘积,一共要进行(p*q*r)次乘法运算
我们的目标是要尽量减少连乘时乘法运算的次数,以加快运算速度

以三个矩阵连乘为例
我们有以下两种种计算最终结果的运算顺序
A_1(A_2A_3)
(A_1A_2)A_3
假设它们分别是10×100,100×5,5×50的矩阵
那么,第一种顺序的乘法次数为:100*5*50+10*100*50 = 25000+50000 = 75000
第二种顺序的乘法次数为: 10*100*5+10*5*50 = 5000+2500 = 7500
差距还是很大di

考虑这个问题的特点,乘法次数与矩阵的内容无关,仅与矩阵的下标相关
我们要做的就是给矩阵加括号,事实上也就是给下标加括号
n个矩阵连乘,加括号的方法一共有
P(n)={
1, if n = 1
sigma(k=1, to n-1, P(k)P(n-k)), if n >= 2
}
将相邻矩阵的重复下标去除后,矩阵序列A的下标序列记为:
p=(p_0,p_1,...,p_n)

解决问题的第一步是找到最优解的结构,也就是如何通过子问题的最优解构建总问题的最优
我们将子问题的最优解记为m[i,j],如果求得了i=1,j=n时得m,问题就解决了
对于n个矩阵连乘,无论如何加括号,最终总是可以分解为两个矩阵的乘法
可以简记为(A_1...A_i)*(A_i+1...A_n)
前面括号里面乘积是一个矩阵,后面也是
那么这样一组括号总的乘法次数为
m[1,i]+m[i+1,n]+p_0*p_i*p_n

然后是递推求解过程
n个矩阵一个有n-1组括号
所以
m[1,n]=
={
0, if n = 1
min{m[1,k]+m[k+1,n]+p_0*p_k*p_n}, if n > 1
}

m[i,j]{
0, if i = j
min{m[i,k]+m[k+1,j]+p_i-1*p_k*p_j}, if i < j
}

通过序列p,可以把每个m[i,j]都计算出来,记录它们的结果
一共需要记录(n*(n+1)/2)个结果

最后,通过这个公式
m[i,i+1]=p_i-1*p_i*p_i+1
就可以得到最终结果了

这样只是得到了最终结果的值,如果我们希望知道括号是如何加的(对连乘计算过程有意义)
还需要在每次计算m[i,j]时,记录k的取值

循环形式的具体算法:
MATRIX-CHAIN-ORDER(p)
1 n <- length[p] - 1
2 for i <- 1 to n
3    do m[i,i] <- 0
4 for l <- 2 to n //l表示链长
5    do for i <- l to n-l+1
6          do j <- i+l-1
7             m[i,j] <- infinite
8             for k <- i to j-1
9                do q <- m[i,k]+m[k+1,j]+p_i-1*p_k*p_j
10                  if q < m[i,j]
11                     then m[i,j] <- q
12                          s[i,j] <- k
13 return m and s
递归形式的算法留给大家自己写一下

这种方法大致运行时间为o(n^3)
空间消耗前面计算过,是n*(n+1)/2


作业:
1、写出递归形式的连乘算法
2、计算下标序列为
(5,10,3,12,5,50,6)
的矩阵连乘所需的最少乘法数
试着通过直觉提出一种算法
如果和你通过以上算法得到的答案一致的话
能不能证明这种直觉方法总是有效的?


顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-19 12:43 资料 短消息 只看该作者


QUOTE:
原帖由van于2005-04-18, 10:52:49发表
直接用STL好了,list和set

STL是c++的标准库吧?
不是每种语言都会定义基本数据结构的
如果我用C、pascal甚至BASIC语言写程序的时候
它们没有提供这种支持
只能通过语言提供的基本数据类型和功能进行组合和扩展
现在的编程工具(并不仅仅是语言本身)
提供了太多的kits,相应的,文档也膨胀了n倍
可是语言本身的功能并没有多大提高
计算机语言的演变只是想让开发者使用起来更轻松
可是事情并不见得如此
搞明白原理才能有备无患啊
顶部
性别:未知-离线 陈珺

Rank: 6Rank: 6Rank: 6
组别 校尉
级别 军师将军
好贴 5
功绩 26
帖子 927
编号 3820
注册 2003-12-27
来自 福建福州


发表于 2005-4-20 19:16 资料 个人空间 短消息 只看该作者
我把我对哈希表理解说说
有一大堆数据,每个数据面前都做个记号,然后将这些记号分类,一种类型给一种名称.需要用的时侯只要把类型名称输入就会输出这些记号.
我的理解对不对呢
顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-21 11:01 资料 短消息 只看该作者
大概差不多吧,呵呵,理解这种东西不好评价
我认为,哈希的关键在于映射关系,可以把这种关系理解为索引
以有限的空间对应较多的数据,提高检索、修改等操作的效率
后面还要提到一种更为极端的数据结构B树
以极为有限的空间对应极多的数据,因此被广泛应用于跟物理存储相关的系统中
比如操作系统、数据库等等
顶部
性别:未知-离线 Maxwell

代王
监管使
枢密直学士
山南西道节度使

Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27
柱国(正二品)
组别 诸侯
级别 征东将军
好贴 4
功绩 1845
帖子 5799
编号 622
注册 2004-7-7


发表于 2005-4-21 13:13 资料 文集 短消息 只看该作者
哈希可以当作这么个过程,有n张卡片m个盒子,为了检索卡片方便,可以用某种方法根据卡片的内容计算出一个编号,然后到指定编号的盒子里去找这张卡片。它快就快在不需要从第一张卡片开始比较,而是根据你要找的内容算出它在哪个盒子里。所以哈希算法的好坏是由计算编号的算法决定的。
顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-21 15:46 资料 短消息 只看该作者
第十日:贪心算法
贪心算法和前面的动态规划一样,都不是具体的算法,而是一类解决问题方法的总称
它们要解决的问题也差不多
都可以分解为子问题,子问题都存在最优解
但是,贪心算法与动态规划的不同在于
贪心算法在解决每个阶段问题的时候,仅考虑当前阶段的最优解
而不考虑“将来”哪些子问题将成为最优解的组成部分,绝不为将来打算
这样,与动态规划相比,贪心算法在存储空间和运算时间上都具有优势
不过,无法保证“贪心”出来的结果一定是最好的

贪心算法的一个著名的例子:
霍夫曼编码(Huffman codes)
霍夫曼编码用于解决这样一个问题:
在通讯中需要传输大量的信息,我们希望通过对通讯内容进行编码使得通讯长度最小,以降低通讯成本
这就需要对通讯内容重新编码
一种基于统计的方法认为,如果我们给出现频率较多的“码”以较短代号,那么就可以有效的降低通讯的长度
Huffman code就是这样一种编码方法,它把固定长度的编码方案,替换为变长的编码方案

在计算机中,最简单的变长编码方案应该是以二进制数字,从小到大对应于出现频率从大到小的“码”
但是,这样在解码,也就是编码的反过程中,会出现二义性
比如,将e->0,a->1,b->01,c->10,d->11
那么,如果需要解码:1001
我们就无法知道,原码到底是aeea还是cb
这样就引出了一个概念:前缀编码
每个编码后的单词,不会是其他单词的前缀
比如以下编码方案:0,10,110,1110...
这样从前向后读编码后的文字,就不会出现二义性了

Huffman code显然考虑到了这一点,它以二叉树的形式提供编码方案
每个叶子节点表示一个编码
以深度表示编码长度
以左右表示0、1

由于叶子节点不存在子节点,所以可以避免成为其他编码的前缀

Huffman code的具体编码方案如下:
HUFFMAN(C )
1 n <- size_of[C]
2 Q <- C
3 for i <- 1 to n-1
4    do allocate a new node z
5       left[z] <- x <- EXTRACT-MIN(Q)
6       right[z] <- y <- EXTRACT-MIN(Q)
7       f[z] <- f[x] + f[y]
8       INSERT(Q,z)
9 return EXTRACT-MIN(Q)
解释一下
以上的伪代码是类C语言风格的,连续的赋值语句从右向左进行
C是待编码字母的集合,EXTRACT-MIN(X)操作从集合X中取f值最小的元素,被取出的元素不再属于集合X
f表示字母出现的频率,f值越小,越不容易在文章中出现

为什么说Huffman编码过程是一个Greedy过程呢?
原因就在于EXTRACT-MIN()操作,
每次该操作都会取出出现频率最小的那个元素,而不考虑取出的元素对整体编码方案是否最优

那么,到底Huffman code是不是一个最优的前缀编码方案呢?
如果不是,那它也不会这么有名了  
那么怎么定义最优呢?
如果一种方案得到的编码,平均长度比其他编码方案都要小,那么它就是最优的
这里的平均是经过f加权的平均值
证明过程就不写了
有兴趣可以自己证明,很简单

最后以一个问题结束,这个问题说明了Greedy不总是正确的
找零钱问题:
如果我们假设一种零钱方案如下:
1分、10分、20分、50分
提出一种贪心方法,使得每次支付得零钱数量最少

如果零钱方案变化为
1、5、8、10
原有方案是否可行?
为什么?
顶部
性别:未知-离线 Maxwell

代王
监管使
枢密直学士
山南西道节度使

Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27
柱国(正二品)
组别 诸侯
级别 征东将军
好贴 4
功绩 1845
帖子 5799
编号 622
注册 2004-7-7


发表于 2005-4-21 17:33 资料 文集 短消息 只看该作者
哈夫曼是贪心算法吗?好像不是吧?哈夫曼不是通过完整统计做出的最优结果吗?有种动态哈夫曼没准可以算贪心算法,不过对哈夫曼是贪心算法表示怀疑。
顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-22 09:27 资料 短消息 只看该作者


QUOTE:
原帖由Maxwell于2005-04-21, 17:33:57发表
哈夫曼是贪心算法吗?好像不是吧?哈夫曼不是通过完整统计做出的最优结果吗?有种动态哈夫曼没准可以算贪心算法,不过对哈夫曼是贪心算法表示怀疑。

Huffman树的构建过程确实是Greedy过程
完整统计只是统计了原始字母出现的频率
算法中所使用的贪婪准则为:从可用的二叉树中选出权重最小的两棵。
类似的还有最小生成树算法等等
Huffman编码最终会产生最优结果
这并不影响它是一个Greedy算法
再比如说背包问题
要求得无限制背包问题的最优解,动态规划是少不了的
甚至没有一个多项式时间的解法
但如果对放入背包的物品作一定的限制
Greedy过程足矣

有专门的关于什么样的问题,采用Greedy算法能够得到最优解的讨论
不过用到了线性代数中拟阵这个概念
就不贴了
顶部
性别:未知-离线 Maxwell

代王
监管使
枢密直学士
山南西道节度使

Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27Rank: 27
柱国(正二品)
组别 诸侯
级别 征东将军
好贴 4
功绩 1845
帖子 5799
编号 622
注册 2004-7-7


发表于 2005-4-22 10:43 资料 文集 短消息 只看该作者
搜索了一下,确实有把哈夫曼当作贪心算法的,不过也有很多不把它当成贪心算法的,这难道是个人看法不同?
我的理解贪心算法只能算那种可能产生不了最优解的算法,哈夫曼本身就是冲着整体最优解去的,只是这个问题正好每一步都是局部最优解,我倒是觉得它有一点分治的意思。
顶部
性别:未知-离线 loranrowe

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 奋威校尉
好贴 1
功绩 6
帖子 143
编号 17767
注册 2004-9-16


发表于 2005-4-22 10:53 资料 短消息 只看该作者


QUOTE:
原帖由Maxwell于2005-04-22, 10:43:39发表
搜索了一下,确实有把哈夫曼当作贪心算法的,不过也有很多不把它当成贪心算法的,这难道是个人看法不同?
我的理解贪心算法只能算那种可能产生不了最优解的算法,哈夫曼本身就是冲着整体最优解去的,只是这个问题正好每一步都是局部最优解,我倒是觉得它有一点分治的意思。

有一种说法是
如果找到一种能够解决现有问题的Greedy算法
就坚决的使用它
不要管它是不是最优的
至于分治
我认为它是所有算法思想的最初来源
顶部

正在浏览此帖的会员 - 共 18 人在线




当前时区 GMT+8, 现在时间是 2024-10-6 13:51
京ICP备2023018092号 轩辕春秋 2003-2023 www.xycq.org.cn

Powered by Discuz! 5.0.0 2001-2006 Comsenz Inc.
Processed in 0.013753 second(s), 8 queries , Gzip enabled

清除 Cookies - 联系我们 - 轩辕春秋 - Archiver - WAP