算法设计与分析——第二章_递推算法

递归算法

¢程序直接或间接调用自身的编程技巧称为递归算法
(Recursion)。

¢一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

¢递归需要有边界条件、递归前进段和递归返回段

l边界条件不满足时,递归前进
l边界条件满足时,递归返回
l注意:在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口,否则将无限进行下去(死锁)
¢递归的缺点:
l递归算法解题的运行效率较低

递归调用过程中,系统为每一层的返回点、局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等

¢3.2  Fibonacci数列的递推算法

int  fib[50];  //采用数组保存中间结果

void fibonacci(int n)

{

  fib[0] = 1; 

  fib[1] = 1;

  for (int i=2; i<=n; i++)

  fib[i] = fib[i-1]+fib[i-2];

}

集合的全排列问题

算法设计与分析——第二章_递推算法

//产生从元素km的全排列,作为前k1个元素的后缀

void Perm(int list[], int k, int m)

{

  //构成了一次全排列,输出结果

  if(k==m)

  {

  for(int i=0;i<=m;i++)

  cout<<list[i]<<" ";

  cout<<endl;

  }

  else

  //在数组list中,产生从元素km的全排列

  for(int j=k;j<=m;j++)

  {

  swap(list[k],list[j]);

  Perm(list,k+1,m);

  swap(list[k],list[j]);

  }

}

//产生从元素km的全排列,作为前k1个元素的后缀

void Perm(int list[], int k, int m)

{

  if(k==m)   //构成了一次全排列,输出结果

  {

  for(int i=0;i<=m;i++)

  cout<<list[i]<<" ";

  cout<<endl;

  }

  else

  //在数组list中,产生从元素km的全排列

  for(int j=k;j<=m;j++)

  {

  swap(list[k],list[j]);

  Perm(list,k+1,m);

  swap(list[k],list[j]);

  }

}

整数划分问题

¢整数划分问题是算法中的一个经典命题之一。把一个正整数n表示成一系列正整数之和:
¢
¢正整数n的这种表示称为正整数n的划分。正整数n的不同划分个数称为正整数n的划分数,记作      
l正整数6有如下11种不同的划分,所以               

6

5+1

4+2, 4+1+1

3+3, 3+2+1, 3+1+1+1

2+2+2, 2+2+1+1, 2+1+1+1+1

1+1+1+1+1+1

算法设计与分析——第二章_递推算法

算法设计与分析——第二章_递推算法

分治策略

¢分治策略是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。
¢递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

 

¢分治法在每一层递归上都有三个步骤:
1.分解将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
2.解决若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
3.合并将各个子问题的解合并为原问题的解。
¢分治法所能解决的问题一般具有以下几个特征:
1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

二分搜索技术

¢给定n个元素a[0n1],需要在这n个元素中找出一个特定元素x
l首先对n个元素进行排序,可以使用C++标准模板库函数sort()
l比较容易想到的是用顺序搜索方法,逐个比较a[0n1]中的元素,直至找到元素x或搜索遍整个数组后确定x不在其中。
l因此在最坏的情况下,顺序搜索方法需要 O(n)次比较。
l二分搜索技术充分利用了n个元素已排好序的条件,采用分治策略的思想,在最坏情况下用O(log  n) 时间完成搜索任务。
¢二分搜索算法的基本思想是将n个元素分成个数大致相同的两半,取a[n/2]x作比较。
l如果xa[n/2],则找到x,算法终止。
l如果xa[n/2],则我们只要在数组a的左半部分继续搜索x
l如果xa[n/2],则我们只要在数组a的右半部分继续搜索x

3.6  二分搜索算法

//数组a[]中有n个元素,已经按升序排序,待查找的元素x

template<class Type>

int BinarySearch(Type a[],const Type& x,int n)

{

  int left=0;  //左边界

  int right=n-1;  //右边界

  while(left<=right)

  {

  int middle=(left+right)/2;  //中点

  if (x==a[middle]) return middle;

   if (x>a[middle]) left=middle+1;

  else right=middle-1;

  }  return -1;  //未找到x

}

