递归思路
1、找重复:找出子问题
2、找变化:变化的量应该作为参数;
3、找边界:设计函数的出口;
T 1:求阶乘
输入n,使用递归的方法输出n!
int Rec(int n){ if(n==1) return 1; return n*Rec(n-1);}
T 2:求数组元素和
对arr的所有元素求和
int Rec(vector arr,int begin){ if (begin == arr.size() - 1) return arr[begin]; return arr[begin] + Rec(arr,begin + 1);}
T 3:斐波那契数列
f(0)=1
f(1)=1
f(2)=f(0)+f(1)=2
f(3)=f(1)+f(2)=3
f(n)=f(n-2)+f(n-1)
输入n,求f(n)
int Feb(int n){ if(n==0 || n==1) return 1; return Feb(n-2)+Feb(n-1); }
T 4:最大公约数
输入m,n,求它俩的最大公约数(m>n)
int Rec(int m,int n){ if(n==0) return n; return Rec(n,m%n)}
T 5:插入排序改递归
使用递归的方法对数组arr进行排序;
void insertSort(vector &arr, int pos)//对arr[0]-arr[pos]进行排序{ if (pos == 0) return; insertSort(arr, pos - 1);//等pos前面的元素都排序完了,我再对pos元素进行排序; int p = pos; int val = arr[p]; while (p > 0 && val < arr[p - 1])//只要比前面的元素小,就继续向前找位置 { arr[p] = arr[p - 1]; p--; } //此时arr[p]>arr[p-1],在此位置插入; arr[p] = val;}
T 6:汉诺塔问题
A B C三处,将A处的n个盘子移动到C处,每次只能移动1个盘子,下盘子只能放在大盘子上面;
1、找重复:把n块盘子从A移动到C,那么我只需叫人通过中介C帮我把n-1个盘子移动到B处,然后我把第n个盘子移动到C处,再叫别人把B处的n-1个盘子通过中介A挪到C处;
2、找变化:移动过程中,「初始地、中介、目的地以及目前移动的哪块盘子」都不一样,这些都是变化量;
3、找边界:如果n==0时,不存在第0块盘子,所以此时什么都不用做,返回即可;
void Hanuo(int n, char begin, char help, char dest){ if (n < 1) return; Hanuo(n - 1, begin, dest, help); cout << "move " << n << " from " << begin << " to " << dest << endl; Hanuo(n - 1, help, begin, dest);}int main(){ Hanuo(3, 'A', 'B', 'C'); return 0;}
T 7:二分查找的递归算法
对于有序数组arr,输入key,使用递归形式的二分查找方法查找key所对应的索引;
int BinarySearch(vector arr, int left, int right, int key){ if(left > right) //此时表示在数组arr中找不到该key值 return -1; //二分查找 int mid = left + (right - left) / 2; if (arr[mid] == key) return mid; else if (arr[mid] < key) { return BinarySearch(arr, mid + 1, right, key); } else { return BinarySearch(arr, left, mid - 1, key); }}
递归性能
递归的时间复杂度取决于子问题的分支数;
比如求阶乘的时间复杂度为O(n),而斐波那契时间复杂度大约为O(2^n^);
因为分支数越多意味着有更多的「冗余运算」,即多做了很多次重复的计算,这是就需要动态规划或者非递归的迭代形式来降低复杂度;
由于递归重复调用当前函数,函数主要使用栈空间,所以递归的空间复杂度也较高;
但递归有个最大的好处就是:容易理解;
总结
「递归就是偷懒」:
我解决问题的一个部分,剩下的交给别人处理;或者理解为一个蛋糕,我只切一小块,剩下的怎么分交给别人完成;