递归是一种数学上分而治之的思想:
将原问题分解为规模较小的问题进行处理;
分解后的问题与原问题的类型完全相同,但规模较小
通过小规模问题的分解,能够轻易求得原问题的解
问题的分解是有限的(递归不能无限进行)
当边界条件不满足时,分解问题(递归继续进行)
当边界条件满足时,直接求解(递归结束)
递归在程序设计中的应用:
递归函数:
函数体中存在自我调用的函数;——问题的分解
递归函数必须有递归出口(边界条件);
函数的无限递归将导致程序崩溃。
下面分别实现三个递归函数:求和
unsigned int sum( unsigned int n)
{
if( n > 1)
{
return n+ sum(n -1);
}
else
{
return 1;
}
}
斐波那契数列:
unsigned int fac(unsigned int n)
{
if( n > 2)
{
return fac(n-1) + fac(n-2);
}
if( n == 2)
{
return 1;
}
if( n == 1)
{
return 1;
}
return 0;
}
函数求字符串长度:
unsigned int _strlen_(const char* s)
{
/*if( *s != '\0')
{
return 1 + _strlen_(s+1);
}
else {
return 0;
}*/
//*************一行代码实现***************************//
}
return s ? (*s ? (1 + _strlen_(s+1)):0):0; //先判断s是否为零,再判断字符是否为 '\0'
递归的思想在于分而治之,首先建立递归的模型,模型中必须要有边界条件,最好能先写出递归函数的数学表达。
注意:不要陷入递归函数的执行细节,要学会通过代码来描述递归问题。
单向链表的转置:
先定义一个链表结点
struct Node{
int value;
Node* next;
};
实现三个和链表相关的函数:
Node* create_list(int v, int len)
{
Node* ret = NULL;
Node* slider = NULL;
for(int i=0; i<len; i++)
{
Node* n = new Node();
n->value = v++;
n->next = NULL;
if( slider == NULL)
{
slider = n;
ret = n;
}
else
{
slider->next = n;
slider = n;
}
}
return ret;
}
void destory_list(Node* list)
{
while (list)
{
Node* del = list;
list = list->next;
delete del;
}
}
void print_list(Node* list)
{
while (list)
{
cout<< list->value <<"->";
list = list->next;
}
cout<< "NULL"<<endl;
}
转置函数的实现:
Node* reverse(Node* list)
{
if( (list == NULL) || (list->next == NULL))
{
return list; //先找出口
}
else
{
Node* guard = list->next; //将大问题分解为子表的递归
Node* ret = reverse(list->next); //子表的转置
guard->next = list;
list->next = NULL;
return ret;
}
}
单向排序链表的合并:(合并之后也要单向有序)
先找到出口条件:
Node* merge(Node* list1, Node* list2)
{
if( list1 == NULL)
{
return list2;
}
else if( list2 == NULL)
{
return list1;
}
else if(list1->value < list2->value)
{
Node* list1_ = list1->next;
Node* list = merge(list1_, list2);
list1->next = list;
return list1;
//优化::: return (list1->next = merge(list1->next, list2), list1); 逗号表达式
}
else
{
Node* list2_ = list2->next;
Node* list = merge(list1, list2_);
list2->next = list;
return list2;
//优化::: return (list2->next = merge(list1, list2->next), list2); 逗号表达式
}
}
汉诺塔问题:
void HanoiTower(int n, char a, char b, char c)// a ==>src b==>middle c==>dest
{
if( n == 1)
{
cout<< a << "-->" << c <<endl; //打印移动步骤
}
else
{
HanoiTower(n-1, a, c, b); //第一次,先将n-1个木块,从a始发地,借助于c,移动到b
HanoiTower(1, a, b, c); //将最后一个木块(在a上面),直接移动到c
HanoiTower(n-1, b, a, c); //剩下的n-1个木块在b上面,借助于中转站a移动到目的地c
}
}
全排列问题:
void permutation(char* s, char* e) // e表示始终指向首元素的地址
{
if( *s == '\0') //不参与全排列
{
cout<< "look here:" <<endl;
cout<< e <<endl;
}
else
{
int len(strlen(s)); //初始化的一种
for(int i=0; i<len ; i++) //注意这里的遍历,是S[0]不断与每个元素进行交换
{
if( (i == 0) || (s[0] != s[i])) //避开集合中有相同元素的情况
{
swap(s[0], s[i]); //没考虑元素是否一样
permutation(s+1, e); //交换过后,对其余元素进行全排列。
swap(s[0], s[i]); //交换回来
}
}
}
}
测试代码:
int main()
{
char s[] = "cba";
permutation(s, s);
/*
Node* list1 = create_list(1,3);
Node* list2 = create_list(2,3);
print_list(list1);
print_list(list2);
Node* list = merge(list1, list2);
print_list(list);
destory_list(list);
destory_list(list1);
destory_list(list2);
*/
//HanoiTower(3,'a','b','c');
return 0;
}
使用递归来进行回溯算法的设计
函数调用过程的回顾:
程序运行后有一个特殊的内存区域供函数调用使用:比如全局变量在全局数据区,数据段;代码放在代码段。
这块区域用于保存函数中的实参,局部变量、临时变量等,从起始地址开始往一个方向增长,(如高地址--->低地址),并且有一个专用的“指针”标识当前已经使用内存的“顶部”。
特点:只在内存某块区域的一端进行操作。
程序中的栈区:一段特殊的专用内存区(供函数调用)
回溯算法的核心:g()函数调用结束之后,f()函数调用对应的栈空间的数据不会发生变化。
示例1:逆序打印单链表中的偶数结点
void r_print_even(Node* list)
{
if(list != NULL) //为NULL则直接返回了
{
r_print_even(list->next); //先逆序打印子表
if( (list->value % 2) == 0) //出口
{
cout<< list->value <<endl;
}
}
}
问:如何实现逆序的?
每次函数递归调用的时候,都会在内存中分配空间,直到空指针出现。然后出现退栈,函数返回。一层层退栈,然后不断进行判断,符合条件则打印,然后连续退栈、判断。
打印的过程就是回溯的过程。开始退栈的时候,就要决定是否打印。打印的内容来自于栈上面的参数,此处的参数是指针。在递归调用函数的时候,只是使用栈上的参数(指针)保存数据,数据暂时没有使用,只是使栈上的指针指向对应的数据(结点),继续建栈,在函数调用返回(退栈)的时候,才使用这些数据。(类似于迷宫中的标记(指针))
回溯算法设计的本质,就是在栈上做标记,方便回退。利用栈保存数据,回溯的时候再利用数据再进行计算(判断)。
测试代码示例:
int main()
{
/*Node* list = create_list(2,5);
print_list(list);
r_print_even(list);
print_list(list);
//char s[] = "cba";
//permutation(s, s);
Node* list1 = create_list(1,3);
Node* list2 = create_list(2,3);
print_list(list1);
print_list(list2);
Node* list = merge(list1, list2);
print_list(list);
destory_list(list);
destory_list(list1);
destory_list(list2);
*/
//HanoiTower(3,'a','b','c');
return 0;
}
请读者自行测试