一、分治策略的基本想法
在分治策略中,我们递归的求解一个问题,在每层递归中应用如下三个步骤:
分解步骤将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小。
解决步骤递归地解出子问题。如果子问题地规模足够小,则停止递归,直接求解。
合并步骤将子问题的解组合成原问题的解。
上述是《算法导论》中一书对分治策略步骤的归纳,怎样?是不是感觉云里雾里,不知所然。其实字面意思很好理解无非就是先分,分到足够小然后解小问题再合并,但是在实际应用中,怎么分?怎么判断最小?怎么合并?都是此算法的难点。对于这些较为抽象的算法描述,我的一贯做法是通过实例去加以解读。下面给出几个利用分治策略的典型例子,从实例中真正体会分治法的妙用。
此外补充一个小点:当我们想到分治法时很自然的就想到了递归,这是没有问题的,分治法的思想本身也是源于递归,但是需要知道的是迭代法也可以实现分治法。
二、典型例题
1.二分检索
问题:在有序表中(以经按照关键字非减排序),搜索给定元素。在此处我们仅考虑数组,即查找有序数组中的某一元素并返回下标。
按照常规想法是从数组的第一个元素开始顺序查找,查到到给定元素就停止,并返回下标。此处我们采用分治法,首先将大规模的数组划分为小规模,划分到只有一个元素为最小规模,这个时候就开始解决问题,然后合并。代码如下:
#include<iostream>
#include<vector>
using namespace std;
int BinarySearch(vector<int>& A, int low, int high,int setvalue); //二分查找函数
int main()
{
vector<int> test_array = {1,3,5,8,9};
cout << "测试数组中5的小标为:" << BinarySearch(test_array, 0, 4, 5)<<endl;
return 0;
}
/*======================================================================
* 函数功能:二分查找元素,返回下边
* 形参意义:A;查找数组,low:左最小下标,high:右最大下边,setvalue:查找值
* 注意:分治法划分规模,一般划分下边即可,[low,high]为一个更小规模
========================================================================*/
int BinarySearch(vector<int>& A, int low, int high,int setvalue)
{
int mid = (low + high) / 2;
if (A[mid] == setvalue)
{
return mid;
}
else if (A[mid] > setvalue && low != high)
{
return BinarySearch(A, low, mid, setvalue); //中值大于查找值,接下来向左[low,mid]查找
}
else if (A[mid] < setvalue && low != high)
{
return BinarySearch(A, mid + 1,high, setvalue); //中值小于查找值,接下来向[mid+1,high]查找
}
else {
return -1; //当low==high时,并且A[mid]!=stevalue返回-1,说明查找失败,数组中没有这个值
}
}
结果如下:
2.二分归并排序
把两个有序的序列排成一个有序序列。根据下图我对归并排序做一个解读。
首先是递归法的基本想法,把整体划分为小规模问题,即划分数组,以下是对上述实例套用下面代码的一个过程介绍,蓝色数字标注的就是代码实际运行中执行步骤顺序,你可以对照BinarySearch函数自己脑动跑一遍就行。
#include<iostream>
#include<vector>
using namespace std;
void BinaryMergeSort(int A[], int low, int high);
void Merge_guo(int A[], int low, int mid, int high);
int main()
{
int testdata[9]= { 2,4,6,19,1,7,22,34,7 };
BinaryMergeSort(testdata, 0, 8);
for (int i = 0; i < 9; i++)
{
cout << "第" << i << "个数:" << testdata[i]<<endl;
}
return 0;
}
void BinaryMergeSort(int A[], int low, int high)
{
int mid = 0;
if (low < high)
{
mid = (low + high) / 2;
BinaryMergeSort(A, low, mid);
BinaryMergeSort(A, mid + 1, high);
Merge_guo(A, low, mid, high);
}
}
void Merge_guo(int A[], int low, int mid, int high)
{
int n1 = mid - low + 1;
int n2 = high - mid;
vector<int> L(n1+1);
vector<int> R(n2+1);
for (int i = 0; i < n1; i++)
{
L[i] = A[low + i];
}
for (int j = 0; j < n2; j++)
{
R[j] = A[mid + 1 + j];
}
L[n1] = INT_MAX;
R[n2] = INT_MAX;
int i = 0;
int j = 0;
int z = 0;
while (z<(high-low+1))
{
if (L[i] <= R[j])
{
A[z+low] = L[i];
i++;
z++;
}
else {
A[z+low] = R[j];
j++;
z++;
}
}
}
3.最大子段和
问题描述如下:
#include<iostream>
#include<vector>
using namespace std;
int findsubarray(vector<int>& nums ,int low,int mid,int high);
int findMaxsubarray(vector<int>& nums, int low, int high);
int main()
{
vector<int> A = { 13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7 };
int output=findMaxsubarray(A, 0, 15);
cout << output << endl;
return 0;
}
int findsubarray(vector<int>& nums, int low, int mid, int high)
{
int left_sum = INT_MIN;
int sum = 0;
int left = 0;
for (int i = mid; i >= low; i--)
{
sum = sum + nums[i];
if (sum > left_sum)
{
left_sum = sum;
left = i;
}
}
int right_sum = INT_MIN;
sum = 0;
int right;
for (int j = mid + 1; j <= high; j++)
{
sum = sum + nums[j];
if (sum > right_sum)
{
right_sum = sum;
right = j;
}
}
return left_sum + right_sum;
}
int findMaxsubarray(vector<int>& nums, int low, int high)
{
if (low >= high)
{
return nums[low];
}
else
{
int mid = (low + high) / 2;
int left_sum = findMaxsubarray(nums, low, mid);
int right_sum = findMaxsubarray(nums, mid + 1, high);
int across_sum = findsubarray(nums, low, mid, high);
if (left_sum >= right_sum && left_sum > +across_sum)
{
return left_sum;
}
else if (right_sum >= left_sum && right_sum >= across_sum)
{
return right_sum;
}
else {
return across_sum;
}
}
}