贪心算法详解:
一、算法思想
贪心法的基本思路:
--从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。
该算法存在问题:
1. 不能保证求得的最后解是最佳的;
2. 不能用来求最大或最小解问题;
3. 只能求满足某些约束条件的可行解的范围。
实现该算法的过程:
从问题的某一初始解出发;
while 能朝给定总目标前进一步 do
求出可行解的一个解元素;
由所有解元素组合成问题的一个可行解;
二、例题分析
1、[背包问题]有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A B C D E F G
重量 35 30 60 50 40 10 25
价值 10 40 30 50 35 40 30
分析:
目标函数: ∑pi最大
约束条件是装入的物品总重量不超过背包容量:∑wi <=M( M=150)
(1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占空间最小的物品装入是否能得到最优解?
(3)每次选取单位容量价值最大的物品,成为解本题的策略。 ?
马踏棋盘的贪心算法
123041-23 XX
【问题描述】
马的遍历问题。在8×8方格的棋盘上,从任意指定方格出发,为马寻找一条走遍棋盘每一格并且只经过一次的一条路径。
【初步设计】
首先这是一个搜索问题,运用深度优先搜索进行求解。算法如下:
1、 输入初始位置坐标x,y;
2、 步骤 c:
如果c> 64输出一个解,返回上一步骤c--
(x,y) ← c
计算(x,y)的八个方位的子结点,选出那此可行的子结点
循环遍历所有可行子结点,步骤c++重复2
显然(2)是一个递归调用的过程,大致如下:
void dfs(int x,int y,int count)
{
int i,tx,ty;
if(count> N*N)
{
output_solution();//输入一个解
return;
}
for(I=0;i <8;++i)
{
tx=hn[i].x;//hn[]保存八个方位子结点
ty=hn[i].y;
s[tx][ty]=count;
dfs(tx,ty,count+1);//递归调用
s[tx][ty]=0;
}
}
这样做是完全可行的,它输入的是全部解,但是马遍历当8×8时解是非常之多的,用天文数字形容也不为过,这样一来求解的过程就非常慢,并且出一个解也非常慢。
怎么才能快速地得到部分解呢?
【贪心算法】
其实马踏棋盘的问题很早就有人提出,且早在1823年,J.C.Warnsdorff就提出了一个有名的算法。在每个结点对其子结点进行选取时,优先选择‘出口’最小的进行搜索,‘出口’的意思是在这些子结点中它们的可行子结点的个数,也就是‘孙子’结点越少的越优先跳,为什么要这样选取,这是一种局部调整最优的做法,如果优先选择出口多的子结点,那出口少的子结点就会越来越多,很可能出现‘死’结点(顾名思义就是没有出口又没有跳过的结点),这样对下面的搜索纯粹是徒劳,这样会浪费很多无用的时间,反过来如果每次都优先选择出口少的结点跳,那出口少的结点就会越来越少,这样跳成功的机会就更大一些。这种算法称为为贪心算法,也叫贪婪算法或启发示算法,它对整个求解过程的局部做最优调整,它只适用于求较优解或者部分解,而不能求最优解。这样的调整方法叫贪心策略,至于什么问题需要什么样的贪心策略是不确定的,具体问题具体分析。实验可以证明马遍历问题在运用到了上面的贪心策略之后求解速率有非常明显的提高,如果只要求出一个解甚至不用回溯就可以完成,因为在这个算法提出的时候世界上还没有计算机,这种方法完全可以用手工求出解来,其效率可想而知。
在前面的算法基础之上,增添一些程序加以实现:
函数1:计算结点出口多少
int ways_out(int x,int y)
{
int i,count=0,tx,ty;
if(x <0||y <0||x> =N||y> =N||s[x][y]> 0)
return -1;//-1表示该结点非法或者已经跳过了
for(i=0;i <8;++i)
{
tx=x+dx[i];
ty=y+dy[i];
if(tx <0||ty <0||tx> =N||ty> =N)
continue;
if(s[tx][ty]==0)
++count;
}
return count;
}
函数2:按结点出口进行排序
void sortnode(h_node *hn,int n)//采用简单排序法,因为子结点数最多只有8
{
int i,j,t;
h_node temp;
for(i=0;i <n;++i)
{
for(t=i,j=i+1;j <n;++j)
if(hn[j].waysout <hn[t].waysout)
t=j;
if(t> i)
{
temp=hn[i];
hn[i]=hn[t];
hn[t]=temp;
}
}
}
函数3:修改后的搜索函数
void dfs(int x,int y,int count)
{
int i,tx,ty;
h_node hn[8];
if(count> N*N)
{
output_solution();
return;
}
for(i=0;i <8;++i)//求子结点和出口
{
hn[i].x=tx=x+dx[i];
hn[i].y=ty=y+dy[i];
hn[i].waysout=ways_out(tx,ty);
}
sortnode(hn,8);
for(i=0;hn[i].waysout <0;++i);//不考虑无用结点
for(;i <8;++i)
{
tx=hn[i].x;
ty=hn[i].y;
s[tx][ty]=count;
dfs(tx,ty,count+1);
s[tx][ty]=0;
}
}
函数4:主调函数
void main()
{
int i,j,x,y;
for(i=0;i <N;++i)//初始化
for(j=0;j <N;++j)
s[i][j]=0;
printf( "Horse jump while N=%d/nInput the position to start: ",N);
scanf( "%d%d ",&x,&y);//输入初始位置
while(x <0||y <0||x> =N||y> =N)
{
printf( "Error! x,y should be in 0~%d ",N-1);
scanf( "%d%d ",&x,&y);
}
s[x][y]=1;
dfs(x,y,2);//开始搜索
}
http://blog.csdn.net/cfreez/archive/2007/06/27/1668543.aspx
普遍意义的背包是求总价值最大,这里我只讨论能背的总重量最大。
为了便于理解,我通过一个具体的例子来说明
有物品重量为{16,13,9,6,7,1,3,18} 背包能装的最大重量为51
(1)先进行升序排列{1,3,6,7,9,13,16,18}
(2)求得相邻数的差 2 3 1 2 4 3 2
(3)对相邻数差分组并将得到的数按升序排列,于是有下结果
差为1 6-7
差为2 1-3 ,7-9, 16-18
差为3 3-6,13-16
差为4 9-13
(4)从最大的差组最大的数往下加,如果大于总重则跳过,直到到达最小值为止,并计算出其差值
得到数列为 13,9,16,6,3,18,7,1
13+9 +16+6+3+1=48 51-48=3差值为3
(5)找到小于或等于差值的组,从差最大的组找起,找到前缀在其中的数,并置换
在差为3组中找到13-16,3-6 但是16,6已在其中,于是往下找
在差为2组中找到16-18,替换
13 + 9 +18 + 6 +3 +1 =50 51-50=1
在差为1组中找到6-7,替换
13 + 9 +18 + 7 +3 +1 =50
结束
这种算法我称为“序差置换法”
该文转至:http://topic.csdn.net/u/20070816/23/43fc5a00-301d-444c-9c52-f3eee8696be6.html