递归与尾递归(完)

内容概要

  一、递归注意事项

  二、递归的两个环节

  三、尾递归

  四、递归练习——汉诺塔、快速排序

 

1、递归注意事项

  递归指的是函数的递归,函数的递归简单来说就是自身调用自身的过程

#include <stdio.h>

void func(void);

void func(void){
    printf("...\n");
    func();
}

int main(void){
    func();
    return 0;
}

  添加了一个if条件判断,同时将参数作为条件一起传递给下层函数

#include <stdio.h>

void func(int);

void func(int i){
    if (i > 0){
    printf("...\n");
    func(i-1);
    }
    else{
        printf("end");
    }
}

int main(void){
    int a;
    printf("please input a number:");
    scanf("%d",&a);
    func(a);
    return 0;
}

  这里递归执行到func(0)时,会转为执行else语句,不再调用自身,递归结束

  总结:递归一定要有结束语句,通常通过if条件判断实现

 

2、递归的两个环节

  这两个环节分别为

    -回溯

    -递推

    回溯和递推验证代码

#include <stdio.h>

void func(int);

void func(int i){
    
    if (i > 0){
        printf("go back i:%d\n",i);  //回溯过程
        func(i-1);
        printf("go to i:%d\n",i);  //递推过程
    }
    else{
        printf("end\n");
    }
}

int main(void){
    int a;
    printf("please input a number:");
    scanf("%d",&a);
    func(a);
    return 0;
}

 

    图解

 

 

    如果想要使用递归,那么递归代码的实现思路结合   回溯结束,递推开始处和回溯开始两步  思路会比较清晰

 

3、尾递归

  尾递归就是将函数调用自身的代码写在return语句后面,return下行没有后续代码

    一个计算阶层的函数

#include <stdio.h>

long func(int);

long func(int i){
    if (i < 1){
        return 1;
    }
    else{
        return i * func(i-1); //func后续没有代码要执行了
    }
}

int main(void){
    int a;
    long result;
    printf("please input a number:");
    scanf("%d",&a);
    result = func(a);
    
    printf("result is %ld\n",result);
    return 0;
}

    尾递归与普通递归不同的是,尾递归可以视为没有递推过程,因为每次调用自身后,后续没有其它代码需要执行。

    下层函数要将结果返回给上层函数,必须使用return

    在python中是没有尾递归的,因为无论如何都会有递推过程  **有空详细了解原因**

 

4、递归练习——汉诺塔、快速排列

  -汉诺塔实现

    一般情况下,如果可以使用迭代就用迭代;万不得已才使用递推

    汉诺塔游戏规则

      每次只能移动一个圆环,而且小圆环只能放在大圆环上面。要将最左边64个圆环移动到最右边。(图上只画了3个)

        思路:

          将三个柱子分别命名为X、Y、Z,要想将64个圆盘移动到最右边;

          必须经过将前63个圆盘移动到中间的柱子(Y),再将第64个圆盘从X移动到Z;

          然后将Y上63个圆环重新移动到Z上。

 

          要移动63个圆环;

          必须先将前62个圆环移走,放到一个临时柱子上;

          再将第63个圆环移动到目标柱子上,;

          将前62个圆环移动到第63个圆环之上;

 

          要移动62个圆环;

          必须先将前61个圆环移走,放到一个临时柱子上;

          再将第62个圆环移动到目标柱子上;

          将前61个圆环移动到第62个圆环之上;

          

          无限套娃下去...

 

      记录汉诺塔解法函数

#include <stdio.h>

void hanoi(int);

void hanoi(int i, char start, char end, char temp){ //i是圆盘数量
    if (i > 1){
        hanoi(i-1, start, temp, end);  //将i-1个圆盘移动到临时柱子上
        printf("%c -> %c\n",start,end); //将第i个(也就是最下面一个)从开始柱子移动到目标柱子
        hanoi(i-1, temp, end, start); //重新将放在临时柱子上的i-1个圆盘移动到i圆盘上面
    }
    else{
        //printf("%c -> %c\n",start,temp);  //这里是思路错误时候写的,还是留下来好
        printf("%c -> %c\n",start,end);
        //printf("%c -> %c\n",temp,end);
    }
}

int main(void){
    int a;
    long result;
    printf("please input a number:");
    scanf("%d",&a);
    hanoi(a, 'X', 'Z', 'Y');
    return 0;
}

 

      记录汉诺塔移动次数函数

#include <stdio.h>

long long hanoi(int n);

long long hanoi(int n){
    
    static long long result;
    
    if (n > 1){
        result = (2*hanoi(n-1))+1;
    }
    else{
        result = 1;
    }
    
    return result;
}

