算法入门3:分支算法(下)



算法入门3:分支算法(下)

分治算法的设计模式 – 大化小,小化了

分治算法的主要步骤就是:分解,求解,合并。

 

  1. Divide-and-Conquer(P)
  2. {
  3.          //问题规模足够小,直接解决
  4.     if(P≤n0) return(ADHOC(P);
  5.          //问题规模大,则分解为较小的子问题 P1 ,P2 ,…,Pk
  6.     divide p into smaller subinstance P1 ,P2 ,…,Pk
  7.          //递归解决每个小问题
  8.     for i = 1 to k
  9.                  yi =  Divide-and-Conquer(Pi)
  10.          //合并子问题的解
  11.     T =  MERGE(y1,y2,…,yk)
  12.     return(T)
  13. }
Divide-and-Conquer(P)
{
         //问题规模足够小,直接解决
    if(P≤n0) return(ADHOC(P);

         //问题规模大,则分解为较小的子问题 P1 ,P2 ,...,Pk
    divide p into smaller subinstance P1 ,P2 ,...,Pk

         //递归解决每个小问题
    for i = 1 to k
                 yi =  Divide-and-Conquer(Pi)

         //合并子问题的解
    T =  MERGE(y1,y2,...,yk)          

    return(T)
}

 

经典问题

(1)二分搜索

(2)大整数乘法

(3)Strassen矩阵乘法

(4)棋盘覆盖

(5)合并排序

(6)快速排序

(7)线性时间选择

(8)最接近点对问题

(9)循环赛日程表

二分搜索

问题:从一个已经排好序的序列中,查找某一个元素。

 

分析:假设序列数组为a,大小为n,从小到大的顺序排列。按照最基本的思路,那就是遍历一遍数组,时间复杂度为O(n)。如果n很大,效率显然不够高,因为没有把题目中“已经排好序” 的条件用上。既然是已经排好序,那么就可以进行折半查找(二分搜索)。方法如下:

1. 将数组从中间分成上下两半,如果中间的值刚好是要查找的元素,直接输出结果。

2. 如果中间的值比要查找的大,那么说明要查找的元素只可能出现在上半段,再对上半段进行二分搜索。

3. 如果中间的值比要查找的小,那么说明要查找的元素只可能出现在下半段,再对下半段进行二分搜索。

 

这就是分治算法最典型的例子。每次把规模为n的问题划分成n/2,继续划分成n/4,n/8… 直到足够小能直接解决。

 

代码:


 

  1. /************************************************************************
  2. * 名  称:BinarySearch.cpp
  3. * 功  能:分治算法案例:二分查找
  4. * 作  者:JarvisChu
  5. * 时  间:2013-11-7
  6. ************************************************************************/
  7. #include “stdio.h”
  8. /*———————————————————————————-
  9. * 功  能:   在大小为n的数组a中查找元素tag
  10. * 参  数:   a[] 要查找的数组,从小到大排列
  11.                           n 数组大小
  12.                           tag 待查找的元素
  13. * 返  回: 找到则返回所在下标,否则返回-1
  14. ————————————————————————————*/
  15. int BinarySearch(int a[],int n,int tag)
  16. {
  17.          int low = 0,high = n-1,mid = 0;
  18.          while(low<=high)
  19.          {
  20.                  mid = (low+high)/2;
  21.                  //正好是中间的元素
  22.                  if(a[mid] == tag)
  23.                           return mid;
  24.                  //在上半段查找
  25.                  if(a[mid]>tag)
  26.                  {
  27.                           high = mid-1;
  28.                  }
  29.                  //在下半段查找
  30.                  else
  31.                  {
  32.                           low = mid+1;
  33.                  }
  34.          }
  35.          return -1;
  36. }
  37. int main()
  38. {
  39.          //Test
  40.          int a[10]={0,1,2,3,4,5,6,7,8,9};
  41.          printf(“%d\n”,BinarySearch(a,10,4));
  42.          printf(“%d\n”,BinarySearch(a,10,11));
  43.          return 0;
  44. }
/************************************************************************ 
 * 名  称:BinarySearch.cpp 
 * 功  能:分治算法案例:二分查找 
 * 作  者:JarvisChu 
 * 时  间:2013-11-7 
 ************************************************************************/ 

#include "stdio.h"

/*---------------------------------------------------------------------------------- 
 * 功  能:   在大小为n的数组a中查找元素tag
 * 参  数:   a[] 要查找的数组,从小到大排列
                          n 数组大小 
                          tag 待查找的元素
 * 返  回: 找到则返回所在下标,否则返回-1 
 ------------------------------------------------------------------------------------*/  
int BinarySearch(int a[],int n,int tag)  
{
         int low = 0,high = n-1,mid = 0;

         while(low<=high)
         {
                 mid = (low+high)/2;

                 //正好是中间的元素
                 if(a[mid] == tag)
                          return mid;

                 //在上半段查找
                 if(a[mid]>tag)
                 {
                          high = mid-1;
                 }

                 //在下半段查找
                 else
                 {
                          low = mid+1;
                 }
         }

         return -1;
}

int main()
{
         //Test
         int a[10]={0,1,2,3,4,5,6,7,8,9};
         printf("%d\n",BinarySearch(a,10,4));
         printf("%d\n",BinarySearch(a,10,11));

         return 0;
}

 

棋盘覆盖

问题:  在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

    

分析:   当k>0时,将2k×2k棋盘分割为4个2k-1×2k-1子棋盘(a)所示。

特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,如 (b)所示,可以把黄色的方格看做是较小棋盘的特殊方格,从而将原问题转化为4个较小规模的棋盘覆盖问题。

递归地使用这种分割,直至棋盘简化为棋盘1×1。

 

代码:

  1. /************************************************************************
  2. * 名  称:Chessboard.cpp
  3. * 功  能:分治算法案例:棋盘覆盖
  4. * 作  者:JarvisChu
  5. * 时  间:2013-11-7
  6. ************************************************************************/
  7. #include <stdio.h>
  8. /* L型骨牌的类型
  9. ——           ——|            |                         |
  10. |                      |            |                         |
  11. |                      |            |——             ——|
  12. */
  13. #define L1 1
  14. #define L2 2
  15. #define L3 3
  16. #define L4 4
  17. #define N 16            //棋盘大小,边长
  18. int board[N][N]; //棋盘
  19. /*———————————————————————————-
  20. * 功  能: 对左上角为(tr,tc),边长为size的棋盘进行棋盘覆盖
  21. * 参  数:(tr,tc)为棋盘的左上角坐标;(dr,dc)为特殊方格的位置;size为棋盘的大小,即边长
  22. * 返  回:无
  23. ————————————————————————————*/
  24. void ChessBoard(int tr,int tc,int dr,int dc,int size)
  25. {
  26.          //棋盘为1 x 1,不可再分割,直接返回
  27.          if(size == 1) return ;
  28.          //分割子棋盘
  29.          int s = size/2;//子棋盘大小
  30.          //判断特殊方格在哪个子棋盘内,以确定使用哪种L骨牌
  31.          int type = 0;
  32.          if(dr < tr+s && dc < tc+s)//特殊方格在左上角的子棋盘内
  33.          {
  34.                  type = L4; //使用第四种类型的L骨牌
  35.          }
  36.          else if(dr < tr + s && dc >= tc + s) //在右上角
  37.          {
  38.                  type = L3;
  39.          }
  40.          else if(dr >= tr + s && dc < tc + s) //在左下角
  41.          {
  42.                  type = L2;
  43.          }
  44.          else                                 //在右下角
  45.          {
  46.                  type = L1;
  47.          }
  48.          //覆盖左上角的子棋盘
  49.          if(type == L4)           //特殊方格在该子棋盘内
  50.          {
  51.                  ChessBoard(tr,tc,dr,dc,s);//递归覆盖该子棋盘
  52.          }
  53.          else                    //特殊方格不在该子棋盘内
  54.          {
  55.                  board[tr+s-1][tc+s-1] = type;//使用type型骨牌覆盖其右下角的方格
  56.                  ChessBoard(tr,tc,tr+s-1,tc+s-1,s);
  57.          }
  58.          //覆盖右上角的子棋盘
  59.          if(type == L3)        //特殊方格在该子棋盘内
  60.          {
  61.                  ChessBoard(tr,tc+s,dr,dc,s);
  62.          }
  63.          else                 //特殊方格不在该子棋盘内
  64.          {
  65.                  board[tr+s-1][tc+s] = type;//使用type型骨牌覆盖其左下角的方格
  66.                  ChessBoard(tr,tc+s,tr+s-1,tc+s,s);
  67.          }
  68.          //覆盖左下角的子棋盘
  69.          if(type == L2)      //特殊方格在该子棋盘内
  70.          {
  71.                  ChessBoard(tr+s,tc,dr,dc,s);
  72.          }
  73.          else                //特殊方格不在该子棋盘内
  74.          {
  75.                  board[tr+s][tc+s-1] = type; //使用type型骨牌覆盖其右上角的方格
  76.                  ChessBoard(tr+s,tc,tr+s,tc+s-1,s);
  77.          }
  78.          //覆盖右下角的子棋盘
  79.          if(type == L1)         //特殊方格在该子棋盘内
  80.          {
  81.                  ChessBoard(tr+s,tc+s,dr,dc,s);
  82.          }
  83.          else                   //特殊方格不在该子棋盘内
  84.          {
  85.                  board[tr+s][tc+s] = type;//使用type型骨牌覆盖其左上角的方格
  86.                   ChessBoard(tr+s,tc+s,tr+s,tc+s,s);
  87.          }
  88. }
  89. int main()
  90. {
  91.          //初始化棋盘
  92.          for(int row=0;row<N;++row)
  93.          {
  94.                  for(int col=0;col<N;++col)
  95.                           board[row][col] = 0;
  96.          }
  97.          //设定特殊方格位置
  98.          int dr = 3,dc=4;
  99.          //棋盘覆盖
  100.          ChessBoard(0,0,dr,dc,N);
  101.          //打印棋盘
  102.          for(int row=0;row<N;++row)
  103.          {
  104.                  for(int col=0;col<N;++col)
  105.                           printf(“%3d”,board[row][col]);
  106.                  printf(“\n”);
  107.          }
  108.          return 0;
  109. }
/************************************************************************ 
 * 名  称:Chessboard.cpp
 * 功  能:分治算法案例:棋盘覆盖 
 * 作  者:JarvisChu 
 * 时  间:2013-11-7 
 ************************************************************************/ 

#include <stdio.h>

/* L型骨牌的类型

------           ------|            |                         |
|                      |            |                         |
|                      |            |------             ------|

*/
#define L1 1
#define L2 2
#define L3 3
#define L4 4

#define N 16            //棋盘大小,边长

int board[N][N]; //棋盘

/*---------------------------------------------------------------------------------- 
 * 功  能: 对左上角为(tr,tc),边长为size的棋盘进行棋盘覆盖      
 * 参  数:(tr,tc)为棋盘的左上角坐标;(dr,dc)为特殊方格的位置;size为棋盘的大小,即边长
 * 返  回:无 
 ------------------------------------------------------------------------------------*/  
void ChessBoard(int tr,int tc,int dr,int dc,int size)
{
         //棋盘为1 x 1,不可再分割,直接返回
         if(size == 1) return ;

         //分割子棋盘
         int s = size/2;//子棋盘大小

         //判断特殊方格在哪个子棋盘内,以确定使用哪种L骨牌
         int type = 0;
         if(dr < tr+s && dc < tc+s)//特殊方格在左上角的子棋盘内
         {
                 type = L4; //使用第四种类型的L骨牌
         }
         else if(dr < tr + s && dc >= tc + s) //在右上角
         {
                 type = L3;
         }
         else if(dr >= tr + s && dc < tc + s) //在左下角
         {
                 type = L2;
         }
         else                                 //在右下角
         {
                 type = L1;
         }

         //覆盖左上角的子棋盘
         if(type == L4)           //特殊方格在该子棋盘内
         {
                 ChessBoard(tr,tc,dr,dc,s);//递归覆盖该子棋盘
         }
         else                    //特殊方格不在该子棋盘内
         {
                 board[tr+s-1][tc+s-1] = type;//使用type型骨牌覆盖其右下角的方格
                 ChessBoard(tr,tc,tr+s-1,tc+s-1,s);
         }

         //覆盖右上角的子棋盘
         if(type == L3)        //特殊方格在该子棋盘内
         {
                 ChessBoard(tr,tc+s,dr,dc,s);      
         }
         else                 //特殊方格不在该子棋盘内
         {
                 board[tr+s-1][tc+s] = type;//使用type型骨牌覆盖其左下角的方格
                 ChessBoard(tr,tc+s,tr+s-1,tc+s,s);
         }

         //覆盖左下角的子棋盘
         if(type == L2)      //特殊方格在该子棋盘内
         {
                 ChessBoard(tr+s,tc,dr,dc,s);      
         }
         else                //特殊方格不在该子棋盘内
         {
                 board[tr+s][tc+s-1] = type; //使用type型骨牌覆盖其右上角的方格
                 ChessBoard(tr+s,tc,tr+s,tc+s-1,s);
         }

         //覆盖右下角的子棋盘
         if(type == L1)         //特殊方格在该子棋盘内
         {
                 ChessBoard(tr+s,tc+s,dr,dc,s);    
         }
         else                   //特殊方格不在该子棋盘内
         {
                 board[tr+s][tc+s] = type;//使用type型骨牌覆盖其左上角的方格
                  ChessBoard(tr+s,tc+s,tr+s,tc+s,s);
         }
}

int main()
{
         //初始化棋盘
         for(int row=0;row<N;++row)
         {
                 for(int col=0;col<N;++col)
                          board[row][col] = 0;
         }

         //设定特殊方格位置
         int dr = 3,dc=4;

         //棋盘覆盖
         ChessBoard(0,0,dr,dc,N);

         //打印棋盘
         for(int row=0;row<N;++row)
         {
                 for(int col=0;col<N;++col)
                          printf("%3d",board[row][col]);
                 printf("\n");
         }

         return 0;
}

 

备注:博主用MFC实现了一个图形化的棋盘覆盖程序。如有兴趣可以留下邮箱索要源码。

可以设置棋盘的格数,特殊方格(红色)的位置,点击开始演示,则执行棋盘覆盖

 

 

转载本文请注明作者和出处

作者 :JarvisChu

出处:http://blog.csdn.net/jarvischu