分治法介绍(以及在棋盘覆盖问题的使用)

分治法


1.基本概念

​ 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个更多的相同或相似的**子问题,再把子问题分成更小的子问题**……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……

​ 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。

​ 例如,对于 n 个元素的排序问题,当 n=1 时,不需任何计算。n=2 时,只要作一次比较即可排好序。n=3 时只要作 3 次比较即可,…。而当 n 较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。



2.基本思想以及策略

  • 分治法的设计思想: 将一个难以解决的 大问题—>小问题,逐一击破
  • **分治策略是:**对于一个问题规模为 n的问题,分解到 **k**个小问题;最后将各个 子问题的解合并得到原问题的解注:分割到 最后 子问题 可以得到答案的程度,eg:求解斐波那契数列最小子问题就是前两项 分别为1,1 …
  • 我们通常使用递归的方法来当做分治法的手段

3.分治法的特征

分治法所能解决的问题一般具有以下几个特征:

  1. 该问题的规模缩小到一定的程度就可以容易地解决
  2. 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
  3. 利用该问题分解出的子问题的解可以合并为该问题的解
  4. 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。

第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;

第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。

第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。

4.递归框架

function (parameters){  //parameter 参数
	//递归出口
	if(parameters == [某个值]){
		return;
	}
	//分割问题
	if([某情况..]){
		function( new parameters);
	}...........
 	
}

例题:棋盘覆盖问题

棋盘覆盖问题的描述:

image-20240530000915845

棋盘覆盖问题的分治求解思路:

img

1.解题思路

1

​ 不难看出我们可以把一个大的棋盘分割成小的,当分割到1*1时就是一种临界值,子棋盘大小size == 1 时 就是递归出口!

​ 我们在画一个8*8的棋盘看看是否是这个规律

​ 不难看出分割后规律也是如此,

  • **总结:**不断分割棋盘,把分割后的棋盘进行判断,如果子棋盘无特殊方块,那我们就设置一个,如上图,所示

代码实现过程

主要框架

// 分割函数
int title = 0;
void DivideQiPan(QiPan* Q, int tr, int tc, int dr, int dc, int size){
    // 结束条件 递归出口
    if (size == 1) {
        return;
    }

    // 分割(形式上分割)
    int t = ++title;
    int s = size / 2;

    // 左上方
    if (dr < tr + s && dc < tc + s) {
        DivideQiPan(Q, tr, tc, dr, dc, s);
    } else {
        Q->Qi[tr + s - 1][tc + s - 1] = t;
        DivideQiPan(Q, tr, tc, tr + s - 1, tc + s - 1, s);
    }

    // 右上方
    if (dr < tr + s && dc >= tc + s) {
        DivideQiPan(Q, tr, tc + s, dr, dc, s);
    } else {
        Q->Qi[tr + s - 1][tc + s] = t;
        DivideQiPan(Q, tr, tc + s, tr + s - 1, tc + s, s);
    }

    // 左下方
    if (dr >= tr + s && dc < tc + s) {
        DivideQiPan(Q, tr + s, tc, dr, dc, s);
    } else {
        Q->Qi[tr + s][tc + s - 1] = t;
        DivideQiPan(Q, tr + s, tc, tr + s, tc + s - 1, s);
    }

    // 右下方
    if (dr >= tr + s && dc >= tc + s) {
        DivideQiPan(Q, tr + s, tc + s, dr, dc, s);
    } else {
        Q->Qi[tr + s][tc + s] = t;
        DivideQiPan(Q, tr + s, tc + s, tr + s, tc + s, s);
    }
}

完整代码

#include <stdio.h>
#define MAXSIZE 16
int title = 0;

//棋盘结构体
//------------------------------
typedef struct {
    int Qi[MAXSIZE][MAXSIZE];
    int size;
} QiPan;
//------------------------------

//-------------------算法函数----------------------

/*
 * 初始化棋盘*/
QiPan initQiPan(QiPan Q);

/*
 * 输入特殊方块*/
void input(int *tr,int*tc);
/*
 * 分割统计棋盘*/
