哈尔滨师范大学
学 年 论 文
题 目 关于贪心算法研究 学 生 *** 指导教师 年 级 2009级
专 业 计算机科学与技术 系 别 计算机科学与技术
学 院 计算机科学与信息工程学院
哈尔滨师范大学
年 月
论 文 提 要
为满足人们对大数据量信息处理的渴望,解决各种实际问题,计算机算法学得到了飞速的发展。设计一个好的求解算法更像是一门艺术而不像是技术。
当一个问题具有最优子结构性质和贪心选择性质时,贪心算法通常会给出一个简单、直观、高效的解法。贪心算法通过一系列的选择来得到一个问题的解。它所作的每一个选择都是在当前状态下具有某种意义的最好选择,即贪心选择;并且每次贪心选择都能将问题化简为一个更小的与原问题具有相同形式的子问题。尽管贪心算法对许多问题不能总是产生整体最优解,但对诸如最短路径问题、最小生成树问题,以及哈夫曼编码问题等具有最优子结构和贪心选择性质的问题却可以获得整体最优解。而且所给出的算法一般比动态规划算法更加简单、直观和高效。
贪心算法设计及其实际应用研究
***
摘 要:在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。从贪心算法的定义可以看出,贪心法并不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其它算法相比具有一定的速度优势。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。本文讲述了贪心算法的含义、基本思路及实现过程,贪心算法的核心、基本性质、特点及其存在的问题。并通过贪心算法的特点举例列出了以往研究过的几个经典问题,对于实际应用中的问题,也希望通过贪心算法的特点来解决。
关键词:贪心算法;哈夫曼编码;最小生成树;多处最优服务次序问题;删数问题
一、贪心算法的基本知识概述
(一)贪心算法的核心
贪心算法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。 贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解) 的一种解题方法。其实,从“贪心策略”一词我们便可以看出,贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解。
(二)贪心算法的理论基础
正如前文所说的那样,贪心策略是最接近人类认知思维的一种解题策略。但是,越是显而易见的方法往往越难以证明。下面我们就来介绍贪心策略的理论—拟阵。 “拟阵”理论是一种能够确定贪心策略何时能够产生最优解的理论,虽然这套理论还很不完善,但在求解最优化问题时发挥着越来越重要的作用。
拟阵M 定义为满足下面3个条件的有序对(S ,I ): (1)S 是非空有限集;
(2)I 是S 的一类具有遗传性质的独立子集族,即若B ∈I ,则B 是S 的独立子集,且B 的任意子集也都是S 的独立子集。空集¢必为I 的成员;
(3)I 满足交换性质,即若A ∈I ,B ∈I 且|A|
定理1.1 拟阵M 中所有极大独立子集具有相同大小。
引理1.1 (拟阵的贪心选择性质)设M=(S ,I )是具有权函数M 的带权拟阵,且S 中元素依权值从大到小排列,又设x ∈S 是S 中第一个使得{x}是独立子集元素,则存在S 的最优子集A 使得x ∈A 。
引理1.2 设M=(S,I) 是拟阵。若S 中元素x 不是空集¢的一个可扩元素,则x 也不可能是S 中任一独立子集A 的可扩展元素。
引理1.3 (拟阵的最优子结构性质)设x 是求带权拟阵M=(S ,I )的最优子集的贪
心算法Greedy 所选择的S 中的第一个元素。那么,原问题可简化为求带权拟阵M'=(S' ,I' )的最优子集问题,其中
S'={y|y∈S 且{x,y}∈I} I'={B|B S-{x}且B ∪{x}∈I}
M' 的权函数是M 的权函数在S' 上的限制(称M' 为M 关于元素x 的收缩)。
定理1.4(带权拟阵贪心算法的正确性)高M=(S ,I )是具有权函数W 的带权拟阵,算法Greedy 返回M 的最优子集。
适宜于用贪心策略来求解的许多问题都可以归结为在加权拟阵中找一个具有最大权值的独立子集的问题,即给定一个加权拟阵M=(S ,I ),若能找出一个独立且具有最大可能权值的子集A ,且A 不被M 中比它更大的独立子集所包含,那么A 为最优子集,也是一个最大的独立子集。我们认为,针对绝大多数的信息学问题,只要它具备了“拟阵”的结构,便可用贪心策略求解。拟阵理论对于我们判断贪心策略是否适用于某一复杂问题是十分有效的。
二、贪心算法的C++实现
(一) 哈夫曼算法的实现 哈夫曼算法思路为:
(1)以n 个字母为结点构成n 棵仅含一个点的二叉树集合,字母的频率即为结点的权。 (2)每次从二叉树集合中找出两个权最小者合并为一棵二叉树:
增加一个根结点将这两棵树作为左右子树。新树的权为两棵子树的权之和。 (3)反复进行步骤(2)直到只剩一棵树为止。如图2-1所示。
图2-1 哈夫曼树
哈夫曼树的算法: template
BinaryTreeHuffmanTree(T f[], int n) {//根据权f[1:n]构造霍夫曼树 //创建一个单节点树的数组
Huffman *W=newHuffman [n+1]; BinaryTree z,zero ; for(int i=1;i
z.MakeTree(i, zero , zero) ; W[i].weight=f[i]; W[i].tree=z; }
//数组变成—个最小堆
MinHeap>Q(1); Q.Initialize(w,n ,n) ; //将堆中的树不断合并 Huffman x,y for(i=1;i
z.MakeTree(0, x.tree, y.tree); x.weight+=y.weight;x.tree=z Q.Insert(x); }
Q. DeleteMin(x);//最后的树 Q. Deactivate(); delete[]w; return x.tree; 算法分析
HuffmanTree 初始化优先队列Q 需要O(n)。DeleteMin 和Insert 需O(logn)。n-1次的合并总共需要O(nlogn)。所以n 个字符的哈夫曼算法的计算时间为O(nlogn)。 (二) 单源最短路径问题
问题:给定带权有向图G=(V,E) ,其中每条边的权是一个非负实数。要计算从V 的一点v0(源) 到所有其他各顶点的最短路长度。路长指路上各边权之和。
算法思路(Dijkstra):设最短路长已知的终点集合为S ,初始时v0∈S ,其最短路长为0,然后用贪心选择逐步扩充S :每次在V-S 中,选择路径长度值最小的一条最短路径的终点x 加入S 。
构造路长最短的最短路径:设已构造i 条最短路径,则下一个加入S 的终点u 必是V-S 中具有最小路径长度的终点,其长度或者是弧(v0,u) ,或者是中间只经过S 中的顶点而最后到达顶点u 的路径。
算法描述:
(1)用带权的邻接矩阵c 来表示带权有向图,c[i][j]表示弧上的权值。若∈V ,则置c[i][j]为∞。设S 为已知最短路径的终点的集合,它的初始状态为空集。从源点v 到图上其余各点vi 的当前最短路径长度的初值为:dist[i]=c[v][i] vi∈V
(2)选择vj ,使得dist[j]=Min{dist[i]|vi∈V-S},vj 就是长度最短的最短路径的终点。令S=SU{j}。
(3)修改从v 到集合V-S 上任一顶点vk 的当前最短路径长度:如果dist[j]+c[j][k]
(4)重复操作(2),(3)共n-1次。
voidDijkstra(int n, intv,Type dist[], int prev[], Type **c) { bool s[maxint]; for (int i=1;i
if(dist[i]= =maxint) prev[i]=0 else prev[i]=v ; }
dist[v]=0;s[v]=true; for (int i=1;i
for (int j= 1;j
temp=dist[j];} s[u]=true;
for (int j=1;j
算法分析
用带权邻接矩阵表示有n 个顶点和e 条边的带权有向图,主循环体需要O(n)时间,循环需要执行n-1次,所以完成循环需要O(n2)。 (三)删数问题
删数算法的思路为: (1)x1
(2)如果xk
(3)对N1而言,即删去了1位数后,原问题T 变成了需对n-1位数删去k-1个数新问题T ’。
(4)新问题和原问题相同,只是问题规模由n 减小为n-1,删去的数字个数由k 减少为k-1。
(5)基于此种删除策略,对新问题T ’,选择最近下降点的数进行删除,如此进行下去,直至删去k 个数为止。
算法分析:时间复杂性为O(n),空间复杂性为O(n)。 (四) 会场安排问题
通过第八章对会场安排问题的分析,可以得出解会场安排问题的思路为: (1)n 个活动开始和结束时间分别是s[i]和f[i],而且f[1]
(2)把n 个活动时间看做直线上n 个半闭区间,s[i]和f[i]和组成2n 个有序数组。Count
用于会场数使用的最大数,遍历数组,统计区间的最大的重叠数目。遇到s[i],一种活动进栈(相当于要安排一个会场),比较当前的会场使用数是否是最大。遇到f[i],一种活动出栈(相当于一个会场用完,可以作为其他活动用),直到遇到最后一个活动的开始时间,把所有的活动都安排好,结束遍历。
注:这里的stack 只是作为统计最大的重叠数目用,如果要真的模拟使用情况,用queue 模拟响应的活动安排好。
算法复杂度为O (nlogn )。
三、程序编码与程序调试
具体应用核心代码见附录,调试结果见图3-1、图3-2、图3-3、图3-4所示。
图3-1哈夫曼编码调试后的结果
图3-2单源最短路径问题的调试
图3-3 删数问题的调试
图3-4 会场安排问题的调试
四、总结
贪心算法是很常见的算法,贪心策略是最接近人的日常思维的一种解题策略,虽然它不能保证求得的最后解一定是最佳的,但是它可以为某些问题确定一个可行性范围,因此贪心策略在各级各类信息学竞赛,尤其在对NPC 类问题的求解中发挥着越来越重要的作用。对于一个问题的最优解只能用穷举法得到时,用贪心算法是寻找问题最优解的较好算法。总之,充分利用贪心算法的优势,从局部最优出发,构造贪心策略比较容易,且简单易行。
参考文献:
[1] 严蔚敏, 吴伟民. 数据结构(c语言版)[M].北京:清华大学出版社,1997.
[2] 王晓东. 计算机算法设计与分析(第3版)[M].北京:电子工业出版社,2007. [3] 王晓东. 计算算法设计与分析(第二版)[M].北京:电子工业出版社,2OO4. [4] 王晓东. 算法设计与分析[M].北京:清华大学出版社,2O04.
学年论文(设计)成绩表
哈尔滨师范大学
学 年 论 文
题 目 关于贪心算法研究 学 生 *** 指导教师 年 级 2009级
专 业 计算机科学与技术 系 别 计算机科学与技术
学 院 计算机科学与信息工程学院
哈尔滨师范大学
年 月
论 文 提 要
为满足人们对大数据量信息处理的渴望,解决各种实际问题,计算机算法学得到了飞速的发展。设计一个好的求解算法更像是一门艺术而不像是技术。
当一个问题具有最优子结构性质和贪心选择性质时,贪心算法通常会给出一个简单、直观、高效的解法。贪心算法通过一系列的选择来得到一个问题的解。它所作的每一个选择都是在当前状态下具有某种意义的最好选择,即贪心选择;并且每次贪心选择都能将问题化简为一个更小的与原问题具有相同形式的子问题。尽管贪心算法对许多问题不能总是产生整体最优解,但对诸如最短路径问题、最小生成树问题,以及哈夫曼编码问题等具有最优子结构和贪心选择性质的问题却可以获得整体最优解。而且所给出的算法一般比动态规划算法更加简单、直观和高效。
贪心算法设计及其实际应用研究
***
摘 要:在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。从贪心算法的定义可以看出,贪心法并不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。贪心算法所作的选择可以依赖于以往所作过的选择,但决不依赖于将来的选择,也不依赖于子问题的解,因此贪心算法与其它算法相比具有一定的速度优势。如果一个问题可以同时用几种方法解决,贪心算法应该是最好的选择之一。本文讲述了贪心算法的含义、基本思路及实现过程,贪心算法的核心、基本性质、特点及其存在的问题。并通过贪心算法的特点举例列出了以往研究过的几个经典问题,对于实际应用中的问题,也希望通过贪心算法的特点来解决。
关键词:贪心算法;哈夫曼编码;最小生成树;多处最优服务次序问题;删数问题
一、贪心算法的基本知识概述
(一)贪心算法的核心
贪心算法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。 贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解) 的一种解题方法。其实,从“贪心策略”一词我们便可以看出,贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解。
(二)贪心算法的理论基础
正如前文所说的那样,贪心策略是最接近人类认知思维的一种解题策略。但是,越是显而易见的方法往往越难以证明。下面我们就来介绍贪心策略的理论—拟阵。 “拟阵”理论是一种能够确定贪心策略何时能够产生最优解的理论,虽然这套理论还很不完善,但在求解最优化问题时发挥着越来越重要的作用。
拟阵M 定义为满足下面3个条件的有序对(S ,I ): (1)S 是非空有限集;
(2)I 是S 的一类具有遗传性质的独立子集族,即若B ∈I ,则B 是S 的独立子集,且B 的任意子集也都是S 的独立子集。空集¢必为I 的成员;
(3)I 满足交换性质,即若A ∈I ,B ∈I 且|A|
定理1.1 拟阵M 中所有极大独立子集具有相同大小。
引理1.1 (拟阵的贪心选择性质)设M=(S ,I )是具有权函数M 的带权拟阵,且S 中元素依权值从大到小排列,又设x ∈S 是S 中第一个使得{x}是独立子集元素,则存在S 的最优子集A 使得x ∈A 。
引理1.2 设M=(S,I) 是拟阵。若S 中元素x 不是空集¢的一个可扩元素,则x 也不可能是S 中任一独立子集A 的可扩展元素。
引理1.3 (拟阵的最优子结构性质)设x 是求带权拟阵M=(S ,I )的最优子集的贪
心算法Greedy 所选择的S 中的第一个元素。那么,原问题可简化为求带权拟阵M'=(S' ,I' )的最优子集问题,其中
S'={y|y∈S 且{x,y}∈I} I'={B|B S-{x}且B ∪{x}∈I}
M' 的权函数是M 的权函数在S' 上的限制(称M' 为M 关于元素x 的收缩)。
定理1.4(带权拟阵贪心算法的正确性)高M=(S ,I )是具有权函数W 的带权拟阵,算法Greedy 返回M 的最优子集。
适宜于用贪心策略来求解的许多问题都可以归结为在加权拟阵中找一个具有最大权值的独立子集的问题,即给定一个加权拟阵M=(S ,I ),若能找出一个独立且具有最大可能权值的子集A ,且A 不被M 中比它更大的独立子集所包含,那么A 为最优子集,也是一个最大的独立子集。我们认为,针对绝大多数的信息学问题,只要它具备了“拟阵”的结构,便可用贪心策略求解。拟阵理论对于我们判断贪心策略是否适用于某一复杂问题是十分有效的。
二、贪心算法的C++实现
(一) 哈夫曼算法的实现 哈夫曼算法思路为:
(1)以n 个字母为结点构成n 棵仅含一个点的二叉树集合,字母的频率即为结点的权。 (2)每次从二叉树集合中找出两个权最小者合并为一棵二叉树:
增加一个根结点将这两棵树作为左右子树。新树的权为两棵子树的权之和。 (3)反复进行步骤(2)直到只剩一棵树为止。如图2-1所示。
图2-1 哈夫曼树
哈夫曼树的算法: template
BinaryTreeHuffmanTree(T f[], int n) {//根据权f[1:n]构造霍夫曼树 //创建一个单节点树的数组
Huffman *W=newHuffman [n+1]; BinaryTree z,zero ; for(int i=1;i
z.MakeTree(i, zero , zero) ; W[i].weight=f[i]; W[i].tree=z; }
//数组变成—个最小堆
MinHeap>Q(1); Q.Initialize(w,n ,n) ; //将堆中的树不断合并 Huffman x,y for(i=1;i
z.MakeTree(0, x.tree, y.tree); x.weight+=y.weight;x.tree=z Q.Insert(x); }
Q. DeleteMin(x);//最后的树 Q. Deactivate(); delete[]w; return x.tree; 算法分析
HuffmanTree 初始化优先队列Q 需要O(n)。DeleteMin 和Insert 需O(logn)。n-1次的合并总共需要O(nlogn)。所以n 个字符的哈夫曼算法的计算时间为O(nlogn)。 (二) 单源最短路径问题
问题:给定带权有向图G=(V,E) ,其中每条边的权是一个非负实数。要计算从V 的一点v0(源) 到所有其他各顶点的最短路长度。路长指路上各边权之和。
算法思路(Dijkstra):设最短路长已知的终点集合为S ,初始时v0∈S ,其最短路长为0,然后用贪心选择逐步扩充S :每次在V-S 中,选择路径长度值最小的一条最短路径的终点x 加入S 。
构造路长最短的最短路径:设已构造i 条最短路径,则下一个加入S 的终点u 必是V-S 中具有最小路径长度的终点,其长度或者是弧(v0,u) ,或者是中间只经过S 中的顶点而最后到达顶点u 的路径。
算法描述:
(1)用带权的邻接矩阵c 来表示带权有向图,c[i][j]表示弧上的权值。若∈V ,则置c[i][j]为∞。设S 为已知最短路径的终点的集合,它的初始状态为空集。从源点v 到图上其余各点vi 的当前最短路径长度的初值为:dist[i]=c[v][i] vi∈V
(2)选择vj ,使得dist[j]=Min{dist[i]|vi∈V-S},vj 就是长度最短的最短路径的终点。令S=SU{j}。
(3)修改从v 到集合V-S 上任一顶点vk 的当前最短路径长度:如果dist[j]+c[j][k]
(4)重复操作(2),(3)共n-1次。
voidDijkstra(int n, intv,Type dist[], int prev[], Type **c) { bool s[maxint]; for (int i=1;i
if(dist[i]= =maxint) prev[i]=0 else prev[i]=v ; }
dist[v]=0;s[v]=true; for (int i=1;i
for (int j= 1;j
temp=dist[j];} s[u]=true;
for (int j=1;j
算法分析
用带权邻接矩阵表示有n 个顶点和e 条边的带权有向图,主循环体需要O(n)时间,循环需要执行n-1次,所以完成循环需要O(n2)。 (三)删数问题
删数算法的思路为: (1)x1
(2)如果xk
(3)对N1而言,即删去了1位数后,原问题T 变成了需对n-1位数删去k-1个数新问题T ’。
(4)新问题和原问题相同,只是问题规模由n 减小为n-1,删去的数字个数由k 减少为k-1。
(5)基于此种删除策略,对新问题T ’,选择最近下降点的数进行删除,如此进行下去,直至删去k 个数为止。
算法分析:时间复杂性为O(n),空间复杂性为O(n)。 (四) 会场安排问题
通过第八章对会场安排问题的分析,可以得出解会场安排问题的思路为: (1)n 个活动开始和结束时间分别是s[i]和f[i],而且f[1]
(2)把n 个活动时间看做直线上n 个半闭区间,s[i]和f[i]和组成2n 个有序数组。Count
用于会场数使用的最大数,遍历数组,统计区间的最大的重叠数目。遇到s[i],一种活动进栈(相当于要安排一个会场),比较当前的会场使用数是否是最大。遇到f[i],一种活动出栈(相当于一个会场用完,可以作为其他活动用),直到遇到最后一个活动的开始时间,把所有的活动都安排好,结束遍历。
注:这里的stack 只是作为统计最大的重叠数目用,如果要真的模拟使用情况,用queue 模拟响应的活动安排好。
算法复杂度为O (nlogn )。
三、程序编码与程序调试
具体应用核心代码见附录,调试结果见图3-1、图3-2、图3-3、图3-4所示。
图3-1哈夫曼编码调试后的结果
图3-2单源最短路径问题的调试
图3-3 删数问题的调试
图3-4 会场安排问题的调试
四、总结
贪心算法是很常见的算法,贪心策略是最接近人的日常思维的一种解题策略,虽然它不能保证求得的最后解一定是最佳的,但是它可以为某些问题确定一个可行性范围,因此贪心策略在各级各类信息学竞赛,尤其在对NPC 类问题的求解中发挥着越来越重要的作用。对于一个问题的最优解只能用穷举法得到时,用贪心算法是寻找问题最优解的较好算法。总之,充分利用贪心算法的优势,从局部最优出发,构造贪心策略比较容易,且简单易行。
参考文献:
[1] 严蔚敏, 吴伟民. 数据结构(c语言版)[M].北京:清华大学出版社,1997.
[2] 王晓东. 计算机算法设计与分析(第3版)[M].北京:电子工业出版社,2007. [3] 王晓东. 计算算法设计与分析(第二版)[M].北京:电子工业出版社,2OO4. [4] 王晓东. 算法设计与分析[M].北京:清华大学出版社,2O04.
学年论文(设计)成绩表