递归基础练习
关于递归,有切蛋糕思维,或者找找递推公式
求阶乘
1
2
3
4
5
6
7
8
9
10
| /*
求n的阶乘
找重复:n*(n-1)的阶乘.求n-1的阶乘是原问题的重复,只是规模更小---(子问题)
找变化:变化的量应该作为参数
找边界: 出口判断
*/
int f1(int n){
if(n==1) return 1; //写递归首先想到出口判断
return n*f1(n-1);
}
|
打印 i ~ j
1
2
3
4
5
6
7
8
9
10
11
12
| /*
打印i~j
找重复:找规模更小的子问题 ,我做一部分,委托一部分
找变化:变化的量应该作为参数
找边界: 出口判断
*/
void f2(int i,int j){
if(i>j) return;
//思路就是我把 i 打印了,剩下的从 i+1 ~ j递归来做
cout<<i<<endl;
f2(i+1,j);
}
|
数组求和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| /*
数组求和
找重复:我求第一个,剩下的还是子问题.
找变化:变化的量应该作为参数
找边界: 出口判断
*/
int f3(int arr[],int len,int begin){//此处用到了加参数的技巧
if(begin==len) return arr[begin];
return arr[begin]+f3(arr,len,begin+1);
}
//C++中求数组长度要用模板技术
template<class T>
int length(T& a)
{
return sizeof(a)/sizeof(a[0]);
}
int main(){
int arr[] = {1,2,3,4,5,6};
int len = length(arr)-1;
cout<<f3(arr,len,0);
return 0;
}
|
斐波那契数列
1
2
3
4
5
6
| int f4(int n){
if(n==1||n==2){
return 1;
}
return f4(n-1)+f4(n-2);
}
|
最大公约数
1
2
3
| int gcd(int m,int n){
return n==0?m:gcd(n,m%n);
}
|
汉诺塔
1
2
3
4
5
6
7
8
9
| void printHanoiTower(int N,string from,string to,string help){
if(N==1){
cout<<"move "<<N<<" from"<<from<<" to "<<to;
return;
}
printHanoiTower(N-1,from,help,to);
cout<<"move "<<N<<" from "<<from<<" to "<<to<<endl;
printHanoiTower(N-1,help,to,from);
}
|
插入排序
插入排序递归解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| //插入排序的递归写法
void insertSort(int arr[],int k){
if(k==0) return;
//对前k-1个元素排序
insertSort(arr,k-1);
//把位置为k的元素插入到前面的部分
int x = arr[k];
int index = k-1;
while(index>-1&&x<arr[index]){
arr[index+1] = arr[index];
index--;
}
arr[index+1] = x;
}
|
二分查找递归解法
等价于三个子问题:
- 左边找(递归)
- 中间比
- 右边找(递归)
- 注意:左查找和右查找只选其一.
1
2
3
4
5
6
7
8
9
10
11
| int binarySearch1(int arr[],int low,int high,int key) {
if(low>high) return -1;
int mid = low+((high-low)>>1);//(low+high)>>>1;//防止溢出,移位也更高效
int midVal = arr[mid];
if(midVal<key)
return binarySearch1(arr,min+1,high,key);
else if(midVal>key)
return binarySearch1(arr,low,high-1,key);
else
return mid;
}
|
希尔排序
希尔排序也是一种插入排序,是简单插入排序的更高效的版本.也称为缩小增量排序,同时该算法是冲破O(n^2)的第一批算法之一.希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| void shellSort(int arr[],int n){
for(int interval = n/2;interval>0;interval=interval/2){
//间隔变化,也就是步长的变化,不断地缩小增量
for(int i = interval;i<n;i++){
int target = arr[i];
int j = i-interval;
while(j>-1&&target<arr[j]){
arr[j+interval] = arr[j];
j-=interval;
}
arr[j+interval] = target;
}
}
}
|
2的幂表
2的幂 | 准确值(X) | 近似值 | X字节转换成MB、GB等 |
---|
7 | 128 | | |
8 | 256 | | |
10 | 1 024 | 一千 | 1K |
16 | 65 536 | | 64K |
20 | 1 048 576 | 一百万 | 1MB |
30 | 1 073 741 824 | 十亿 | 1GB |
32 | 4 294 967 296 | | 4GB |
40 | 1 099 511 627 776 | 一万亿(trillion) | 1TB |
如何评估递归算法的复杂度
![](https://imgconvert.csdnimg.cn/)
排序算法稳定性
![](https://imgconvert.csdnimg.cn/)
题目1:小白上楼梯(递归设计)
小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式.
分析:这是个经典问题,可以可以倒推,先从最后一节台阶算起.多少步可以走完楼梯,然后倒数第一节台阶,看看多少种方式走完楼梯,以此类推,将大问题化简为等价子问题.
1
2
3
4
5
6
| int f(int n){
if(n==0) return 1;
if(n==1) return 1;
if(n==2) return 2;
return f(n-1)+f(n-2)+f(n-3);
}
|
题目2:旋转数组的最小数字(改造二分法)
把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转,输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素,例如{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.
分析:经过分析可以得出,经过旋转,中间切开,一边有序一边无序,最小值出现在无序的那一边,经过多次旋转之后,最小值会出现下最大值右边,也就是说,最后剩下两个元素,一个是最大值,一个是最小值.
(这题有点晕,先贴代码.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| int min(int arr[],int n){
int begin=0;
int end=n-1;
//考虑没有旋转这种特殊的旋转
if(arr[begin]<arr[end]) return arr[begin];
//begin和end指向相邻元素,退出
while(begin+1<end){
int mid=begin+((end-begin)>>1);
//要么左侧有序,要么右侧有序
if(arr[mid]>=arr[begin]){//左侧有序
begin=mid;
}else{
end=mid;
}
}
return arr[end];
}
|
这个代码还是有问题的,中间的值和右边的值相等的时候,比如{1,0,1,1,1},这个代码是有问题的.
题目3:在有空字符串的有序字符串数组中查找
有个排序后的字符串数组,其中散布着一些空字符串,编写一个方法,找出给定字符串(肯定不是空字符串)的索引
分析:这前三个题都是二分法的变体.
贴代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| public class Case03_特殊有序数组中查找 {
public static void main(String[] args) {
String[] arr = {"a", "", "ac", "", "ad", "b", "", "ba"};
int res = indexof(arr, "b");
System.out.println(res);
}
private static int indexof(String[] arr, String p) {
int begin = 0;
int end = arr.length - 1;
while (begin <= end) {
int indexOfMid = begin + ((end - begin) >> 1);
while (arr[indexOfMid].equals("")) {
indexOfMid++;
if (indexOfMid > end)
return -1;
}
if (arr[indexOfMid].compareTo(p) > 0) {
end = indexOfMid - 1;
} else if (arr[indexOfMid].compareTo(p) < 0) {
begin = indexOfMid + 1;
} else {
return indexOfMid;
}
}
return -1;
}
}
|
题目4:最长连续递增子序列(部分有序)
如(1,9,2,5,7,3,4,6,8,0)的最长递增子序列(3,4,6,8)
分析:设置两个指针a和b,a首先指向最左侧,是每当遇到一个更大的数b指针就向右移动;如果遇到个比前一个数小的数,该序列终止,去统计该序列长度;然后a指针和b指针都指向下一个递增子序列起始位置,重复上述步骤直到b指向终止。
题目5:设计一个高效求a的n次幂的算法
1
2
3
4
5
6
7
8
| //普通算法,使用循环逐次累乘,时间复杂度为O(n).
int f(int a,int n){
int res = 1;
for(int i = 0;i<n;i++){
res *= a;
}
return res;
}
|
高效算法,减少了循环的次数,时间复杂度为O(logn)
ex每次循环左移一位,也就是乘2,普通算法是以+1的速度逼近n次幂,而该算法是以2为底的指数增长,此时循环内的结果也是翻倍增加.最后剩下n-ex次幂没有处理,就进行递归操作,将每次递归结果乘积在一起即可.
1
2
3
4
5
6
7
8
9
10
| int pow(int a,int n){
if(n==0) return 1;
int res = a;
int ex = 1;
while((ex << 1)<=n){
res = res*res;
ex<<=1;
}
return res*pow(a,n-ex);
}
|