分治法实验报告——以棋盘覆盖和快速排序为例

算法分析与设计——分治法

1 实验目的

通过练习掌握分治法的基本思想

2 实验要求

  1. 掌握棋盘覆盖程序,输入为2n*2n的棋盘,包含一个特殊方块。
  2. 随着n的增长,n从3增长到10,分别打印出棋盘覆盖程序所需的时间。
  3. 掌握快速排序算法。
  4. 分析快速排序算法的时间复杂度,比较算法与其他排序算法(冒泡)的时间复杂度。

3 实验内容

  1. 设计棋盘覆盖算法程序
  2. 分别求出不同规模的输入算法所需的运行时间,总结出该时间与n的关系。
  3. 分析棋盘覆盖程序实际复杂度与理论复杂度的差异。
  4. 设计快速排序算法程序。
  5. 用少量数据(10个数据)调试并测试算法的正确性。
  6. 用大量数据(10000个随机生成的数据)测试算法与冒泡排序算法的完成时间比较。

4 实验步骤:具体完成步骤

4.1 棋盘覆盖算法

  1. 把棋盘等分成四个正方形分别是:左上、左下、右上、右下四个子棋盘。
  2. 对于每个子棋盘,如果存在特殊方格,将它再分成四个子棋盘,并用同样的方法,对子棋盘进行递归。
  3. 对于不存在特殊方格的子棋盘,假定与另外三个子棋盘相接的为特殊方格,有了特殊方格后,对这个子棋盘进行递归。
  4. 直到子棋盘为1*1的正方形。

4.2 快速排序法

设要排序的数组时A[0]…A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始时:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索j–,找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索i++,找到第一个大于key的A[i],将A[i]和A[j]互换。
重复第3、4步,直到i=j;(3,4步中没有找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直到找到为止。找到符合条件的值,进行交换的时候i,j指针位置不变。另外,i==j这一过程一定正好是i++或j–完成的时候,此时令循环结果。)
注:如果是算法,要有对算法基本过程的归纳。

5 代码

棋盘覆盖

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<time.h>
using namespace std;
//特殊方格的位置为:(2,6)
//8*8  N=2^k;
const int N=8; 
int Board[N][N];///二维整形数组N*N的棋盘
int tile=1;///全局变量,表示L型骨牌号

void InitChessBoard(int m,int n)///表示初始化
{

     for(int i=0;i<m;i++)
      for(int j=0;j<n;j++)
      {
        Board[i][j]=0;
      }

}
	

/**
    tr表示棋盘左上角的行号
    tc表示棋盘左上角的列号
    dr表示特殊方格左上角的列号
    dc表示特殊方格左上角的列号
    size=2^k
*/
void ChessBoard(int tr,int tc,int dr,int dc,int size)///棋盘覆盖的分治法如下(结合递归)
{
   if(size==1)///递归的出口
    return;
   int t=tile++;///L型骨牌号
   int s=size/2;///将棋盘一分为四(分治法)

   ///覆盖左上角的棋盘
   if(dr<tr+s&&dc<tc+s)///在此棋盘中
   {
       ChessBoard(tr,tc,dr,dc,s);///此时长度发生变化(递归分治,将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);///覆盖其他方格
    }


}

void OutPut(int n,int m)///输出覆盖后的棋盘矩阵
{
    for(int i=0;i<n;i++)
    {
     for(int j=0;j<m;j++)
     {
     cout<<setw(3)<<Board[i][j];
     }
     cout<<endl<<endl;
    }
}


int main()
{
	
    cout<<"\t输出ChessBoard(棋盘覆盖的算法如下——L型骨牌覆盖)"<<endl<<endl;
    int k;//表示边长的次方()
	int n;///表示特殊方格的行号
    int m;///表示特殊方格的列号
    cin>>k;
    cin>>n;
    cin>>m;
    
    int t1=clock();//时间节点1 
    InitChessBoard(N,N);//初始化棋盘 
    ChessBoard(0,0,n,m,N);
    cout<<endl<<endl;
    OutPut(N,N);//输出棋盘 
  	int t2=clock();//时间节点2     
    cout<<endl<<endl;
    
    printf("%15.10fm",(double)(t2-t1)/CLOCKS_PER_SEC);
    
	return 0;
}

快速排序

#include <iostream>
using namespace std;

//数组打印
void P(int a[],int n)
{
    for(int i=0; i<n; i++)
        cout<<a[i]<<" ";
    cout<<endl;
}

int Partition(int s[], int l, int r){
    int i = l, j = r+1, x = s[l]; 
	int b;//将最左元素记录到x中
  while (true) {
           while (s[++i] <x && i<r);
           while (s[--j] >x);
           if (i >= j) break; 
           b=s[i];
           s[i]=s[j];
           s[j]=b;
           }
       s[l] = s[j];
       s[j] = x;
       P(s,10);
       return j;
}

void QuickSort(int s[], int l, int r)
{
     if (l<r) {
        int q=Partition(s,l,r);
        QuickSort (s,l,q-1); //对左半段排序
        QuickSort (s,q+1,r); //对右半段排序
        }

}


int main()
{
    int a[]= {5,2,3,6,1,7,8,9,0,4};
    cout<<"快速排序:"<<" \n"; 
    P(a,10);
    QuickSort(a,0,9);//注意最后一个参数是n-1
     cout<<"最终排序:"<<" \n"; 
    P(a,10);
    return 0;
}

6 实验结果与分析

6.1 棋盘算法

n=10
n=10棋盘算法

n不同时运行时间分析:
在这里插入图片描述
理论复杂度:
在这里插入图片描述
解此递归方程可得:由于2n*2n棋盘所有的骨牌个数为(4^n-1)/3,所以这个算法是一个渐进意义的最优算法。

6.2 快速排序算法

用少量数据(n=10)验证算法的正确性
在这里插入图片描述

用大量数据(10000个随机生成的数据)测试算法与冒泡排序算法的完成时间比较

n=10000时,快速排序的运行时间
在这里插入图片描述

n=10000时,冒泡排序的运行时间
在这里插入图片描述
由此可见,快速排序的T(n)<<冒泡排序的T(n)

补充:
分治法所能解决的问题一般具有以下几个特征:
1.该问题的规模缩小到一定的程度就可以容易地解决;(因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征。)
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质(这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用)
3.利用该问题分解出的子问题解可以合并为该问题的解;(能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划。)
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。(这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。)

  • 24
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值