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

本文介绍递归与分治算法的设计思想,并通过棋盘覆盖、合并排序及集合最大元问题三个实例展示其实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

NEFU,计算机与控制工程学院,基于C/C++的算法设计与分析课程

实验一 递归与分治算法设计

环境

操作系统:Windows 10
IDE:Visual Studio Code、Dev C++ 5.11、Code::Blocks

说明

 “实验一 递归与分治算法设计” 包含以下问题

  1. 棋盘覆盖问题
  2. 合并排序问题
  3. 集合最大元问题

其他联系方式:

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;
}

3.3 测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值