前言
NEFU,计算机与控制工程学院,基于C/C++的算法设计与分析课程
实验一 递归与分治算法设计
环境
操作系统:Windows 10
IDE:Visual Studio Code、Dev C++ 5.11、Code::Blocks
说明
“实验一 递归与分治算法设计” 包含以下问题
- 棋盘覆盖问题
- 合并排序问题
- 集合最大元问题
其他联系方式:
Gitee:@不太聪明的椰羊
B站:@不太聪明的椰羊
一、实验目的
掌握分治算法的基本思想,掌握分治算法的设计步骤及用递归技术实现分治策略。
二、实验原理
算法总体思想:对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之,如下图所示。
三、实验内容
1、棋盘覆盖问题
在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
1.1 分析
当k>0时,将2k×2k棋盘分割为4个2k-1×2k-1 子棋盘(a)所示。
特殊方格必位于4个较小子棋盘之一中,其余3个子棋盘中无特殊方格。为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,如 (b)所示,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种分割,直至棋盘简化为棋盘1×1。
1.2 代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <math.h>
using namespace std;
const int N = 520;
int board[N][N];
int tile = 1;//L型骨牌,编号从 1 开始
//当前棋盘左上角方格的行号 tr、列号 tc,特殊方格的行号 dr、列号 dc, 棋盘规模size
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); //覆盖左上角子棋盘
}
else//此棋盘中无特殊方格(左上角棋盘)
{
board[tr + s - 1][tc + s - 1] = t;//用 t号L型骨牌覆盖右下角
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;//用 t号L型骨牌覆盖左下角
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;//用 t号L型骨牌覆盖右上角
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;//用 t号L型骨牌覆盖左上角
chessBoard(tr + s, tc + s, tr + s, tc + s, s);//将已经覆盖的方格作为特殊方格,覆盖其余方格
}
}
int main()
{
int k, dr, dc, size;
while (cin >> k)//k表示阶数,可以同时对多个不同的棋盘进行覆盖
{
size = (int)pow(2, k); //棋盘规模size=2^k
cin >> dr >> dc; //特殊方格的行号 dr、列号 dc
board[dr][dc] = 0;//特殊方格用 0 标记
chessBoard(1, 1, dr, dc, size);
for (int i = 1; i <= size; ++i)
{
for (int j = 1; j <= size; ++j)
{
cout << left << setw(5) << board[i][j];
}
cout << endl;
}
}
return 0;
}
1.3 测试
2、合并排序问题
对n个元素组成的序列进行排序。
基本思想:将待排序元素分成大小大致相同的两个子集合,分别对两个集合进行排序,最终将排序好的子集合合并成所要求的排好序的集合。
2.1 分析
分:分成两个大小大致相同的子集合。
治:分别对两个子集合进行排序。
合:将排序好的子集合并成一个有序的集合。
Merge 函数用于合并两个有序数组,并将结果存储到数组 b 中。
copy 函数用于复制数组 b 的内容到数组 a 中。
MergeSort 函数是归并排序的主要逻辑,通过递归将数组分成两部分进行排序,然后合并已排序的部分。
合并排序的时间复杂度为 O(n log n),属于稳定的排序算法,适用于各种数据情况。通过不断地将数组分割成小块再合并的方式,实现了高效的排序算法。
2.2 代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <math.h>
using namespace std;
int a[1024];//待排序数组
int b[1024];//合并结果
void Merge(int a[], int b[], int left, int mid, int right) //合并两个有序数组,存储到b数组
{
int p = left, q = mid+1;// p是a数组左区间的下标,q是a数组右区间的下标
int k = 0;// k是合并数组b的下标
while(p <= mid && q <= right)
{
if(a[p] < a[q])
{
b[k++] = a[p++];
}
else
{
b[k++] = a[q++];
}
}
while(p <= mid)
b[k++] = a[p++];
while(q <= right)
b[k++] = a[q++];
}
void copy(int a[], int b[],int left,int right)
{
for(int i = left; i <= right; i++) {
a[i] = b[ i- left];// a数组下标从left开始 ,b数组下标从0开始
}
}
void MergeSort(int a[], int left, int right) {
if (left < right) //至少有2个元素
{
int i = (left + right) / 2; //取中点
MergeSort(a, left, i); //左区间
MergeSort(a, i + 1, right); //右区间
Merge(a, b, left, i, right);//合并到数组b
copy(a, b, left, right);//复制回数组a
}
}
int main() {
int n, i;
cout << "输入待排序元素的个数:";
cin >> n;
cout << "输入待排序元素:";
for (i = 0; i < n; i++) {
cin >> a[i];
}
MergeSort(a, 0, n - 1);
cout << "排序后的序列为:";
for(i = 0; i < n; i++) {
cout << a[i] << " ";
}
return 0;
}
2.3 测试
3、 集合最大元问题
在规模为n的数据元素集合中找出最大元。当n=2时,一次比较就可以找出两个数据元素的最大元和最小元。当n>2时,可以把n个数据元素分为大致相等的两半,一半有n/2个数据元素,而另一半有n/2个数据元素。 先分别找出各自组中的最大元,然后将两个最大元进行比较,就可得n个元素的最大元
3.1 分析
使用分治法寻找给定集合中的最大元素。代码中的 maxN
函数采用递归方式,将集合划分为较小的子集,直到子集中只包含一个元素时返回该元素作为最大元素。然后不断地比较左右子集的最大元素,最终得到整个集合的最大元素。
3.2 代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <math.h>
using namespace std;
int maxN(int *a,int l,int r)
{
//单个元素时自身最大
if(l==r)
return a[l];
//两个元素时返回二者中的最大元素
else if(l+1==r)
return (a[l] > a[r]) ? a[l] : a[r];
//两个元素以上进行分治递归
else
return (maxN(a, l, (l + r) / 2) > maxN(a, (l + r) / 2 + 1, r)) ? maxN(a, l, (l + r) / 2) : maxN(a, (l + r) / 2 + 1, r);
}
int main()
{
int n, i;
cout << "输入集合元素个数:";
cin >> n;
int *a = new int[n];
cout << "数据集合元素:";
for (i = 0; i < n; i++)
{
cin >> a[i];
}
int m;
m = maxN(a, 0, n-1);
cout << "集合最大元为:";
cout << m;
return 0;
}