虽然上两篇的说明中有阶乘,Fibonacci 函数,这样这么好的例子来解释递归程序运行的例子,但是这样就会给你留下一个递归只能在数学的函数领域上使用的印象,但是事实上,只要事件满足递归的两个条件,都可以用递归来解决。这篇文章就来介绍一下递归在非数学领域的应用。
Checking palindromes
回文数(palindromes),就是一串既能从前面开始读,又可以从后面开始读的字符串。例如字符串“level”还有"noon"。尽管这样的回文数我们可以通过他的字符来判断他是否为回文数(参考我的string这篇博文),但是我们同样可以通过递归的方式来检查
它是否为回文数。你需要观察到的就是,在一个非单个字母的回文数中,它的里面一定包含更短的回文数,举个例子,“level”,这个回文数中,里面有更小的回文数“eve”。因此当你要检测一个数是否是回文数的时候,你需要做的是(假设它的长度不等于1):
1. Check to seethat the first and last characters are the same.
2. Check to see whether the substring generated by removing the first andlast characters is itself a palindrome
#include <iostream>
#include <string>
using namespace std;
/*函数原型*/
bool isPalindrom(string str);
int main() {
string str;
cin >> str;
if(isPalindrom(str)) { /*如果为true,则输出yes,否则输出no*/
cout << "Yes" << endl;
}else{
cout << "No" << endl;
}
}
/*采用递归判断是否为回文数
*substr(1,len-2)是截取这部分的字符串,在字符串头文件有讲
*/
bool isPalindrom(string str) {
int len = str.length();
if(str.length() < 2) {
return true;
}else {
return str[0] == str[len - 1] && isPalindrom(str.substr(1, len -2));
}
}
程序的执行效果如图:
是不是觉得很简单,如果我们跟着这个步骤去做的话。但是,你必须知道这样子是很不高效率的。为什么?因为你每调用一次这个函数,那么都要计算一次长度,并截取一次字符串,所以我们可以通过下面的方法来改进这个程序:
1.Calculate thelength of the argument only once.只计算一次字符串的长度,因为原来的程序中,在每一次的递归等级中,我们都要计算一次长度,显然这是很费事的。
2.Don’t make asubstring on each call.不要每次调用都截取字符串,我们之前每次都截取并判断末尾跟首个元素,闲这也是没必要的。
具体应该怎么做呢?我们可以回想一下我们是怎么提高fibonacci数列的效率的?没错,就是wrapper函数!!回顾上篇文章内容,我们可以写出这样的函数原型:
bool isSubstringPalindrome(string str, int p1, int p2);
p1跟p2 用来记录字符串的起始还有结束的位置。所以程序最后可以改写成
bool isPalindrome(string str) {
return isSubstringPalindrome(str, 0, str.length() - 1);
}
bool isSubstringPalindrome(string str, int p1, int p2) {
if (p1 >= p2) {
return true;
} else {
return str[p1] == str[p2] && isSubstringPalindrome(str, p1 + 1, p2 - 1);
}
}
The binary search algorithm
当你对一串存储在vector中的数据进行操作的时候,最常见的操作就是在vector中寻找一个指定的数值(元素)吧,(至于vector是什么,我有篇介绍stl的博文有提到,暂且看做一个大小可以自定义的数组)。因此我们要是有下面的一个函数将会很好用:
int findInVector(string key, Vector<string> & vec);
在vector中寻找等于key的那个值,如果等于,那么返回他所在位置的下标,要是不存在,那么就返回-1。当数组中没有这号元素的时候,我们就必须遍历所有的元素。这种策略我们称为线性查找算法(This strategy is called the linear-searchalgorithm, which can be time-consuming if the arrays are large),当数组的大小很大的时候,我们要花费更长的时间。反过来,如果这个数组的长度更加短,那么很显然画的时间就越短所以我们的优化策略就是把数组变短,然后再在短的数组中找寻我们的元素。试想,我们把一个长的数组分为两半,利用下标去对比寻找元素,如果key在后半部分里面,那么我们又可以通过把后半部分分为两半,进一步锁定范围,直到找到这个元素。其实这个方法就是我们高中介绍的二分法找根,在计算机科学中我们称为折半查找法(binarysearch )。这种方法显然比线性查找更加的高效,这也是一个完美的分离与组合的策略。因此我们先思考一下我们应该怎么写这个函数的原型,首先这个函数要包含我们要查找的元素 key,其次我们要在一个vector中寻找,寻找时候改变它的长度。最后我们分开后得有一个范围吧,所以考虑了那么多,我们可以写出函数的原型:
int binarySearch(string key, Vector<string> & vec, int p1, int p2)
这个时候我们考虑simple case,如果这个数组中元素为空,或者它恰好就是中间的那个元素。那么他就是我们要找的元素。好了,接下来我们用一个wrapper函数来实现这个功能,代码如下:
int findInSortedVector(string key, Vector<string> & vec) {
return binarySearch(key, vec, 0, vec.size() - 1);
}
int binarySearch(string key, Vector<string> & vec, int p1, int p2) {
if (p1 > p2) return -1;
int mid = (p1 + p2) / 2;
if (key == vec[mid]) return mid;
if (key < vec[mid]) {
return binarySearch(key, vec, p1, mid - 1);
} else {
return binarySearch(key, vec, mid + 1, p2);
}
}