C通过运行时堆栈支持递归函数的实现,有趣的是,标准并未说明递归需要堆栈。但是,堆栈非常适合于实现递归,所以很多编译器都使用堆栈来实现递归。——《C和指针》
以组合数C(n,m)举例:
C(n,m)=C(n-1,m)+C(n-1,m);
当n>=0时,C(n,0)=1, C(n,n)=1 。
递归算法代码:
int comb(int n, int m)
{
if(n<m || n<0 || m<0)
return 0;
if(n>=0)
{
if(m==0)
return 1;
if(n==m)
return 1;
}
return comb(n-1,m)+comb(n-1,m-1);
}
复制代码
以C(4,3)来计算: C(4,3)=C(3,3)+C(3,2)=1+C(2,2)+C(2,1)=1+1+C(1,1)+C(1,0)=1+1+1+1=4
利用栈先进后出算法思想的非递归算法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define StackSize 100
//DataType为元素的数据类型,stack用于存储栈中的数据元素的数组,top为栈顶指针
typedef int DataType;
typedef struct
{
DataType stack[StackSize];
int top;
}SeqStack;
//初始化栈
void StackInit(SeqStack *S)
{
S->top=0;
}
//判断栈是否为空
int StackEmpty(SeqStack S)
{
if(S.top==0)
return 1;
else
return 0;
}
//取栈顶元素
int GetTop(SeqStack S, DataType *e)
{
if(S.top<=0)
{
printf("栈已经空!\n");
return 0;
}
else
{
*e=S.stack[S.top-1];
return 1;
}
}
//将元素入栈
int PushStack(SeqStack *S, DataType e)
{
if(S->top==StackSize)
{
printf("栈已经满!\n");
return 0;
}
else
{
S->stack[S->top]=e;
S->top++;
return 1;
}
}
//将栈顶元素出栈
int PopStack(SeqStack *S, DataType *e)
{
if(S->top==0)
{
printf("栈已经空!\n");
return 0;
}
else
{
S->top--;
*e=S->stack[S->top];
return 1;
}
}
//求栈的长度
int StackLength(SeqStack S)
{
return S.top;
}
//清空栈
void ClearStack(SeqStack *S)
{
S->top=0;
}
int comb(int n, int m)
{
if(n>=0)
{
if(m==0)
return 1;
if(n==m)
return 1;
}
return comb(n-1,m)+comb(n-1,m-1);
}
//判断是否需要入栈
int comb_cal(int n, int m)
{
if(n>=0)
{
if(n==m)
return 1;
if(m==0)
return 1;
}
return 0;
}
int comb_stack(int n, int m)
{
int i=n;
int j=m;
int sum=0;
int temp1,temp2;
//A Stack存放n值,B Stack存放m值,两个同时入栈出栈,
//所以判断为空只需要判断一个即可
SeqStack A,B;
if(n<m || n<0 || m<0)
return 0;
StackInit(&A);
StackInit(&B);
PushStack(&A,i);
PushStack(&B,j);
while(!StackEmpty(A))
{
GetTop(A,&i);
GetTop(B,&j);
if(comb_cal(i,j)==0)
{
PopStack(&A,&temp1);
PopStack(&B,&temp2);
i--;
PushStack(&A,i);
PushStack(&B,j);
j--;
PushStack(&A,i);
PushStack(&B,j);
}
else
{
PopStack(&A,&temp1);
PopStack(&B,&temp2);
sum+=1;
}
}
return sum;
}
int main()
{
int b=comb_stack(4,3);
int c=comb(4,3);
printf("b:%d\n",b);
printf("c:%d\n",c);
return 0;
}
复制代码
上面的递归算法代码很好写,有直接的递归计算公式,但有些递归算法代码就不那么好写了,那么递归算法代码该怎么去写呢?今天在网上看到一种根据数学归纳法的思维来写递归算法代码。
最简单和常见的数学归纳法是证明当n等于任意一个自然数时某命题成立。证明分下面两步:
1.证明当n= 1时命题成立。
2.假设n=k时命题成立,那么可以推导出在n=k+1时命题也成立。(k代表任意自然数)
以链表反序算法为例:
第一步 n=1 ==>也就是链表为空或者链表只有一个元素的时候,这时候可以直接返回链表头指针 这是递归限制条件
第二步 假设n=k时成立,推导出n=k+1时也成立==>当程序能够反序head->next时(也就是图中空白框部分包括了k个结点全部完成了反序后)该如何处理呢? 需要2步:
1.head->next结点的next指针指向需要改变为指向head
2.head结点的next指针指向需要改为指向NULL
typedef struct Node{
int data;
Node *next;
} Node, *List;
Node * reverseList(List head)
{
//如果链表为空或者链表中只有一个元素
if(head == NULL || head->next == NULL)
{
return head;
}
else
{
//先反转后面的链表,走到链表的末端结点
Node *newhead = reverseList(head->next);
//再将当前节点设置为后面节点的后续节点
head->next->next = head;
head->next = NULL;
return newhead;
}
}
复制代码
一旦你理解了递归,阅读递归函数最容易的方法不是纠缠于它的执行过程,而是相信递归函数会顺利完成它的任务。如果你的每个步骤正确无误,你的限制条件设置正确,并且每次调用之后更接近限制条件,递归函数总是能够正确地完成任务。————《C和指针》