开开心心学算法:分治策略求解问题

分治策略求解问题

  • 最大子数组问题
    • 问题描述及思路
    • 代码
    • 时间复杂度分析
  • 汉诺塔
    • 问题描述及思路
    • 代码
  • 参考书籍

最大子数组问题

问题描述及思路

该问题的特点,数组必须有正有负。

问题描述:找到数组中连续相加和最大的子数组;
思路:

  1. 将原问题分解成子问题进行求解
  2. 子问题包括,两个与原问题形式相同的子问题,一个与原问题形式不同的子问题
  • 假设原问题的区间为[low, high],则与原问题形式相同的子问题的区间分别为[low, mid]和[mid +1, high](其中mid = (low + high)/2,向下取整);之后将在子问题中寻找连续相加和最大的子数组。
    -而与原问题形式不同的子问题需要单独写函数求解,该函数需要解决跨中心最大子数组,也就是说连续相加和最大的子数组即不在[low, mid]中,也不在[mid+1, high]中,而是在[i, j]中(其中,low <= i <= mid, mid+1 <= j <= high)。如下图所示:
    开开心心学算法:分治策略求解问题_第1张图片
    对于每个子问题而言同样可以划分为上述三个更小的子问题,因此,我们采用递归的方法来将问题进行逐级划分。

代码

// 求解跨越中心的最大子数组
int cross_max_subarray(vector<int> & a, int & low, int mid, int & high) {
	int sum_L = 0, sum_R = 0;		// 左右两边的累加和
	int max_L = INT_MIN;		// 左边的最大值
	int max_R = INT_MIN;		// 右边的最大值
	int min_i;					// 左边最大值的脚标
	int max_j;					// 右边最大值的脚标

	// 从mid往low计算子数组的最大和
	for (int i = mid; i >= low; i--) {
		sum_L += a[i];
		if (sum_L > max_L) {
			max_L = sum_L;
			min_i = i;
		}
	}

	// 从mid+1往high计算子数组最大和
	for (int j = mid + 1; j <= high; j++) {
		sum_R += a[j];
		if (sum_R > max_R) {
			max_R = sum_R;
			max_j = j;
		}
	}

	low = min_i;		// 返回左边脚标
	high = max_j;		// 返回右边角标

	return max_L + max_R;
}

// 递归求解原问题
int max_subarray(vector<int> &a, int low, int high) {
	if (low == high)
		return a[low];
	else{
		int mid = (low + high) / 2;
		int L, R, M;
		L = max_subarray(a, low, mid);				// 求解左边的子问题
		R = max_subarray(a, mid + 1, high);			// 求解右边的子问题
		M = cross_max_subarray(a, low, mid, high);	// 求解跨中心的子问题

		//cout << low << "," << high << endl;		// 输出每次最大子数组的区间,最后一个为最大的数组区间

		// 判断三个子问题中哪个返回的值最大
		if (L >= R && L >= M)
			return L;
		else
			if (R >= L && R >= M)
				return R;
			else
				return M;
		
	}
}

时间复杂度分析

  1. 对于函数cross_max_subarray()而言,每执行一次for循环花费 θ ( 1 ) \theta(1) θ(1)的时间,两个for循环分别执行了mid - low +1和high - mid次循环,因此总的循环次数为(mid - low +1) + (high - mid) = high - low +1 = n。因此该函数所花费的时间为 θ ( n ) \theta(n) θ(n)
  2. 对于函数max_subarray()而言,假设该函数的时间复杂度为T(n),同时假设该问题的规模是2的幂。if语句所花费的时间为 θ ( 1 ) \theta(1) θ(1),mid求解所花费的时间为 θ ( 1 ) \theta(1) θ(1),两个同形式的子问题的时间复杂度均为T(n/2),由于原问题被分成了两个同样规模的子问题n/2。而不同形式的子问题的时间复杂度我们在上面已经求解过了,为 θ ( n ) \theta(n) θ(n)。后面判断大小的if-else语句所花费的时间都是 θ ( 1 ) \theta(1) θ(1)。所以,可以得出当n>1时,该函数的时间复杂度为T(n) = 2T(n/2) + θ ( n ) \theta(n) θ(n)。而当n=1时,T(n) = θ ( 1 ) \theta(1) θ(1)
    时间复杂度