void DivideQiPan(QiPan* Q,int tr,int tc,int dr,int dc,int size);

/*
 *遍历棋盘
 */
void TraverseQiPan(QiPan Q){
    for (int i = 0; i < Q.size; ++i) {
        for (int j = 0; j < Q.size; ++j) {
            printf("%d\t",Q.Qi[i][j]);
        }
        printf("\n");
    }
}

//------------------------------------------------

int main() {
//    特殊方块行和列
    int dr; // 行
    int dc; // 列

//    分割后的 棋盘左上方第一个开始位置 用于判断特殊方块位置
    int tr = 0;
    int tc = 0;
    QiPan Q = {};
//    初始化棋盘
    Q = initQiPan(Q);
    printf("分割前的棋盘\n");
    printf("-------------------------------------------------------------\n");
    TraverseQiPan(Q);
    printf("-------------------------------------------------------------\n");
//    输入特殊方块
    input(&dr,&dc);
//    开始分治法填充棋盘
    DivideQiPan(&Q,tr,tc,dr-1,dc-1,Q.size);

    printf("分割后的棋盘\n");
    printf("-------------------------------------------------------------\n");
    TraverseQiPan(Q);
    printf("-------------------------------------------------------------\n");
    printf("一共填充了 %d 个目标方块",title);

}
//--------------------------------------------------------------

//-----------------------函数实现---------------------------------
/* 判断一个数是否为 2 的 次方
 * if(n <= 0) {
        return 0;
    }
    return (n & (n - 1)) == 0;
 * */
QiPan initQiPan(QiPan Q) {
    int mark = 1;
    do {
        printf("请输入棋盘的长度(2^n 且 棋盘长度小于16)长度\n");
        scanf("%d", &Q.size);
        int temp = Q.size;
//        判断Q.size输入是否合法
        if ((temp & (temp - 1)) == 0) {
            printf("输入合法!\n");
            mark = 0;
        } else {
            printf("输入不合法!");
        }
    } while (mark);
//    给矩阵初始化为 0
    for (int i = 0; i < Q.size; ++i) {
        for (int j = 0; j < Q.size; ++j) {
            Q.Qi[i][j] = 0;
        }
    }
    return Q;
}
void input(int *dr,int*dc){
    printf("请输入特殊方块位置:\n");
    printf("请输入行: ");
    scanf("%d",dr);
    printf("请输入列: ");
    scanf("%d",dc);
    printf("\n");
}
void DivideQiPan(QiPan* Q, int tr, int tc, int dr, int dc, int size) {
    // 结束条件 递归出口
    if (size == 1) {
        return;
    }

    // 分割(形式上分割)
    int t = ++title;
    int s = size / 2;

    // 左上方
    if (dr < tr + s && dc < tc + s) {
        DivideQiPan(Q, tr, tc, dr, dc, s);
    } else {
        Q->Qi[tr + s - 1][tc + s - 1] = t;
        DivideQiPan(Q, tr, tc, tr + s - 1, tc + s - 1, s);
    }

    // 右上方
    if (dr < tr + s && dc >= tc + s) {
        DivideQiPan(Q, tr, tc + s, dr, dc, s);
    } else {
        Q->Qi[tr + s - 1][tc + s] = t;
        DivideQiPan(Q, tr, tc + s, tr + s - 1, tc + s, s);
    }

    // 左下方
    if (dr >= tr + s && dc < tc + s) {
        DivideQiPan(Q, tr + s, tc, dr, dc, s);
    } else {
        Q->Qi[tr + s][tc + s - 1] = t;
        DivideQiPan(Q, tr + s, tc, tr + s, tc + s - 1, s);
    }

    // 右下方
    if (dr >= tr + s && dc >= tc + s) {
        DivideQiPan(Q, tr + s, tc + s, dr, dc, s);
    } else {
        Q->Qi[tr + s][tc + s] = t;
        DivideQiPan(Q, tr + s, tc + s, tr + s, tc + s, s);
    }
}

结果

image-20240530001658888

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

崔渭阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值