分治法小总结--例题(棋盘覆盖问题,归并排序,快速排序,循环赛日程表,整数因子分解,半数集)

##目录

  • 分治法基本思想
  • 分治法的使用前提
  • 分治法的解题步骤
  • 分治法的时间复杂度
  • 棋盘覆盖问题
  • 归并排序和快速排序
  • 循环赛日程表
  • 整数因子分解
  • 半数集

###分治法基本思想

相关的基本知识:

  • 就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  • 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
  • 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

###分治法的使用前提
前提:

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

注意:

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

###分治法的解题步骤
步骤

  • 划分问题:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  • 递归求解:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
  • 合并问题:将各个子问题的解合并为原问题的解。

###分治法的时间复杂度
一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:
T(n) = k*T(n/m) + f(n)。


###棋盘覆盖问题
棋盘覆盖问题:

在一个2^k * 2^k的方格中,由一个特殊棋盘,要你用四种不同形态的L型骨牌覆盖除了这个特殊棋盘以外的棋盘。
分治法求解思路:
特殊方格必定位于4个较小棋盘之中,其余3个子棋盘中无特殊方格。为了这三个无特殊棋盘的子棋盘转换为特殊棋盘,我们可以用一个L型骨牌来覆盖这三个较小棋盘的汇合处(每次覆盖一个(相当于覆盖之后就变成了特殊棋盘)),然后原问题转换成四个较小的子问题,递归的使用这种分割,知道棋盘转换成11的棋盘。
时间复杂度分析:
当k = 0(k就是2^k中的k)时,T(k) = O(1); 当k大于0时T(k) = 4
T(k-1) + O(1); 所以可以得到T(k) = O(4^k);
这里写图片描述

import java.io.BufferedInputStream;
import java.util.Scanner;

/**
 * 利用分治法解决棋盘覆盖问题
 * @author 郑鑫
 */

public class ChessBoard {
	
	private static int title;
	private static int[][] board;
	
	//tr,tc棋盘左上角方格的行号和列号,dr,dc表示特殊方格的行号和列号
	public static void chessBoard(int tr,int tc,int dr,int dc,int size){ //size表示的是2*k -->棋盘大小2*k * 2*k 
		if(size == 1)return;
		int t = title++;  //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;  //在左上角棋盘的右下方填充一下
			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);
		}
		
	}
	
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int size = cin.nextInt();
		title = 1;
		board = new int[size+1][size+1];
		chessBoard(0,0, 0, 1, size);
		
		System.out.println("------输出覆盖后的棋盘(特殊棋盘用0表示)-------");
		for(int i = 0; i < size; i++){ 
			for(int j = 0; j < size; j++){
				System.out.print(board[i][j] + " ");
			}
			System.out.println();
		}
	}
}

运行效果
这里写图片描述


###归并排序和快速排序
这两个也是分治法很好的一个例子,具体的算法过程在各种排序算法总结已经分析过。这里不再细说。


###循环赛日程表

循环日程表问题。n=2^k个运动员进行网球循环赛,需要设计比赛日程表。每个选手必须与其他n−1个选手各赛一次;每个选手一天只能赛一次;循环赛一共进行n−1天。按此要求设计一张比赛日程表,该表有n行和n−1列,第i行j列为第i个选手第j天遇到的选手。
也是使用分治法解决,和棋盘覆盖问题有点像。
我们将所有选手对分成两半,n个选手的比赛日程表就可以通过为n/2个选手设计的比赛日程表来决定,递归的用这种一根为二的策略对选手进行分割,直到只剩下两个选手时,比赛日程表的指定就变得很简单了,这时只要这两个选手进行比赛就可以了。
这里写图片描述

import java.io.BufferedInputStream;
import java.util.Scanner;

/**
 * 用分治法解决循环日程表安排问题
 * @author 郑鑫
 */
public class CirculateSchedule {
	private static int[][] map;
	// 将2^k * 2^k的表格分成2^(k-1)*2^(k-1)的四个子表格
	// 每个表格的左上角赋值
	// 左上子表格等于右下子表格,右上子表格等于左下子表格
	// 右上子表格等于左上子表格加上子表格大小
	public static void Table(int r,int c,int size){
		if(size == 1)return;
		int half = size/2;
		map[r + half][c + half] = map[r][c];
		map[r+half][c] = map[r][c+half] = map[r][c]+half;
		Table(r, c, half);
		Table(r, c+half, half);
		Table(r+half, c, half);
		Table(r+half, c+half, half);
	}
	
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int n = cin.nextInt();
		map = new int[n+1][n+1];
		map[1][1] = 1;  
		Table(1, 1, n);
		for(int i = 1; i <= n; i++){
			System.out.print(i + " ");
			for(int j = 2; j <= n; j++){
				System.out.print(map[i][j] + " ");  
			}
			System.out.println();
		}
	}
}

效果
这里写图片描述


###整数因子分解
整数因子分解问题
问题描述:

大于1 的正整数n 可以分解为:n=x1 x 2xm 。 例如,当n= 12
时,共有8 种不同的分解式: 12= 12; 12=6
2; 12=43; 12=34; 12=322; 12=26;
12=2
32; 12=22*3。 对于给定的正整数n,编程计算n 共有多少种不同的分解式。

数据输入:
输入数据第一行有1 个正整数n (1≤n≤2000000000) 。
结果输出:
将计算出的不同的分解式数。
输入 : 12 输出 : 8

解题思路 : 递归所有可能被分解的因子,然后一次除尽到1总的个数就加一。

import java.io.BufferedInputStream;
import java.util.Scanner;
/**
 * 整数因子分解: 递归分治
 * @author 郑鑫
 */
public class Factoring {
	private static int sum;
	public static void solve(int n){
		if(n == 1) sum ++;
		else for(int i = 2; i <= n; i++)if(n % i == 0)solve(n/i);
	}
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int n = cin.nextInt();
		sum = 0;
		solve(n);
		System.out.println(sum);
	}
}

###半数集
问题描述:
给定一个自然数n,由n 开始可以依次产生半数集set(n)中的数如下。
(1) n∈set(n);
(2) 在n 的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如,set(6)={6,16,26,126,36,136}。半数集set(6)中有6 个元素。
注意半数集是多重集。

解题思路:也是运用递归分治的思想,很容易看出以下公式:
set(n) = set(i)(i : 1~n/2) + 1;然后根据递归的思想求解即可。
这里可以设置一个标记数组避免计算重复的子问题(计算重复的子问题的和)

import java.io.BufferedInputStream;
import java.util.Scanner;

/**
 * 半数集问题
 * @author 郑鑫
 */
public class CompSet {
	private static long[] vis;
	public static long comp(int n){
		long ans = 1;
		if(vis[n] > 0)return vis[n];
		for(int i = 1; i <= n/2; i++)ans += comp(i);
		vis[n] = ans;
		return ans;
	}
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int n = cin.nextInt();
		vis = new long[n+1];
		for(int i = 0; i <= n; i++)vis[i] = 0;  //先规0
		System.out.println(comp(n));
	}
}

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值