算法笔记2 递归与分治策略

一.递归的概念:直接或间接地调用自身的算法成为递归算法。用函数自身给出定义的函数成为递归函数。
1.1递归解决阶乘函数。定义=(n是非负整数,n的阶乘是n*(n-1)......2*1)
int factorial(int n)
            {
                    if(n == 0)return 1;
                      return n * factorial(n - 1);
              }
 
1.2递归解决Fibonacci(斐波那契)数列。定义=当n〉时,数列的第n项是他前面两项之和,初始值F(0)和F(1)都==1。
int fibonacci(int n)
              {
                      if(n <= 1) return 1;
                    return fibonacci(n - 1) + fibonacci(n - 2);
              }
 
1.3递归解决排列问题。R的全排列定义=当n=1时,全排列Perm(R) = (r),其中r是集合R中唯一的元素;      当n>1时,Perm(R)由(r1)Perm(R1),(r2)Perm(R2),...,(rn)Perm(Rn)构成
template<class Type>
  void Perm(Type list[], int k, int m)//k是待排列数组的起始位,m是结束位。
  {//产生List[k, m]的所有排列
    if(k == m)
    {//只剩下一个元素
      for(int i = 0;i <= m; i++) cout << list[i];
      cout << endl;
    }
    else//还有多个元素待排列,递归产生排列
    {
      for(int i = k;i <=m; i++)//递归调用时,对剩下的k->m之间的数,继续排列。
      {
        Swap(list[k], list[i]);
        Perm(list, k + 1, m);
        Swap(list[k], list[i]);
      }
    }
   
  }
  void Swap(Type &a, Type &b)
  {
    Type temp = a;a = b; b = temp;
  }
 
1.4递归解决Hanoi塔问题。定义=有a,b,c三个塔座,a上自下而上,由大到小叠有n个圆盘。要求将塔座a上的所有圆盘移动b上,并仍按同样顺序叠置。移动过程中遵循的规则:1)每次只能移动一个圆盘2)任何时刻都不允许小的圆盘上有大的3)满足1)2)的前提下,可将圆盘移到a,b,c中任一塔座上。
基本解决思路:移动过程中,奇数次移动,将最小的顺时针(a->b->c->a)移到下一塔座;偶数次移动,最小的不动,其他两个塔座间,次小的移到另一个塔座上。
递归解决思路:n=1时,直接移动。n>1时,设法将n-1个较小的移到c上,然后将剩下的最大的从a移到b上;在设法将c上n-1个较小的从c移到b。

  void hanoi(int n, int a,int b,int c)//a,b,c可看做三个塔座a,b,c;n是n个圆盘
  {
    if(n > 0)
    {
      hanoi(n - 1, a, c, b);
      move(a,b);  //模拟移动动作
      hanoi(n - 1, c, b, a);
    }
  }
 
二.分治法的基本思想:将一个规模为n的问题分解为k个规模较小的子问题,这些子问题,互相独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
一个分治法,将规模为n的问题分成k个规模为n/m的子问题去解。为方便起见,设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。另外,再设将原问题分解为k个子问题及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。如果用T(n)表示该分治法divide-and-conquer(P)解规模为|P|=n的问题所需的计算时间,则有T(n)=O(1) n=1;T(n)=kT(n/m) + f(n).
它的一般的算法设计模式如下:
divide-and-conquer(P)
  {
    if(|P| <= n0) adhoc(P);
    divide P into small subinstances P1,P2,···,Pk;
    for(int i = 1; i <= k; i++)
    {
      yi = divide-and-conquer(Pi);
      return merge(y1,y2,···,yk);
    }
  }
 