循环赛日程表

¢问题描述:设有n2k个运动员要进行网球循环赛。
¢现要设计一个满足以下要求的比赛日程表:
1.每个选手必须与其他n1个选手各赛一次;
2.每个选手一天只能参赛一次;
3.循环赛n1天内结束。
l请按此要求将比赛日程表设计成有n行和n1列的一个表
l表中的第i行,第j列处填入第i个选手在第j天所遇到的选手,其中1in1jn1

 

void Table(int k)

  int i, r;

  int n = 1 << k; 

  //构造正方形表格的第一行数据

  for (i=0; i<n; i++)

  a[0][i] = i + 1;

  //采用分治算法,构造整个循环赛日程表

  for (r=1; r<n; r<<=1)

  for (i=0; i<n; i+=2*r)

  {

  Copy(r, r + i, 0, i, r);  //

  Copy(r, i, 0, r + i, r);  //

  }

}

实现方阵的拷贝

//源方阵的左上角顶点坐标(fromx, fromy),行列数为r

//目标方阵的左上角顶点坐标(tox, toy),行列数为r

void Copy(int tox, int toy, int fromx, int fromy, int r)

{

  for (int i=0; i<r; i++)

  for (int j=0; j<r; j++) 

  a[tox+i][toy+j] = a[fromx+i][fromy+j];

}

输油管道问题

¢某石油公司计划建造一条由东向西的主输油管道。该管道要穿过一个有n口油井的油田。从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连。
¢如果给定n口油井的位置,即它们的x坐标(东西向)和y坐标(南北向),应如何确定主管道的最优位置,即使各油井到主管道之间的输油管道长度总和最小的位置?
¢给定n口油井的位置编程计算各油井到主管道之间的输油管道最小长度总和。

 

采用分治策略求中位数

int n;  //油井的数量

int x;  //x坐标,读取后丢弃

int a[1000];  //y坐标

cin>>n;

for (int i=0; i<n; i++)

  cin>>x>>a[i];

int y = select(0, n-1, n/2);  //采用分治算法计算中位数

//计算各油井到主管道之间的输油管道最小长度总和

int min=0;

for(int i=0;i<n;i++)

  min += (int)fabs(a[i]-y);

cout<<min<<endl;

半数集问题

¢给定一个自然数n,由n开始可以依次产生半数集set(n)中的数如下。

(1) n    set(n)

(2) n的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;

(3) 按此规则进行处理,直到不能再添加自然数为止。

例如,set(6)={6162612636136}

半数set(6)中有6个元素。

l注意半数集是多重集

对于给定的自然数n,编程计算半数集set(n)中的元素个数

计算半数集问题的递归算法—记忆式搜索

int a[1001];

int comp(int n)

{

  int ans=1;

  if(a[n]>0)return a[n];  //已经计算

  for(int i=1;i<=n/2;i++)

  ans+=comp(i);

  a[n]=ans;  //保存结果

  return ans;

}

整数因子分解

int total;  //定义为全局变量

void solve(int n)

{

  if (n==1) total++;  //获得一个分解

  else for (int i=2; i<=n; i++)

  if (n%i==0) solve(n/i);

}

//主函数main()中数据的读取与调用

int n;

while( scanf("%d",&n)!=EOF)

{

  total = 0;

  solve(n);

  printf("%d\n",total);

}

取余运算

¢输入三个正整数apk ap%k 的值。
¢输入
l输入有多组测试例。
l对每组测试例,有三个正整数apk 0apk2 232)。
¢输出
l对每组测试例输出1行,ap%k 的值。

 

算法设计与分析——第二章_递推算法

int mod(__int64 a, __int64 p, __int64 k)

{

  if (p == 1) return a%k;

  if (p%2) return mod(a%k, p-1, k)*a%k//p是奇数

  else return mod((a*a)%k, p/2, k);  //p是偶数

}

//主函数main()中数据的读取与调用

unsigned a, p, k;

while (scanf("%u%u%u",&a,&p,&k)!=EOF)

  printf("%d\n", mod(a, p, k));