递归与栈

314 阅读6分钟

递归知识点

递归的定义

什么是递归?若在一个函数,过程或数据结构定义的内部又直接(或间接)出现定义本身的应用,则成为他们是递归的。

对于能够分解成几个简单且解法相对活类似的子问题,来求解,便成为递归求解,例如,在求解4时先求解3,然后再进一步分解进行求解,这种求解方式叫做“分治法”

采取“分治法”进行递归求解的问题需要满足以下三个条件:

1 能将一个问题转换变成一个小问题,而新问题和原问题解法相同类同,不同的仅仅是处理的对象。并且这些处理更小且变化有规律的

2 可以通过上述转换而使得问题简化

3 必须有一个明确的递归出口,或成为递归边界

数据结构是递归的

其数据结构本身具有递归的特性

例如。对于链表,其结点LNode的定义由数据域data和指针next组成,而指针域next是一种知性LNode类型的指针,即LNode的定义中又用到了其自身,所以链表是一种递归的数据结构

 void TraverseList(LinkList p){
 //递归终⽌
if(p == NULL) return;
 else{
 //输出当前结点的数据域
printf("%d",p->data);
 //p指向后继结点继续递归
 TraverseList(p->next);
 }
 } 

问题的解法是递归的

有一类问题,虽然问题本身并没有明显的递归结构,但是采样递归求解比迭代求解更简单,如Hanoi塔问题,八皇后问题,迷宫问题


递归过程和工作栈

⼀个递归函数,在函数的执⾏过程中,需要多次进⾏⾃我调⽤. 那么思考⼀下,⼀共递归函数是如何执⾏的?
在了解递归函数是如何执⾏之前,先来了解⼀下任何的2个函数之间调⽤是如何进⾏的;
在⾼级语⾔的程序中,调⽤函数和被调⽤的函数之间的链接与信息交换都是通过栈来进⾏的.
通常,当在⼀个函数的运⾏期间调⽤另⼀个函数时, 在运⾏被调⽤函数之前, 系统需要先完成3件事情:
1. 将所有的实参,返回地址等信息调⽤传递被调⽤函数保存;
2. 为被调⽤函数的局部变量分配存储空间
3. 将控制转移到被调函数⼊⼝;
⽽从被调⽤函数返回调⽤函数之前,系统同样需要完成3件事:
1. 保存被调⽤函数的计算结果;
2. 释放被调⽤函数的数据区
3. 依照被调⽤函数保存的返回地址将控制移动到调⽤函数.
当多个函数构成嵌套调⽤时, 按照"先调⽤后返回"的原则, 上述函数之间的信息传递和控制转移必须通
过"栈"来实现. 即系统将整个程序运⾏时的所需要的数据空间都安排在⼀个栈中, 每当调⽤⼀个函数时,就在
它的栈顶分配⼀个存储区. 每当这个函数退出时,就释放它的存储区.则当前运⾏时的函数的数据区必在栈顶.

在主函数main中调⽤函数first, ⽽在函数first ⼜嵌套调⽤了second 函数. 则,当我们执⾏当前在执⾏的firt

函数时,则栈空间⾥保存了这些信息; ⽽当我们执⾏second则图(b)保存了这些信息.
⼀个递归函数的运⾏过程类似多个函数嵌套调⽤; 只是调⽤函数和被调⽤函数是同⼀个函数. 因此, 和每次
调⽤相关的⼀个重要概念是递归函数运⾏的"层次". 假设调⽤该递归函数的主函数为第0层, 则从主函数调
⽤递归函数进⼊第1层, 从第i层递归调⽤本函数为进⼊下⼀层.即第i+1层. 反正退出第i层递归应返回上⼀层,
即第i-1层.
为了保证递归函数正确执⾏, 系统需要设⽴⼀个"递归⼯作栈"作为整个递归函数运⾏期间使⽤的数据存储
区. 每⼀层递归所需信息构成⼀个⼯作记录, 其中包括所有的实参,所有的局部变量以及上⼀层的返回地址.
每进⼊⼀层递归, 就产⽣⼀个新的⼯作记录压⼊栈顶. 每退出⼀个递归,就从栈顶弹出⼀个⼯作记录, 则当前
执⾏层的⼯作记录必须是递归⼯作栈栈顶的⼯作记录, 称为"活动记录".

文字很多,等消化后估计理解的就没那么多了


栈 实操

链式存储栈结构

#define MAXSize 20
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int Status;
typedef int ElementType;

结点定义

typedef struct StackNode{
ElementType data;
struct StackNode *next;
}StackNode,*LinkStackPatr;

栈定义

typedef struct {
LinkStackPatr top;
int count;
}LinkStack;

/*  构造一个空寨*/

Status initStack(LinkStack *S){
S->top = NULL;
S->count = 0;
return OK;
}

/*  把链栈S置为空栈*/

Status ClearStak(LinkStack *S){
LinkStackPatr p,q;
p = S->top;
while (p) {
q = p;
p = p->next;
free(q);
}
S->count = 0;
return OK;
}


/*  若栈S为空栈,则返回TRUE 否则返回FALSE*/

Status StackEmpty(LinkStack S){
if (S.count == 0) {
return TRUE;
}
return FALSE;
}

/* 返回S的元素个数,即栈的长度*/

int StackLength(LinkStack S){
return S.count;
}

/*  若链栈S不为空 则用e返回栈顶元素,并返回OK,否则返回ERROR*/

Status GetTop(LinkStack S,ElementType *e){
if (S.top == NULL) {
return ERROR;
}else
*e = S.top ->data;
return OK;
}

/*  插入元素e到栈链S(成为新的栈顶*/

Status Push(LinkStack *S,ElementType e){
//创建新结点temp
LinkStackPatr temp = (LinkStackPatr)malloc(sizeof(StackNode));
//赋值
temp->data = e;
temp->next = S->top;
S->top = temp;
S->count ++;
return OK;
}


/*若栈部位空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERRROR*/

Status Pop(LinkStack *S,ElementType *e){
LinkStackPatr p;
if (StackEmpty(*S)) {
return ERROR;
}

//将栈顶元素赋值给*e

*e = S->top->data;
p = S->top;
S->top = S->top->next;
free(p);
S->count--;
return OK;
}

/*遍历栈*/

Status StackTraverse(LinkStack S){
if (StackEmpty(S)) {
return ERROR;
}
LinkStackPatr node;
node = S.top;
do {
printf("%d ",node->data);
node = node->next;
} while (node);
return OK;
}
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
int j;
LinkStack s;
int e;
if(initStack(&s)){
for(j=1;j<=10;j++)
Push(&s,j);
}
printf("栈中元素依次为:");
StackTraverse(s);
Pop(&s,&e);
printf("弹出的栈顶元素 e=%d\n",e);
StackTraverse(s);
printf("栈空否:%d(1:空 0:否)\n",StackEmpty(s));
GetTop(s,&e);
printf("栈顶元素 e=%d 栈的长度为%d\n",e,StackLength(s));
// ClearStack(&s);
printf("清空栈后,栈空否:%d(1:空 0:否)\n",StackEmpty(s));
return 0;
}