《实用算法的分析与程序设计》的读书笔记(第2天)

类别:编程语言 点击:0 评论:0 推荐:
递归 第20页

[例1]划分问题 设s是一个具有n个元素的集合s 下列条件的子集合sl,s z,·。,s k: 1.si 56呼 (al,a z,·。,a。),现将s集合划分成K个满足 2.S;门Sj=69 ’ 3.S1廖S 2LJ S 3LJ·.·廖Sn=S · (1毒i,j毒k,i,6j) 则称s n,s z,…,s n是s的一个划分。它相当于把s集合中的n个元素a n·.·a n放人k 个无标号的盒子中,使得没有一个盒子为空。试确定n个元素a1…an放人k个无标号 盒的划分数s(n,k)。 分析:

设n个元素a n…a n故人k个无标号盒的划分数为s(n,k)。在配置过程中,有两种互 不相容的情况: 1.设(a n)是k个子集中的一个子集,于是把(a n…a n—1)划分为k一1子集有s(n一 1,k—1)个划分数; 2.如果(an)不是k个子集中的一个,即an必与其它的元素构成一个子集。首先把 (a,,…,an—1)划分成k个子集,这共有s(n一1,k)种划分方式。然后再把an加入到k 个子集中的一个子集中去,这有k种加入方式。对于每一种加入方式,都使集合划分为k 个子集,因此由乘法原理知,划分数共有 k*s(n一1,k)。 应用加法原理于上述两种情况,得出I a,,…,an)划分为k个子集的划分数: s(n,k)=s(n一1,k一1)十k*s(n一1,k) (n>1,k>=1)

现在考虑s(n,k)的边界条件:1). s(n,0)=0; 当k>n时s(n,k)=0;

2).s(n,1)=1; s(n,n)=1;

由此得到递归定义:

s(n,k)=s(n一1,k一1)十k*s(n一1,k) (n>k,k>=1) s(n,k)=o (n<k)或(k=0<n) s(n,k)=1 (k=1)或(k=n)

题解:

#include<iostream.h>
int s(int n,int k)
{
 int sum;
 if(n<k||k==0) sum=0;
   else if(k==1||k==n) sum=1;
        else sum=s(n-1,k-1)+k*s(n-1,k);
   return sum;
}
int main()
{
 int n,k;
   while(cin>>n>>k){
    int sum=s(n,k);
      cout<<sum<<endl;
   }
   return 1;
}

分治法 第22页

所谓分治策略:既将原问题分成n个规模较小而结构与原问题相似的 子问题。递归地解这些子问题,然后合并其结果就得到原问题的解。n=2 时的分治法又称二分法。

分治法在每一层递归上都有三个步骤: 分解:将原问题分解成一系列子问题;
解决:递归地解各子问题。若子问题足够小,则直接解
合并:将子问题的结果合并成原问题的解;
[例1I合并排序
    对序列A[1],A[2],……,A[n]进行合并排序。
算法分析:
  合并排序的算法就是二分法。
  分解:将n个元素各含rn/2。1(或Ln/2J)个元素的子序列
  解决:用合并排序法对两个子序列递归地排序;
  合并:合并两个已排序的子序列以得到排序结果。

前面在贪心法中用到的函数 merge(int p,int q,int r)与merge(int p,int r)就

是典型的二分排序法。这里不再重复。

 

枚举法 第31页

所谓枚举法,指的是从可能的解的集合中一一枚举各元素, 用题目给定的检验条件 判定哪些是无用的,哪些是有用的。能使命题成立,即为其解.

一般来说,如果预先对问题确定了解的个数以及各个解的值域。我们则可以利用ror语 句和条件判断语句逐步求解或证明. 
如果我们无法预先确定解的个数或各解的值域,我们只能采用隐式图搜索的算法求
解,例如回溯法等。由于回溯搜索,每个可能解的枚举一般不止一次,因此在相同的检验
条件下,回溯法要比枚学法费时一些。
[例0填写运算符
输入任意5个数 x1,x2,x3,x4,x5
每相邻两个数间填上一个运算符。在填入四个运算符后,使得表达式值为一个指定值
y(y由键盘输入)。求出所有满足条件的表达式。 分析:为了解决运算的优先级问题,我们设置如下变量:
    f——减法标志。减法运算时,置f=一1,否则f=1;
    q——若当前运算符为十(一)时,q存贮运算符的左项值;
若当前运算符为*(/)时,q存贮两数乘(除)后的结果;
    p——累加器。每填一个算符p=p十f *q。

然后四重for循环,解决问题。这是一道典型的枚举题,运算
量很大。具体程序还是比较简单,故不再转化。

枚举法有其计算量大的缺点,但是如果能够排除那些明显不属
于解集的元素,在局部地方使用枚举法,其效果会十分显著。

[例2]时针问题
    在图1.5。1所示的3×3阵列中有9个时钟,我们的目标是旋转时钟指针,使所有
时钟的指针都指向12点。允许旋转时钟指针的方法有9种,每一种移动用一个数字号(1,
2,…,9)表示。  图1.5—2示出了9个数字号与相应的受控制的时钟,这些时钟在图中以灰
色标出,其指针将顺时针旋转90度。

输入数据:
输入9个数码,这些数码给出9个时钟针的初始位置。数码与时刻
的对应关系为0-->12点,1->3点,2-->6点,3-->9点。
例:3 3 0 2 2 2 2 1 2
输出数据:
输出一个最短的移动序列(数字)
例:5 8 4 9

题解:

#include<iostream.h>
int clocks[9],map[9];
void init()
{
 int i;
   for(i=0;i<9;i++){
      cin>>clocks[i];
      clocks[i]=(4-clocks[i])%4;
   }
}
int order(int k)
{
 int c=k;
   while(c>4)c-=4;
   while(c<0)c+=4;
   return c;
}
void clock()
{
 init();
   int i; bool flag=false;
   for(map[0]=0;map[0]<=3;map[0]++)
    for(map[1]=0;map[1]<=3;map[1]++)
       for(map[2]=0;map[2]<=3;map[2]++){
          map[3]=order(clocks[0]-map[0]-map[1]);
            map[5]=order(clocks[2]-map[1]-map[2]);
            map[4]=order(clocks[1]-map[0]-map[1]-map[2]);
            map[6]=order(clocks[3]-map[0]-map[3]-map[4]);
            map[8]=order(clocks[5]-map[2]-map[4]-map[5]);
            map[7]=order(clocks[7]-map[6]-map[8]-map[4]);
            if(clocks[6]==(map[3]+map[6]+map[7])%4&&
             clocks[4]==(map[4]+map[0]+map[2]+map[6]+map[8])%4&&
               clocks[8]==(map[5]+map[7]+map[8])%4){
               for(i=0;i<9;i++)
                if(map[i]!=0)cout<<(i+1);
               cout<<endl;
               flag=true;
            }
         }
         if(!flag)cout<<"NO ANSWER!";
}
int main()
{
 clock();
   return 1;
}

本文地址:http://com.8s8s.com/it/it29558.htm