汉诺塔

问题描述及思路

如下图所示:
开开心心学算法:分治策略求解问题_第2张图片
图中有三个圆柱A,B,C。其中A圆柱上有三个圆盘,现在我们要将这三个圆盘全部放到C圆柱上,应该如何移动圆盘?当前圆盘个数为1个时该如何移动?当圆盘个数为2个时该如何移动?为n个时呢?(注:圆盘的放置规则是由上到下一次增大。)
问题分析: 我们设置圆盘的个数为n,那么该问题可以简化为三种情况

  1. n = 1时,那么直接将圆盘从A住移到C柱(符号表示A → C)即可;
  2. n = 2时,先将小圆盘从A → B,在将大圆盘从A → C,最后将小圆盘从B → C;
  3. n > 2时,此时的问题变得复杂了,我们可以将该问题分解成小问题,而小问题的形式与n = 2时的情况一模一样。首先,我们将上面 n-1个 圆盘看作一个整体,并把这个整体看作是一个小圆盘,然后,我们再将 第n个 圆盘看成是大圆盘。那么我们的问题就变成了如何将两个圆盘从A → C。这个问题是不是就和n = 2时的问题是一个问题了?大胆回答:是的!所以圆盘的移动轨迹是:A → B(n-1个圆盘),A → C(第n个圆盘),B → C(n-1个圆盘)。
    因此,实际情况就两种:
    移 动 步 骤 = { A → C , n = 1(终止条件) A → B , A → C , B → C , n >1(递归) 移动步骤 = \begin{cases} A → C, & \text {n = 1(终止条件)} \\ A → B,A → C,B → C, & \text {n >1(递归)} \end{cases} ={AC,ABACBCn = 1(终止条件)n >1(递归)
    看图:
    开开心心学算法:分治策略求解问题_第3张图片
    是不是会有小伙伴疑惑我知道如何移动一个圆盘,不知道如何移动n个圆盘啊!!!
    咱们都已经将问题分解了!肯定使用分治法来求解原问题啦!也就是一层层剥皮,我要移动n个,那么可不可以先移动n-1个?我要移动n-1个那我可不可以先移动n-2个?…直到我剥到1个不就ok了!

代码

/// 说明:
/// 设要移动的圆盘数位n,A B C为柱子编号
/// ①当n = 1时,直接将圆盘从A移到C ———— 可作为终止条件
/// ②当n = 2时,先将第一个圆盘移到B,再将第二个圆盘移到C,最后将第一个圆盘移到C。那么圆盘的运行轨迹为:A-B,A-C,B-C
/// ③当n > 2时,可以将1到n-1个圆盘当成一个整体,作为第一个圆盘,而第n个圆盘单独作为一个整体,作为第二个圆盘,那么它们的移动轨迹为:A-B(n-1个),A-C(第n个),B-C(n-1个)
/// 使用hanoi(n, A, B, C)对圆盘进行移动,该调用函数表示,有n个圆盘从A移到C。

#include 
using namespace std;

int k = 0;			// 总移动次数

// 递归操作
void hanoi(int n, char A, char B, char C) {
	/// 函数说明:
	/// 1. A移到C,B为辅组。
	// 终止条件:只有一个圆盘,直接A-C
	if (n == 1) {
		cout << "将1个圆盘从" << A << "移到" << C << endl;
		k += 1;
	}
		
	else {
		hanoi(n - 1, A, C, B);   // 将n-1个圆盘从A移到B
		cout << "将" << n - 1 << "个圆盘从" << A << "移到" << C << endl;// 将第n个圆盘从A移动C
		k += 1;
		hanoi(n - 1, B, A, C);	// 将n-1个圆盘从B移到C
	}
}

int main() {
	int n;
	cout << "输入圆盘个数:";
	cin >> n;
	// A B C为柱子编号
	hanoi(n, 'A', 'B', 'C');

	cout << "共移动了" << k << "次" << endl;

	cin.get();
	cin.get();
	return 0;
}

参考书籍

[1] 《算法导论》
[2] 《经典算法大全》

你可能感兴趣的:(算法)