递归函数的设计思路:
1.参数语义//f(i)代表了什么? 2.边界因素//递归结束的条件 3.寻找关联//f(i)与f(i-1)或f(i+1)有什么关联
猴子吃香蕉
小猴子摘了一堆香蕉,第一天吃了一半的香蕉,还没吃饱,于是又多吃了一个;从此它每天都吃剩下的香蕉的一半多一个,吃到第n天就只剩下了一个香蕉了,现在小猴想知道一共有多少个香蕉。
//int f(int n)中的n可以理解为第n天吃了多少个桃子,也可以理解为第n天还剩多少个桃子,这里以第n天还剩多少个桃子为例
int fun(int n)
{
if(n<2)//边界因素,这里代表最后一天
{return 1;}
else
return (fun(n-1)+1)*2;//f(n)与f(n+1)的关联:0.5*f(n+1)-1=f(n)
}
弹簧板
有一个小球掉落在一串连续的弹簧板上,小球落到某一个弹簧板后,会被弹到某一个地点,直到小球被弹到弹簧板以外的地方。
假设有 n个连续的弹簧板,每个弹簧板占一个单位距离,a[i] 代表代表第 i个弹簧板会把小球向前弹 a[i]个距离。比如位置 1 的弹簧能让小球前进 2个距离到达位置 3 。如果小球落到某个弹簧板后,经过一系列弹跳会被弹出弹簧板,那么小球就能从这个弹簧板弹出来。
现在小球掉到了1 号弹簧板上面,那么这个小球会被弹起多少次,才会弹出弹簧板。 1号弹簧板也算一次。
/*
样例输入:
5
2 2 3 1 2
样例输出:
2
*/
#include <iostream>
#include <vector>
using namespace std;
int fun(int i,vector<int>& arr,int n) //i代表小球从i位置开始,被弹出弹簧板的次数
{
if (i>=n)//说明小球已经在最后一块弹簧板了
{
return 0;
}
else
{
return fun(i+arr[i],arr,n)+1;//i+arr[i]就是弹簧的下一个位置
}
}
int main()
{
int n;//弹簧板的数量
cin >> n;
vector<int> arr(n);
for (int i = 0; i < n; ++i)
{
cin >> arr[i];
}
int result = fun(0, arr,n);//小球从0位置开始,被弹出弹簧板的次数
cout << result << endl;
return 0;
}
递归实现指数型枚举
/*
样例输入:
3
样例输出:
1
1 2
1 2 3
1 3
2
2 3
3
*/
#include<iostream>
#include<vector>
using namespace std;
vector<int>v(10);
void Print(int n)
{
for (int i = 0; i <= n; i++)
{
cout << v[i] << " ";
}
cout << endl;
}
/*
* i:当前枚举的第i个位置的值
* j:当前位置可以选取的最小值是j
* n:可以选取的最大值
*/
void f(int i, int j, int n)
{
if (j > n) return;//边界因素
else
{
for (int k = j; k <= n; k++)
{
v[i] = k;
Print(i);
f(i + 1, k + 1, n);//关联关系
}
}
}
int main()
{
int n;
cin >> n;
f(0, 1, n);
return 0;
}

