分治法解决棋盘覆盖、循环赛日程表、快速排序、归并排序

说到分治,就不得不先提递归了

递归算法:直接或者间接调用自身的算法称为递归算法。
设计步骤:

  1. 分析问题、寻找递归关系;
  2. 设置边界、控制递归;
  3. 设计函数、确定参数。
    接下来看一个递归的例子:递归解决全排列问题

引用问题描述:设R={r1,r2,…,rn}是要进行排列的n个元素,求R的全排列Perm(R)

  • 问题分析:
    设(ri)perm(X)表示在全排列perm(X)的每一个排列前加上前缀得到的排列。
  • 解决方案:
    1.递归关系:perm(R)由(r1)perm(R1),(r2)perm(R2) ,…, (rn)perm(Rn)构成,其中Ri=R–{ri}
    2.终止条件:n=1时,Perm(R)= r ,r是R中的唯一元素
    3.参数设置:待排序数组List,开始下标k,终止下标m
  • 实现思想:将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
void Perm(int list[], int k, int m)
{
    
    
      if(k==m)
       {
    
       for(int i=0;i<=m;i++)   cout<<list[i];
            cout<<endl;
        }
      else
         for(int i=k;i<=m;i++)
         {
    
      
              swap(list[k],list[i]);
              Perm(list, k+1,m);
              swap(list[k],list[i]);
         } 
 } 
  • 示例:
    当n=3,并且E={a,b,c},对集合E中元素进行全排列,则:
    perm(E)=a.perm({b,c}) + b.perm({a,c}) + c.perm({a,b})
    perm({b,c})=b.perm(c)+ c.perm(b)
    所以,a.perm({b,c})=ab.perm(c)+ ac.perm(b) =ab.c + ac.b=(abc, acb)
    同理有b.perm({a,c}) 和c.perm({a,b})

接下来介绍分治算法以及几个例子

  • 分治法解决问题的基本思想:
  1. 将原始问题划分或者归结为规模较小的子问题,这些子问题相互独立且与原问题相同。
  2. 递归或迭代求解每个子问题。
  3. 将子问题的解综合得到原问题的解。
  • 分治策略注意事项:
  1. 子问题与原始问题性质完全一样
  2. 子问题之间可彼此独立地求解
  3. 递归停止时子问题可直接求解

快速排序

快速排序:对n个元素进行排序。
最好时间复杂度:O(nlogn)     最坏时间复杂度:O(n2)

  • 基本步骤:
  1. 分解:以a[p]为基准元素将a[p:r]分成三段
    a[p:q-1], a[q], a[q+1:r]
    a[p:q-1]<a[q]<a[q+1:r]
  2. 递归分解:对a[p:q-1]和a[q+1:r]进行排序
  3. 合并:不需任何运算
void QuickSort(int *a,int p,int r) 
{
    
    
    if(p<r){
    
    
        int q=Partition(a,p,r);//该函数的功能:把比a[q]小的放在a[q]左边,比a[q]大的a[q]右边。
        QuickSort(a,p,q-1);//对左半段排序
        QuickSort(a,q+1,r);//对右半段排序
    }
}
int Partition(int *a,int p,int r)
{
    
    
   int i=p,j=r+1;
   while(1){
    
    
    while(a[++i]<=a[p]&&j<r);
    while(a[--j]>=a[p]&&j>p);
    if(i>=j)  break;
    swap(a[i],a[j]);
   }
   swap(a[p],a[j]);
   return j;//这里返回的是j的下标,而不是i的下标,因为i要么和j下标一样,要么i在j右边,比j大
}  

合并排序

合并排序:一分为二,先给分开的两边分别排序,再合并。
时间复杂度:O(nlogn)     空间复杂度:O(n)

void  MergeSort(int a[],int left,int right)
{
    
    
    int *b=new int [100];
    if(left<right)
    {
    
        //当数组a中只剩一个元素时,开始往前追溯求解
        int i=(left+right)/2;
        MergeSort(a,left,i);
        MergeSort(a,i+1,right);
        Merge(a,b,left,i,right);
        Copy(a,b,left,right);
    }
}

void  Merge(int a[],int b[],int left,int mid,int right)
{
    
             //此过程类似于数据结构刚开始学的两个递增的单链表的合并
    int i=left;
    int j=mid+1;
    int k=left;
    while(i<=mid&&j<=right)
    {
    
    
        if(a[i]<a[j])   b[k++]=a[i++];
        else            b[k++]=a[j++];
    }
    if(i>mid)
        for(int z=j; z<=right; z++)
            b[k++]=a[z];
    else
        for(int z=i; i<=mid; i++)
            b[k++]=a[z];
}

