分治:也就是分而治之,就是把一个复杂的问题分成两个或更多的相同或者相似的子问题,再把子问题分成更小的子问题......直到最后子问题可以简单地直接求解。
这里所有子问题都和它的原问题有相同或者相似的操作。解决原问题的操作 就是 解决所有子问题的操作的合并。
迭代:迭代是对一定步骤的重复执行,在每次执行这些步骤时,都从变量的原值推出它的一个新值,直到最后满足条件停止迭代。
递归:递归就是在函数解决问题时在函数体内调用自身。“递”就是函数一直传递下去直到达到边界条件。“归”就是从最低层的那个函数(运行后并结束)返回它的上一级,然后层层返回,最后返回最外面的函数,此时最外层的函数才运行结束。
这里要重点说明一下分治,分治有一个坑,我一直以为分治就是一个问题A的操作只有分为问题B和问题C,然后问题B和问题C很好解决,然后问题A就解决了,光看定义很容易就得出这个结论。
事实上不是这样,A不会其他什么都不做只是分成B和C,不然B和C也会什么都不做,因为它们都有相同或类似的操作。
举个例子:斐波那契数列求n,用分治的话求n的操作就变为求n-1和n-2加上返回这个两个子问题结果的合,即 return=Fibon(n-1)+Fibon(n-2) 。除了分成两个子问题还有求合这个操作。
下面继续用归并排序和快速排序理解分治。
归并排序中要排好一个待排序列的操作包括以下三个操作:
- 找一个中间点center。
- 使center的左半边序列有序和右半边序列有序这两个子问题,
- 还有利用子问题的解进行归并这个操作。
void MSort(ElemType A[],ElemType TmpA[],int low,int high)//归并排序
{
if(low<high)//递归结束的条件是待排序列只有一个记录
{
int center=(low+high)/2;//从中间划分两个子序列
MSort( A[],TmpA[],L,center);对左侧子序列进行递归排序
MSort( A[],TmpA[],center+1,high);对右侧子序列进行递归排序
Merge( A[],TmpA[],low,center, high);前两步说明左右子序列已经有序了,进行归并
}
}
这些子问题都是这样的操作,最后的子问题判断了一下if不符合条件然后退出,属于简单求解)
而快速排序中要排好一个待排序列的操作包括:
- 一次划分操作找到枢轴位置。
- 使它的枢轴左半边序列有序和枢轴右半边序列有序这个两个子问题。
void Quick_Sort(ElemType A[],int low,int high)//快速排序
{
if(low<high)//递归跳出的条件
{
int pivotpos=Partition(A,low,high);
Quick_Sort( A,low,pivotpos-1);
Quick_Sort( A,pivotpos+1,high);
}
}
回归正题,分治是一种解决问题的方法,迭代和递归是实现分治法的具体方法。我们以求斐波那契数列的第n个数为例。
分治法:要求第n个数,我们求n-1和n-2之和。求n-1又分为求n-2和n-3之和,求n-2又分为求n-3和n-4之和,直到最后我们已经知道了第一个数和第二个数是多少。
迭代实现:我们要从最简单的子问题开始迭代。
当n=1或者2时,我们直接解决了问题。
当n=3时,我们发现n不是1和2然后迭代到了3,然后解决了问题。
当n=6时,我们一直从3迭代到4然后5然后6就解决了问题。
这个过程就是解决小问题然后接着解决了大问题的过程,而且也满足对一定步骤重复执行的操作,也满足都从变量的原值推出它的一个新值,直到最后满足条件停止迭代。
#include <stdio.h>
int Fibon(int n)
{
if(n == 1 || n == 2)return 1;
int first = 1;
int second = 1;
int temp = 0;
for (int i = 0; i < n - 2; i++)
{
temp = first + second;
first = second;
second = temp;
}
return temp;
}
int main()
{
int n;
scanf("%d", &n);
int Fibon_n = Fibon(n);
printf("%d", Fibon_n);
return 0;
}
递归实现:编译器是用栈实现递归的,相当于自动把问题一层一层分解为子问题压入栈中,最后再逆序一层一层回退,这与分治的解决问题的思路一致。
#include <stdio.h>
int Fibon(int n)
{
if (n == 1 || n == 2) return 1;
return Fibon(n - 1) + Fibon(n - 2);
}
int main()
{
int n;
scanf("%d", &n);
int Fibon_n = Fibon(n);
printf("%d", Fibon_n);
return 0;
}
我们可以看出递归和迭代都是对分治法的具体实现。
递归的优点是代码简洁,更容易理解。缺点是容易栈溢出且效率太低,可能会包含许多重复运算。
迭代的优点就是递归的缺点,迭代的缺点是复杂情况时代码需要考虑很多边界情况。