总体思路:将整体的输出看作一个数组,整体的输出f(i)就相当于将i固定下来+f(i+1)的输出,而f(i+1)的输出又相当于将i+1固定下来+f(i+2)的输出.......
递归实现组合型枚举
/*
样例输入:3 2 (意思是从1到3里面选择2个进行全排列输出)
样例输出:
1 2
1 3
2 3
*/
#include<iostream>
#include<vector>
using namespace std;
vector<int>v(10);//存储枚举的数
void Print(int n)
{
for (int i = 0; i < n; i++)
{
cout << v[i] << " ";
}
cout << endl;
}
/*
* i:当前枚举的第i个位置的值
* j:当前位置可以选取的最小值是j
* n:可以选取的最大值
* m:最多可以输出几个数
*/
void f(int i, int j, int n,int m)
{
if (i == m)//边界因素
{
Print(m);
return;
}
else
{
for (int k = j; k <= n && m - 1 - i <= n - k; k++)
/*m - 1 - i <= n - k 是一个关键条件,它确保即使选择当前值 k,也有足够的剩余位置来生成m个元素的组合。这是因为m-1-i表示剩余需要生成的元素个数,而n-k表示剩余可供选择的值的数量。只有当剩余的位置足够多时,才选择当前的值 k*/
{
v[i] = k;
f(i + 1, k + 1, n, m);//关联关系:
}
}
}
int main()
{
int n, m;
cin >> n >> m;
f(0, 1, n, m);
return 0;
}
为了更好地理解这个代码,可以从深度优先搜索的方式来解释样例:
初始状态:空的组合 [],还需要生成2个元素。调用 f(0, 1, 3, 2)。
第一次迭代:
i = 0,选择第一个元素:k = 1,得到组合 [1]。 进入下一层迭代:现在还需要生成1个元素,而可供选择的范围是从2到3。
第二次迭代:
i = 1,选择第二个元素:k = 2,得到组合 [1, 2]。 进入下一层迭代:现在不需要再生成元素,因为 i = 2,直接输出组合 [1, 2]。 回退到上一层:此时 i = 1,k 也变回了2。 进入下一轮迭代:选择第二个元素:k = 3,得到组合 [1, 3]。 进入下一层迭代:现在不需要再生成元素,直接输出组合 [1, 3]。
回退到上一层:此时 i = 0,k 也变回了1。
进入下一轮迭代:选择第二个元素:k = 3,得到组合 [2, 3]。 进入下一层迭代:现在不需要再生成元素,直接输出组合 [2, 3]。
递归实现排列型枚举
:从1~n这n个整数排列成一排并打乱次序,按字典序输出所有可能的选择方案。
/*样例输入:3
样例输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
#include<iostream>
using namespace std;
int arr[10], vis[10] = { 0 };//arr 用来存储当前排列,vis 用来标记数字是否已经被使用过。
void Print(int n)
{
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
/*i代表枚举到了第i个位置,n代表最多枚举n个数*/
void f(int i, int n)
{
if (i == n)//边界条件
{
Print(n);
return;
}
for (int k = 1; k <= n; k++)
{
if (vis[k]) continue;
arr[i] = k;
vis[k] = 1;
f(i + 1, n);
vis[k] = 0;
}
}
int main()
{
int n;
cin >> n;
f(0, n);
return 0;
}
斐波那契数列的递归优化
#include<iostream>
using namespace std;
int Fibonac(int n, int a, int b)
{
if (n <= 2) return a;
else
return Fibonac(n - 1, a + b, a);
}
int main()
{
int n;
cin >> n;
cout << Fibonac(n, 1, 1) << endl;
return 0;
}
二分查找
不存在重复值
#include<iostream>
using namespace std;
int BinaryFind(const int* ar, int left, int right, int val)
{
int pos = -1;
if (left <= right)
{
int mid = ((right - left) >> 1) + left;
if (val < ar[mid])
pos = BinaryFind(ar, left, mid, val);
else if (val > ar[mid])
pos = BinaryFind(ar, mid + 1, right, val);
else
pos = mid;
}
return pos;
}
int FindArrIsEmpty(const int* ar, int n, int val)
{
if (nullptr == ar || n < 1) return -1;
else
return BinaryFind(ar, 0, n - 1, val);
}
int main()
{
int ar[] = { 12,23,34,45,56,67,78.89,910 };
int len = sizeof(ar) / sizeof(ar[0]);
int n;
cin >> n;
cout << FindArrIsEmpty(ar, len, n);
return 0;
}
存在重复值 ar={12,23,23,23,23,45,67,89,90,100}
#include<iostream>
using namespace std;
int BinaryFind(const int* ar, int left, int right, int val)//目标数组,左下标,右下标,目标值
{
int pos = -1;
if (left <= right)
{
int mid = ((right - left) >> 1) + left;
if (val < ar[mid])
pos = BinaryFind(ar, left, mid, val);
else if (val > ar[mid])
pos = BinaryFind(ar, mid + 1, right, val);
else
{
if (mid > left && ar[mid - 1] == val)
{
pos = BinaryFind(ar, left, mid - 1, val);//如果存在重复元素,就要最靠近左边的下标
}
else
pos = mid;
}
}
return pos;
}
int FindArrIsEmpty(const int* ar, int n, int val)//目标数组,数组长度,目标值
{
if (nullptr == ar || n < 1) return -1;
else
return BinaryFind(ar, 0, n - 1, val);
}
int main()
{
int ar[] = {12,23,23,23,23,45,67,89,90,100};
int n;
cin >> n;
int len = sizeof(ar) / sizeof(ar[0]);
cout << FindArrIsEmpty(ar, len, n);
return 0;
}
快速排序
/*
*将数组的最左端的下标令为left,最右端的下标令为right。left和right不能动,因为他们俩代表了数组的长度,所以要再设下标指针i=left,j=right。
*将left对应的值赋给轴心点pivot,将pivot与最右端的值arr[j]进行比较,大于就将arr[j]赋arr[i],否则就j--。
*如果进行了赋值,那么就不再对j动手,转而对i动手。将pivot与最左端的值arr[i]进行比较,小于就将arr[i]赋给arr[j],否则就i++。
*当i<j时,退出比较,此时发现:轴心的左边的值都比轴心点小,轴心点右边的值都比轴心点大。然后就分别对轴心点的左边部分和右边部分再次调用这个函数。直到left>=right。
*/
#include<iostream>
using namespace std;
void QuickSort(int* arr, int left, int right)
{
if (left >= right) return;
int i = left, j = right;
int pivot = arr[i];
while (i < j)
{
while (i < j && arr[j] > pivot) j--;
arr[i] = arr[j];
while (i < j && arr[i] <= pivot) i++;
arr[j] = arr[i];
}
arr[i] = pivot;
QuickSort(arr, left, i - 1);
QuickSort(arr, i + 1, right);
}
void SortAry(int* arr, int n)
{
if (nullptr == arr || n < 2)
return;
QuickSort(arr, 0, n - 1);
}
int main()
{
int arr[] = { 34,56,1,89,12,78,67,23,45,34 };
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i <= n - 1; ++i)
{
cout << arr[i] << " ";
}
cout << endl;
SortAry(arr, n);
for (int i = 0; i <= n - 1; ++i)
{
cout << arr[i] << " ";
}
return 0;
}
本文介绍了递归函数的设计思路,包括参数语义、边界条件以及递归与非递归关系的探索,通过实例展示了如何使用递归解决猴子吃香蕉、弹簧板问题、组合排列、斐波那契数列优化、二分查找(无/有重复)及快速排序等技术。
1891





