埃及分数(迭代加深搜索)【转】

24 篇文章 0 订阅
24 篇文章 0 订阅
转载自:忘了 ,哈哈 ,稍作改动。

[初步分析]
埃及分数问题属于数论的一个分支“丢番图方程”,现在的研究已经比较深入了,不过仍有很多问题尚未解决。这个题目虽然看起来很简单,但是我们几乎不可能想到一个纯理论性的方法解决,搜索势在必行。

最基本的搜索方式有深度优先和广度优先。这道题目用深度优先很难出解,因为我们无法预料找到的地一个解的深度,因此我们考虑有广度优先方法。暂且不考虑广度优先是否最好,我们先来考虑算法的具体实现。

1.节点类型:
是一个K元组(a1,a2,...ak), 代表当前解中的分母a1,a2..ak我们暂时令k=6,也就是最多保存6个分母(如果觉得不够自己增加k的值)

2.节点扩展:
按照a1<a2<a3..<ak的顺序扩展。(这个是通常做法,大家都知道吧)。扩展第k层节点的时候,最笨的方法,就是从a[k-1]+1开始枚举a[k],一直到预先确定的最大值。但是这个最大值怎么确定呢?直观的讲,a[k]总不能太大,因为如果a[k]太大,1/a[k]就很小,1/a[k+1] ..
1/a[k+2].. 就更小,那么可能加了好多项,还不够a/b..!@#$!@#$,哎,都怪1/a[k]太小了,没有起到什么作用。
例如:
例如已经扩展到19/45=1/5+????那么如果第二项是1/100, 那么由于19/45-1/5=2/9=0.22222...
那么接下来至少要0.2222/(1/100)=22项加起来才足够2/9, 所以继续扩展下去至少还要扩展22项(天哪!)
加起来才差不多足够a/b。这么多项,不大可能是最优解吧!

[确定搜索算法]
好了,刚才的分析其实是在引导我们进行剪枝,但是广度优先搜索停止于第一个解的发现,找到第一个解之前谁也不感贸然把22项那个“大解”扔掉 - 万一它真的是最优解呢?我们希望在预测深度方面剪枝,但是广度优先搜索并不能让我们大胆的剪,这样的情形,我们通常有三种解决方法:
1.分支定界
如果还能够给节点找到上界,相当于有了较优解,那么我们可以放心的对很多“大得荒唐”的节点放心的剪枝。但是本题的上界不大容易给出(至少我现在的知识只知道一些特殊情况的上界),所以在本题中,这个方法并不实用。
2.启发式搜索
既然22不大可能是最优解,我们不扔它,不过也不急着扩展,总可以了吧。理论比较完善的启发式搜索是A*,不过本题用A*实在没多大必要。
3.迭代加深搜索(Iterative Deepening)
迭代加深搜索对于多数初学者来说是个陌生的名词,不过其实原理非常简单:
也就是说:
for depth:=1 to maxdepth do
  if search then
  begin
    writeln("Best=",depth);
    break;
  end;
明白了吗?

你一定明白了,不过可能会想:这样不是重复搜索了好多东西吗?
是的,是重复搜索,但是它的好处在于:
1.空间开销小
因为每个深度下实际上是一个深度优先搜索,不过深度有限制,而DFS的空间消耗小是众所周知的。本题不是很明显,不过在节点复杂的题目里,用BFS和A*空间会远远不足的。
2.编程方便,利于调试
利用递归,我们甚至不需要自己建栈,而BFS用到的队列和A*用到的堆,二分检索树等是比较麻烦的。
3.时间效率不低
虽然重复搜索,但是大家不难理解,前一次搜索跟后一次相不是微不足到的。
4.利于深度剪枝
在这道题目中,这是迭代加深搜索最大的优势。
比如当前深度是3,那么那个22肯定就不要了。而如果最优解是4,肯定在depth=4的时候就能得到最优解(从而立刻结束搜索),也就是说,22个那个东西每次都没用,这和我们主观的剪枝动机是一致的。
最后,对于不熟悉搜索的程序编制的朋友,这里我写出一个C程序,我尽量写得easy to read,希望大家有所帮助。

[关于代码的改进]
大家注意我的枚举部分的实现。我先把枚举的边界s和t求出来再枚举,而不是每次判断时候需要剪枝,这样的代码要快得多。
s:=b div a;  {再小递归以后就是负数了:-P}
if s<d[k-1]+1 then s:=d[k-1]+1; {搜索顺序}
t:=(depth-k+1)*b div a; {根据当前深度定下的最大值}
if t>maxlongint div b then t:=maxlongint div b;
if found and (t>=answer[depth]) then t:=answer[depth]-1;
再是:
for i:=s to t do
...
另外,最容易出错的一点是,递归的时候,求得的新分数必须约分,否则可能出现算术运算overflow!!
求最大公约数用迭代的方法比递归快。

[程序清单]
C语言(建议用djgpp编译)
/*
   Try These Test Cases:
   27 441,  4 109,  59 211,  101 103,  907 911,  523 547
*/
#include<stdio.h>
#include<math.h>
#include<time.h>
#define MAXDEPTH 10
#define MAX 2147483647L
unsigned long depth;
int found;
unsigned long answer[MAXDEPTH], d[MAXDEPTH];
unsigned long t;
unsigned long node=0;
unsigned long gcd(unsigned long a, unsigned long b)
{
  t=a%b;
  while(t){
    a=b;
    b=t;
    t=a%b;
  }
  return b;
}
/* determine the kth number d[k] */
void search(unsigned long a, unsigned long b, unsigned long k)
{
  unsigned long i,m,s,t;
  if (k==depth+1) return;
  else if (b%a==0 && b/a>d[k-1]){
    d[k]=b/a;
    if (!found || d[k]<answer[k])
      memcpy(answer,d,sizeof(d));
    found = 1;
    return;
  }
  node++;
  s=b/a;
  if (s<=d[k-1]) s=d[k-1]+1;
  t=(depth-k+1)*b/a;
  if (t>MAX/b) t=MAX/b;
  if (found && t>=answer[depth]) t=answer[depth]-1;
  for (i=s; i<=t; i++)
  {
     d[k]=i;
     m=gcd(i*a-b,b*i);
     search((i*a-b)/m,b*i/m,k+1);
  }
}
void main()
{
  unsigned long a,b;
  int i;
  found = 0;
  d[0] = 1;
  scanf("%ld%ld",&a,&b);
  for (depth=1; depth<=MAXDEPTH; depth++){
    search(a,b,1);
    if (found){
      for(i=1;i<=depth;i++) printf("%ld ",answer[i]);
      printf("\n");
      break;
    }
  }
  printf("NodeCount=%ld",node);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值