分治法
基本思想
(1) 将一个问题划分为同一类型的若干子问题,子问题最好规模相同。
(2) 对这些子问题求解(一般使用递归方法,但在问题规模足够小时,有时也会利用另一个算法)。
(3) 有必要的话,合并这些子问题的解,以得到原始问题的答案。
经典使用场景
1.归并排序
基本思想
归并排序,默认指二路归并排序。假设初始序列含有n个元素,则可看成n个有序的子序列,每个子序列的长度是1。然后将前后相邻的两个有序序列归并为一个有序序列。这与分治法**将一个问题划分为同一类型的若干子问题,对这些子问题求解,合并这些子问题的解的思路是一致的。
算法实现
// 归并排序
void merge(int arr[],int L,int M,int R){
int LEFT_SIZE = M-L;
int RIGHT_SIZE = R-M+1;
int leftArr[LEFT_SIZE];
int rightArr[RIGHT_SIZE];
int i; int j; int k;
//1.init new arr
for(i=L;i<M;i++){
leftArr[i-L] = arr[i];
}
for(i=M;i<=R;i++){
rightArr[i-M] = arr[i];
}
i=0;j=0;k=L;
// 2.first sort
while(i<LEFT_SIZE && j<RIGHT_SIZE){
if(leftArr[i] < rightArr[j]){
arr[k] = leftArr[i];
i++;
k++;
}
else
{
arr[k] = rightArr[j];
j++;
k++;
}
}
//3.end sort
while(i < LEFT_SIZE){
arr[k] = leftArr[i];
i++;
k++;
num++;
}
while( j<RIGHT_SIZE ){
arr[k] = rightArr[j];
j++;
k++;
}
}
void mergeSort(int arr[],int L,int R){
if( L==R ){
return;
}
else{
int M=(L+R)/2;
mergeSort(arr,L,M);
mergeSort(arr,M+1,R);
merge(arr,L,M+1,R);
}
}
2.快速排序
基本思想
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
算法实现
//归并排序
#include <iostream>
using namespace std;
int Partition(int r[ ], int first, int end);
void QuickSort(int r[ ], int first, int end);
int main( )
{
int i, n = 8, r[8] = {8,3,2,6,7,1,5,4};
QuickSort(r, 0, n-1);
for (i = 0; i < n; i++)
cout<<r[i]<<"\t";
return 0;
}
int Partition(int r[ ], int first, int end)
{
int temp, i = first, j = end;
while (i < j)
{
while (i < j && r[i] <= r[j]) j--; //右侧扫描
if (i < j)
{
temp = r[i]; r[i] = r[j]; r[j] = temp; //将较小记录交换到前面
i++;
}
while (i < j && r[i] <= r[j]) i++; //左侧扫描
if (i < j)
{
temp = r[i]; r[i] = r[j]; r[j] = temp; //将较大记录交换到后面
j--;
}
}
return i; //返回轴值记录的位置
}
void QuickSort(int r[ ], int first, int end) //快速排序
{
if (first < end)
{
int pivot = Partition(r, first, end); //划分,pivot是轴值的位置
QuickSort(r, first, pivot-1); //对左侧子序列进行快速排序
QuickSort(r, pivot+1, end); //对右侧子序列进行快速排序
}
}
3.二叉树问题
因为二叉树可以划分为同样类型的两个更小的组成部分——左子树和右子树,所以许多关于二叉树的问题都可以应用分治法来解决。二叉树是由三个基本单元组成:根结点、左子树和右子树。因此,若能依次遍历这三部分,就能遍历整个二叉树。
减治法
基本思想
(1) 建立原问题实例的解和同样问题较小实 例的解的关系。
(2) 并利用这种关系,从顶而下(递归地)或从底而上(非递归地)解决问题。
(3) 一般情况下,每次算法迭代,都消去一个常量和一个常数因子。
经典算法
1.插入排序
2.折半查找
(1)假币问题
// 减治法求解假币问题
int solve2(int low, int high) {
num2++;
if (low == high) return low; // 只有一个硬币
if (low == high - 1) { // 只有两个硬币
if (data[low] < data[high]) return low;
else return high;
}
int mid = (low + high) / 2;
int sum1=0, sum2=0;
if ((high - low + 1) % 2 == 0) { // 区间内硬币个数为偶数
sum1 = sum(low, mid);
sum2 = sum(mid + 1, high);
printf("硬币%d-%d和硬币%d-%d称重一次:", low, mid, mid + 1, high);
}
else { // 区间内硬币个数为奇数
sum1 = sum(low, mid - 1);
sum2 = sum(mid + 1, high);
printf("硬币%d-%d和硬币%d-%d称重一次:", low, mid - 1, mid + 1, high);
}
if (sum1 == sum2) {
printf("两者重量相同。\n");
return mid;
}
else if (sum1 < sum2) { // 假币在左区间
printf("前者重量轻。\n");
if ((high - low + 1) % 2 == 0) return solve2(low, mid); // 区间内硬币个数为偶数
else return solve2(low, mid - 1); // 区间内硬币个数为奇数
}
else { // 假币在右区间
printf("后者重量轻。\n");
return solve2(mid + 1, high);
}
}
int sum(int low, int high) { // 求data[low..high]的重量
int sum = 0;
for (int i = low; i <= high; i++) sum += data[i];
return sum;
}
(2)金块问题
(3)俄式乘法
(4)约瑟夫斯问题
3.选择问题
/PS:此文归并排序和假币问题的代码实现均借鉴于本社区其他两位博主,整理下来仅供个人学习复习使用,尚未实现的算法部分留待之后学习到该算法时进行完善/