2.1分治法解决二分搜索技术。定义=给定已经排好序的n个元素a[a:n-1],现在要在这n个元素中找出一特定元素x。采用分治策略,最坏情况下用O(logn)时间完成搜索任务。
基本思路:将n个元素分成个数大致相同的两半,取a[n/2]与x比较。如果,x=a[n/2],则找到x,算法终止;如果x<a[n/2],则只要在数组a的左半部继续搜索x;如果x>a[n/2],则只要在数组a的右半部继续搜索x。
template<class Type>
  int BinarySearch(Type a[], const Type&, int n)
  {//在a[0]到a[n-1]中搜索x
    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
  }
 
2.2分治法解决大整数的乘法。定义=需要处理的数无法在计算机硬件对整数的表示范围内直接处理,就必须用软件的方法来实现大整数的算术运算,如果将每两个一位数的乘法或加法看做一步运算,这种方法要进行O(n~2)步才能求出乘积XY。用分治法来设计将更有效地解决这个问题。X=A2~(n/2) + B,Y=C2~(n/2) +D.按此式相乘几乎没有改进算法。写成另外一种形式:XY=AC2~n + ((A-B)(D-C) +AC + BD)2~(n/2) +BD,此式看似复杂,但对于计算机运算来说,效率有了很大改进,T(n)由O(n~2)变为O(n~1.59).
 
2.3分治法解决Strassen矩阵乘法。定义=对于A和B的乘积矩阵C,每计算一个元素c(ij),需要做n次乘法和n-1次加法。因此,求出矩阵C的n~2个元素所需的计算时间是O(n~3)。
分治法解决思路:假设n是2的幂,将矩阵A,B,C中每一矩阵都分块成4个大小相等的子矩阵,每个子矩阵都是n/2 * n/2的方阵,对这个小方阵,改进一下,则2阶方阵用少于原来8次的乘法运算7次,但增加了加、减法的运算次数。用此法改进了算法,T(n)=O(n~2.81).
 
2.4分治法解决合并排序。定义=合并排序算法是用分治策略实现对n个元素进行排序的算法
  基本思想:将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。
  合并排序算法可递归地描述如下:
    template<class Type>
void MergerSort(Type a[], int left, int right)
{
if(left < right)//至少有2个元素
{
int middle = (left + right)/2;//取中点
MergeSort(a, left, middle);
MergerSort(a, middle + 1, right);
Merge(a, b, left, middle, right);//合并到数组b
Copy(a, b, left, right);//赋值回数组a
}
}
template<class Type>
void Merge(Type c[], Type d[], int n, int m, int r)
{//合并c[n:m]和c[m+1:r]到d[n:r]
int i = n, j = m + 1, k = n;
while((i <= m) && (j <= r))
{
if(c[i] <= c[j]) d[k++] = d[i++];
else d[k++] = c[j++];
}
if(i > m)
for(; j <= r; j++) d[k++] = c[j];
else for(; i <= m; i++) d[k++] = c[i];
}
   
消去递归后的合并排序算法可描述如下:
此处略去。
2.5分治法解决快速排序。定义=快速排序算法是基于分治策略的另一个排序算法那。
  基本思想:对于输入的子数组a[p:r],按以下三个步骤进行排序。
  1)分解:以a[p]为基准,分数组为两部分,左边的均小于a[p],右边的均大于a[p]。
  2)递归求解:通过递归调用快速排序法分别对a[p:q-1]和a[q+1:r]进行排序。
  3)合并:对两部分排好序后,不需要任何计算,a[p:r]就已经排好序了。
    template<class Type>
void QuickSort(Type a[], int p, int r)
{
if(p < r)
{
int q = Partition(a,p,r);
QuickSort(a, p, q - 1);//对左半段排序
QuickSort(a, q + 1, r);//对右半段排序
}
}
tempplate<class Type>
int Partition(Type a[], int p, int r)
{
int i = p,j = r + 1;
Type x = a[p];
//将小于x的元素交换到左边区域
//将大于x的元素交换到右边区域
while(true)
{
while(a[++i] < x && i < r);
while(a[--j] >);
if(i >= j) break;
Swap(a[i], a[j]);
a[j] = x;
return j;
}
}




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值