int main(void){
    int input;
    
    long long result;
    scanf("%d",&input);
    
    result = hanoi(input);
    printf("%lld\n",result);
    
    return 0;
}

 

  -快速排列

    这个写到头都大了还是有错 **有空再弄**

    图解

#include <stdio.h>

void array_sort(int array[], int, int);

void array_sort(int array[], int start, int end){
    if(start == end){
        return;
    }
    
    int i,j,temp,center;
    
    i = start;
    j = end;
    
    center = array[(start+end) / 2];
    
    do{
        while(array[i] < center){
            i++;
            }
        while(array[j] > center){
            j--;
        }
        
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        i++;
        j--;
        if (i > end){
            i = end;
        }
        if (j < start){
            j = end;
        }
        
    }while(i <= j);
    
    array_sort(array, start, j);
    array_sort(array, i, end);
}

int main(void){
    int array[] = {1, 22, 35, 76, 23, 647, 1341, 135, 423, 52};
    int length = sizeof(array) / sizeof(array[0]);
    int end = length - 1;
    array_sort(array, 0, end);
    for(int i = 0; i < length; i++){
        printf("%d  ",array[i]);
    }
    return 0;
}

    再战快速排列

#include <stdio.h>

void quick_sort(int [], int, int);

void quick_sort(int array[], int start, int end){
    if (start == end){
        return;
    }
    
    int center, i, j, temp;
    i = start;
    j = end;
    center = array[(start+end) / 2];
    
    do{
        while (array[i] < center){
            i++;
        }
        
        while (array[j] > center){
            j--;
        }
        
        if (i <= j){
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
            i++;
            j--;
        }
        if (j < start){
            j = start;
        }
        if (i > end){
            i = end;
        }
    }
    while (i <= j);
    
    quick_sort(array, start, j);
    quick_sort(array, i, end);
    
    
}

int main(void){
    int array[] = {1,2,3,4,5,8,5,1,6,5};
    
    int end_index = sizeof(array) / sizeof(int);
    quick_sort(array, 0, end);
    
    for (int i = 0; i < 10; i++){
        printf("%d ", array[i]);
    }
    return 0;
}

   对于递归的一些思考

    递归就是——大事化小,小事化无

    要考虑好递归函数的功能;

    递归函数的功能虽然单一,也能通过参数变得灵活;

    递归函数必须要有结束条件,避免死循环和无限递归;

    思考递归函数实现过程,先从一般再到特殊

#include <stdio.h>

void quick_sort(int [], int, int);

void quick_sort(int array[], int start, int end){  //要进行排序的数组, 开始索引, 结束索引
    if (start == end){
        return; //当传入的实参相同时,说明只有一个数,结束本次排序
    }
    
    int center, i, j, temp;  //中间值, 用于遍历的下标i, j
    // 初始化下标
    i = start;
    j = end;
    center = array[(start+end) / 2];
    
    do{
        // goto tip1:
        while (array[i] < center){  //从左到右遍历,找到大于中间值的值
            i++;
        }
        
        while (array[j] > center){  //从右往左遍历,找到小于中间值的值
            j--;
        }
                if (i <= j){ //goto tip2:
            temp = array[i];  //替换两个值
            array[i] = array[j];
            array[j] = temp;
            i++;
            j--;
        }
        // goto tip3:
        if (j < start){
            j = start;
        }
        if (i > end){
            i = end;
        }
    }
    while (i <= j);  //当i>j时,说明数组所有值都被遍历
    
    //将数组分为两个小数组
    quick_sort(array, start, j);  //小于中间值的数组
    quick_sort(array, i, end); //大于中间值的数组
}

int main(void){
    int array[] = {1,2,3,4,5,8,5,1,6,5};
    
    int end_index = sizeof(array) / sizeof(int);
    quick_sort(array, 0, end);
    
    for (int i = 0; i < 10; i++){
        printf("%d ", array[i]);
    }
    return 0;
}

  tip1:

不能将
array[i] < center写成array[i] <= center;
或者
array[j] > center写成array[j] >= center;
这是因为当中间值为最大值时,i直接超出数组最大索引
加入判断i是否超过最大值也不可以,
因为要保证中间值左边都是小于中间值的
中间值右边都是大于中间值的
中间值也应该被移动
对于j来说,当中间值为最小值时同理

  tip2:

if (i <= j)的判断一定要加,这步是最难想到的
i <= j的条件不能写成i < j; 因为当 i = j 时,会进入死循环
加入if (i <= j)的原因是 当中间值为最大值或者是最小值时 以最大值为例

  tip3

加入两个if判断是为了避免i和j超出索引范围

 

  ***完***

posted @ 2021-03-16 18:02  口乞厂几  阅读(282)  评论(0)    收藏  举报