void  Copy(int a[],int b[],int left,int right)
{
    
              //数组b赋值给数组a
    for(int i=left; i<=right; i++)
        a[i]=b[i];
}

棋盘覆盖问题

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

  • 代码分析:
    当k>0时,将2k×2k棋盘分割为4个(2k-1)×(2k-1) 子棋盘。特殊方格必位于这4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为棋盘1×1。
void chessBoard(int tr, int tc, int dr, int dc, int size)
{
    
     //特殊方格横坐标dr,纵坐标dc;当前棋盘横坐标tr,纵坐标tc
    if (size == 1)    return;
    int  t = tile++;// L型骨牌号,用数字代替L骨牌
    s = size/2;     // 分割棋盘
    if (dr < tr + s && dc < tc + s)
        chessBoard(tr, tc, dr, dc, s);
    else  {
    
    
        board[tr + s - 1][tc + s - 1] = t;
        chessBoard(tr, tc, tr+s-1, tc+s-1, s);
    }//左上方
    if (dr < tr + s && dc >= tc + s)
        chessBoard(tr, tc+s, dr, dc, s);
    else  {
    
    
        board[tr + s - 1][tc + s] = t;
        chessBoard(tr, tc+s, tr+s-1, tc+s, s);
    }//右上方
    if (dr >= tr + s && dc < tc + s)
        chessBoard(tr+s, tc, dr, dc, s);
    else  {
    
    
        board[tr + s][tc + s - 1] = t;
        chessBoard(tr+s, tc, tr+s, tc+s-1, s);
    }//左下方
    if (dr >= tr + s && dc >= tc + s)
        chessBoard(tr+s, tc+s, dr, dc, s);
    else {
    
    
        board[tr + s][tc + s] = t;
        chessBoard(tr+s, tc+s, tr+s, tc+s, s);
    }//右下方
}

时间复杂度:O(4k)

循环赛日程表

问题描述:
设有n=2k个运动员要进行循环赛,设计一个比赛日程表,要求满足以下条件的:
1.每个选手必须与其他n-1个选手各赛一次;
2.每个选手一天只能赛一次;
3.循环赛一共进行n-1天。

  • 分治策略:
    可以通过为n/2个选手设计的比赛日程表来决定。递归地对选手进行分割,直到只剩下2个选手时,比赛日程表的制定就变得很简单。这时只要让这2个选手进行比赛就可以了。

该表中,第一列的1 2 3…表示第i个人的比赛安排,第二列之后直到第n列(用第k列表示)指第k-1天第i个人的比赛安排。

void Table(int k,int **a)
{
    
    
    int n=1for(int i=1; i<=k; i++)   n*=2;
    for(int i=1; i<=n; i++)   a[1][i]=i;
    int m=1;                  //初始化第一行
    for(int s=1; s<=k; s++)
    {
    
             //由于每次安排人数都必须是2^k,所以只需循环k遍。
        n/=2;
        for(int t=1; t<=n; t++){
    
    //对当前表格进行安排
            for(int i=m+1; i<=2*m; i++){
    
    
                for(int j=m+1; j<=2*m; j++){
    
    
                    a[i][j+(t-1)*m*2]=a[i-m][j+(t-1)*m*2-m];
                    a[i][j+(t-1)*m*2-m]=a[i-m][j+(t-1)*m*2];
                }
                m*=2;
            }
        }
    }
}

接下来看一个简单题:用递归与分治策略求n个元素中的最大值。

求n个元素中的最大元素值,要求用递归与分治策略解决。
输入
第1行:8             元素个数
第2行:10 3 9 20 4 83 24 65   8个元素的值
输出: 83            最大元素值

#include <bits/stdc++.h>

using namespace std;

int isMax(int *a, int begin,int end)
{
    
    
    if (begin == end){
    
    
        return a[end];
    }
    int mid=(begin + end)/2;
    int x=isMax(a, begin, mid);
    int y=isMax(a, mid + 1, end);
    return max(x,y);
}

int main()
{
    
    
    int n;
    cin >> n;
    int a[100];
    for(int i=0; i<n;i++)   cin >> a[i];
    cout << isMax(a,0,n-1);
}

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/107473854