【每天一点算法】递归算法设计总结

今天被递归算法困扰了好久,感觉这真是个很神奇的算法,可以用那么简短的几行代码处理那么复杂的问题,下面就对其进行总结。
首先我们要来看几个经典的例子,这里只是介绍几个很简单的例子来简单理解,因为我觉得看博客的你一般都只是想快速了解,不要求深入?,复杂的递归算法还有很多。

//阶乘
int Mulity(int n)
{
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n*Mulity(n - 1);
    }
}

//汉诺塔
void Hanio(int n,char a,char b,char c)
{
    if (n==1)
        cout << n << "号盘子从"  << a << "到" << c << endl;
    else {
        Hanio(n-1,a,c,b);//把上面n-1个盘子从a借助b搬到c
        cout << "移动" << n << "号盘子从" << a << "到" << c << endl;
        Hanio(n-1,b,a,c);//再把b上的n-1个盘子借助a搬到c
    }

}

//斐波那契数列
int Fibonacci(int n)
{
    if (n <= 1) {
        return n;
    } else {
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

上面的例子,我相信不用太费脑就能看懂,那么这里面到底蕴含什么样的思想,在设计的时候到底要遵循什么样的准则,依照什么样的步骤呢?我们下面就来谈一谈,一起来将它的本质抽出来。
我们在选择一个算法之前要分析问题的特点,这是毋庸置疑的,我们在选择递归算法前当然也要分析我们面对的问题的特点,看看它是否能够通过递归方法的应用来更好地解决。通过分析上面的三个例子,我们发现,它们都有一个共同特点,那就是初始问题的求解都可以通过求解一个或一些模式相同的规模更小更容易求解的问题,求 ! n !n !n只要求 ! ( n − 1 ) !(n - 1) !(n1),求Fibonacci(n)只要求Fibonacci(n-1),Fibonacci(n-2),同时我们发现,它们都有一个if条件在那里,那是终止条件,所以我们得出结论,一个问题能使用递归的条件有两个:1、一个问题要能够被分成与原问题模式相同的规模更小的问题。2、这个问题的分解不能无限进行,也就是说要有终止条件。
好了,当我们利用上述两条准则确定我们面对的问题能够被递归算法解决的时候,我们就开始来设计函数结构了。我们可以通过函数声明直接表明我们想要解决的问题,然后在函数里面设置一个if{}else{},if里面放置终止条件,else里面放置递归表达式,这个递归表达式的设置很是重要,也是难点,一般就是调用我们的递归函数所能够提供的功能(先不管它是如何实现的)进行相应的操作以完成当前这个递归函数的功能,听着有点绕?,但没办法,它就是这样,可以自己体会一下。

递归算法案例
1、递归地将一棵二叉树变成空树
我们首先来分析这个问题,我们的目标是将一棵树变成空树,也就是想要将所有的树结点都free掉,这是我们的最终目标。那么我们怎么实现这个目标呢?通过分析,我们不难发现,要想释放某个结点需要先释放它的左子树和右子树上所有的结点,如果我们采用正常的逻辑来释放,对于一个深度为0的树,直接返回NULL,对于深度为1的树我们需要这样

Search_Tree *Make_Empty(Search_Tree *T)
{
	if(T->left != NULL)
		free(T->left);
	if(T->right != NULL)
		free(T->right);
	
	free(T)	;
    return NULL;
}

对于深度为2的二叉树,我们需要这样

Search_Tree *Make_Empty(Search_Tree *T)
{
	if(T->left->left != NULL)
		free(T->left->left);
	if(T->left->right ! NULL)
		free(T->left->right);
	if(T->right->right != NULL)
		free(T->right->right);
	if(T->right->left != NULL)
		free(T->right->left);
	free(T)	;
    return NULL;
}

可以自己想一下,深度为3的二叉树需要8个if,深度为4的树需要16个if。。。。。,这种方法即没有适用性(就是无法处理动态深度的树)也没有简洁性(if的数量随着树的深度的增加以指数级速度增长),所以这种方法PASS掉。我们这里采用递归思想

#ifndef TREE_H_INCLUDED
#define TREE_H_INCLUDED
//这是一个二叉树结点结构体
typedef struct tree_node{
    int data;
    struct tree_node *left;
    struct tree_node *right;
}Search_Tree;

//下面是一些常见的操作
Search_Tree *Make_Empty(Search_Tree *T);
Search_Tree *Find(Search_Tree *T, int X);
Search_Tree *Find_Min(Search_Tree *T);
Search_Tree *Find_Max(Search_Tree *T);
Search_Tree *Insert_Node(Search_Tree *T, int X);
#endif // TREE_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include "tree.h"

int main()
{
    Search_Tree *tree = NULL;
    tree = Make_Empty(tree);
    printf("%d\n", tree->data);

}

//递归地将一棵树变成空树
Search_Tree *Make_Empty(Search_Tree *T)
{
	//首先检查这棵树是否存在
    if(T != NULL){
    	//释放左子树
        Make_Empty(T->left);
        //释放右子树
        Make_Empty(T->right);
        free(T);
    }
    return NULL;
}

递归逻辑:想要free一棵树并返回NULL,首先要检查这棵树是否存在,如果存在,需要先free它的左子树和右子树,然后再free它的结点,这些步骤对该结点的左子树和右子树同样适用

2、递归地在一棵二叉树中寻找某个元素

Search_Tree *Find(Search_Tree *T, int X)
{
    if(T == NULL)
        return NULL;
    else{
        if(X == T->data)
            return T;
        else if(X < T->data)
            return Find(T->left, X);
        else
            return Find(T->right, X);
    }
}

递归逻辑:想要在一棵二叉树中寻找某个元素并返回这个元素所在结点的地址,首先要检查这个二叉树是否存在,如果存在,首先要检查它的根结点中的元素,如果结点元素等于需要的元素,返回结点指针,结束检查,如果结点元素大于需要的元素,返回检查左子树的结果,如果结点元素小于需要的元素,返回检查右子树的结果,上面的逻辑对该结点的左子树和右子树同样适用

3、递归地寻找最小值

Search_Tree *Find_Min(Search_Tree *T)
{
    if(T == NULL)
        return NULL;
    else if(T->left == NULL)
        return T;
    else
        return Find_Min(T->left);
}

递归逻辑:想要找到一棵二叉树中的最小元素并返回这个元素所在结点的地址,首先要检查这棵树是否存在,如果存在,首先检查左子树是否为空,如果为空,返回当前结点指针,否则返回对左子树最小搜索的结果,上面的逻辑对该结点的子左子树同样适用

4、递归地向树中插入某个元素

Search_Tree *Insert_Node(Search_Tree *T, int X)
{
    if(T == NULL)
    {
        T = (Search_Tree *)malloc(sizeof(Search_Tree));
        if(T == NULL)
            exit(1);
        T->data = X;
        T->left = NULL;
        T->right = NULL;
    }
    else if(T->data > X)
    {
        T->left = Insert_Node(T->left, X);
    }
    else if(T->data < X)
    {
        T->right = Insert_Node(T->right, X);
    }

    return T;
}

递归逻辑:想要向一棵树里面插入一个元素并返回更新后的树的根结点的地址,首先检查该树是否存在,如果不存在,就分配一个结点并对其进行初始化,然后把这个数值给这个结点,如果这个结点存在且里面的数值大于我们给的数值,那么我们就把这个数值插入它的左子树并更新这个结点的左指针,如果这个结点里面的数值小于我们所给的数值,那么我们就把这个数值插入它的右子树并且更新这个结点的右指针,如果这个结点的数值等于我们所给的数值,那么就什么也不做,上面的逻辑对该结点的左子树和右子树同样适用
5、递归地删除一个元素
这个操作比前面的要复杂一点,我们在进行删除操作的过程中,可能会遇到两种情况,第一种:要删除的元素所在的结点只有一个子节点或者没有结点,这种情形比较简单,那就是将该结点的原指针指向它的儿子之后,删除该结点,如下图所示
在这里插入图片描述
第二种:要删除的结点有两个子节点,这种情况稍微有一点点复杂,不过仔细分析一下也挺简单,一般的删除策略是用它的右子树的最小数据代替该结点的数据,并递归地将右子树中的那个数据删除,如下图所示。
在这里插入图片描述
下面是考虑了上述两种情况的代码

Search_Tree *Delete_Node(Search_Tree *T, int X)
{
    Search_Tree *temp_cell;
    if(T == NULL)
    {
        printf("End!!");
        exit(1);
    }
    else if(X < T->data)
    {
        T->left = Delete_Node(T->left, X);
    }
    else if(X > T->data)
    {
        T->right = Delete_Node(T->right, X);
    }
    else if(T->left && T->right)
    {
        temp_cell = Find_Min(T->right);
        T->data = temp_cell->data;
        T->right = Delete_Node(T->right, T->data);
    }
    else
    {
        temp_cell = T;
        if(T->left == NULL)
            T = T->right;
        else
            T = T->left;
        free(temp_cell);
    }
    return T;
}

递归逻辑:想要从一棵树里面删除某个特定的元素并返回删除完成后的树的根结点指针,首先我们要检查这棵树是否存在,如果不存在,结束,如果存在将我们的数值与该结点的值进行比较,如果我们的值大,那就从这个结点的左子树中寻找并删除该元素,返回删除完成后的子树的根指针并更新该结点的左指针,如果我们的值小,那就从这个结点的右子树中寻找并删除该元素,返回删除完成后的子树的指针并更新该结点的右指针,如果我们的值等于该结点的元素的值,那就要利用到我们在上面所进行的分类讨论了,如果该结点有两个子节点,那么我们需要先用右子树中的最小值来更新更新当前结点的值,然后递归地删除右子树中对应的结点,如果该结点有一个子节点,那么就直接用对应的子节点更新当前结点的指针,然后释放当前结点。上面的逻辑同样适用于该结点的子树说了那么多,都不如直接看代码,看图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值