数据结构开发(15):递归的思想与应用

0.目录

1.递归的思想

2.递归的应用

  • 2.1 单向链表的转置
  • 2.2 单向排序链表的合并
  • 2.3 汉诺塔问题
  • 2.4 全排列问题
  • 2.5 逆序打印单链表中的偶数结点
  • 2.6 八皇后问题

3.小结

1.递归的思想

递归是一种数学上分而自治的思想:

  • 将原问题分解为规模较小的问题进行处理

    1. 分解后的问题与原问题的类型完全相同,但规模较小
    2. 通过小规模问题的解,能够轻易求得原问题的解
  • 问题的分解是有限的 ( 递归不能无限进行 )
    1. 当边界条件不满足时,分解问题 ( 递归继续进行 )
    2. 当边界条件满足时,直接求解 ( 递归结束 )

递归模型的一般表示法:

递归在程序设计中的应用:

  • 递归函数

    1. 函数体中存在自我调用的函数
    2. 递归函数必须有递归出口 ( 边界条件 )
    3. 函数的无限递归将导致程序崩溃

递归思想的应用:

  • 求解:Sum( n ) = 1 + 2 + 3 + ... + n

递归求和:

unsigned int sum(unsigned int n)
{
    if( n > 1 )
    {
        return n + sum(n-1);
    }
    else
    {
        return 1;
    }
}

斐波拉契数列:

数列自身递归定义:1, 1, 2, 3, 5, 8, 13, 21, ...

斐波拉契数列:

unsigned int fac(unsigned int n)
{
    if( n > 2 )
    {
        return fac(n-1) + fac(n-2);
    }

    if( (n == 2) || (n == 1) )
    {
        return 1;
    }

    return 0;
}

用递归的方法编写函数求字符串长度:

用递归的方法编写函数求字符串长度:

unsigned int _strlen_(const char* s)
{
    if( *s != ‘\0‘ )
    {
        return 1 + _strlen_(s+1);
    }
    else
    {
        return 0;
    }
}

unsigned int _strlen_(const char* s)
{
    return s ? (*s ? (1 + _strlen_(s+1)) : 0) : 0;
}

2.递归的应用

2.1 单向链表的转置

预备的单链表:

#include <iostream>

using namespace std;

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 destroy_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;
}

int main()
{
    Node* list = create_list(1, 5);

    print_list(list);

    destroy_list(list);

    return 0;
}

运行结果为:

1->2->3->4->5->NULL

单向链表的转置:

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;
    }
}

int main()
{
    Node* list = create_list(1, 5);

    print_list(list);

    list = reverse(list);

    print_list(list);

    destroy_list(list);

    return 0;
}

运行结果为:

1->2->3->4->5->NULL
5->4->3->2->1->NULL

2.2 单向排序链表的合并

单向排序链表的合并:

Node* merge(Node* list1, Node* list2)
{
    if( list1 == NULL )
    {
        return list2;
    }
    else if( list2 == NULL )
    {
        return list1;
    }
    else if( list1->value < list2->value )
    {
        Node* list_1 = list1->next;
        Node* list = merge(list_1, list2);

        list1->next = list;

        return list1;
    }
    else
    {
        Node* list_2 = list2->next;
        Node* list = merge(list1, list_2);

        list2->next = list;

        return list2;
    }
}

int main()
{
    Node* list1 = create_list(1, 5);
    Node* list2 = create_list(2, 6);

    print_list(list1);
    print_list(list2);

    Node* list = merge(list1, list2);

    print_list(list);

    destroy_list(list);

    return 0;
}

运行结果为:

1->2->3->4->5->NULL
2->3->4->5->6->7->NULL
1->2->2->3->3->4->4->5->5->6->7->NULL

代码优化:

Node* merge(Node* list1, Node* list2)
{
    if( list1 == NULL )
    {
        return list2;
    }
    else if( list2 == NULL )
    {
        return list1;
    }
    else if( list1->value < list2->value )
    {
        return (list1->next = merge(list1->next, list2), list1);
    }
    else
    {
        return (list2->next = merge(list1, list2->next), list2);
    }
}

2.3 汉诺塔问题

汉诺塔问题:

  • 将木块借助 B 柱由 A 柱移动到 C 柱
  • 每次只能移动一个木块
  • 只能出现小木块在大木块之上

汉诺塔问题分解:

  • 将 n-1 个木块借助 C 柱由 A 柱移动到 B 柱
  • 将最底层的唯一木块直接移动到 C 柱
  • 将 n-1 个木块借助 A 柱由 B 柱移动到 C 柱

汉诺塔问题:

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);
        HanoiTower(1, a, b, c);
        HanoiTower(n-1, b, a, c);
    }
}

int main()
{
    HanoiTower(3, ‘a‘ ,‘b‘, ‘c‘);

    return 0;
}

运行结果为:

a-->c
a-->b
c-->b
a-->c
b-->a
b-->c
a-->c

2.4 全排列问题

全排列问题:

void permutation(char* s, char* e) // e始终指向字符数组的首元素
{
    if( *s == ‘\0‘ )
    {
        cout << e << endl;
    }
    else
    {
        int len = strlen(s);

        for(int i=0; i<len; i++)
        {
            swap(s[0], s[i]);
            permutation(s+1, e);
            swap(s[0], s[i]);
        }
    }
}

int main()
{
    char s[] = "abc";

    permutation(s, s);

    return 0;
}

运行结果为:

abc
acb
bac
bca
cba
cab

但是如果存在相同的元素,则会有重复结果,例如:

int main()
{
    char s[] = "aac";

    permutation(s, s);

    return 0;
}

运行结果为:

aac
aca
aac
aca
caa
caa

代码优化:

void permutation(char* s, char* e) // e始终指向字符数组的首元素
{
    if( *s == ‘\0‘ )
    {
        cout << e << endl;
    }
    else
    {
        int len = strlen(s);
        char mark[256] = {0};

        for(int i=0; i<len; i++)
        {
            if( !mark[s[i]] )
            {
                swap(s[0], s[i]);
                permutation(s+1, e);
                swap(s[0], s[i]);
                mark[s[i]] = 1;
            }
        }
    }
}

int main()
{
    char s[] = "aac";

    permutation(s, s);

    return 0;
}

运行结果为:

aac
aca
caa

2.5 逆序打印单链表中的偶数结点

递归还能用于需要回溯穷举的场合。。。

函数调用过程回顾:

  • 程序运行后有一个特殊的内存区供函数调用使用

    1. 用于保存函数中的实参,局部变量,临时变量,等
    2. 从起始地址开始往一个方向增长 ( 如 : 高地址 → 低地址 )
    3. 有一个专用“指针”标识当前已使用内存的“顶部”

程序中的栈区:一段特殊的专用内存区

实例分析:逆序打印单链表中的偶数结点

逆序打印单链表中的偶数结点:

void r_print_even(Node* list)
{
    if( list != 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);

    destroy_list(list);

    return 0;
}

运行结果为:

2->3->4->5->6->NULL
6
4
2

2.6 八皇后问题

八皇后问题:

  • 在一个8x8的国际象棋棋盘上,有8个皇后,每个皇后占一格;要求皇后间不会出现相互“攻击”的现象 ( 不能有两个皇后处在同一行、同一列或同一对角线上 )。

关键数据结构定义:

  • 棋盘:二维数组 ( 10 * 10 )

    1. 0 表示位置为空,1 表示皇后,2 表示边界
  • 位置:struct Pos;

  • 方向:

算法思路:

八皇后问题:

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace StLib;

template <int SIZE>
class QueueSolution : public Object
{
protected:
    enum { N = SIZE + 2 };

    struct Pos : public Object
    {
        Pos(int px = 0, int py = 0) : x(px), y(py) { }
        int x;
        int y;
    };

    int m_chessboard[N][N];
    Pos m_direction[3];
    LinkList<Pos> m_solution;
    int m_count;

    void init()
    {
        m_count = 0;

        for(int i=0; i<N; i+=(N-1))
        {
            for(int j=0; j<N; j++)
            {
                m_chessboard[i][j] = 2;
                m_chessboard[j][i] = 2;
            }
        }

        for(int i=1; i<=SIZE; i++)
        {
            for(int j=1; j<=SIZE; j++)
            {
                m_chessboard[i][j] = 0;
            }
        }

        m_direction[0].x = -1;
        m_direction[0].y = -1;
        m_direction[1].x = 0;
        m_direction[1].y = -1;
        m_direction[2].x = 1;
        m_direction[2].y = -1;
    }

    void print()
    {
        for(m_solution.move(0); !m_solution.end(); m_solution.next())
        {
            cout << "(" << m_solution.current().x << ", " << m_solution.current().y << ") ";
        }

        cout << endl;

        for(int i=0; i<N; i++)
        {
            for(int j=0; j<N; j++)
            {
                switch (m_chessboard[i][j])
                {
                    case 0: cout << " "; break;
                    case 1: cout << "#"; break;
                    case 2: cout << "*"; break;
                }
            }

            cout << endl;
        }

        cout << endl;
    }

    bool check(int x, int y, int d)
    {
        bool flag = true;

        do
        {
            x += m_direction[d].x;
            y += m_direction[d].y;
            flag = flag & (m_chessboard[x][y] == 0);
        }
        while( flag );

        return (m_chessboard[x][y] == 2);
    }

    void run(int j) // 检查第j行有没有可以放置皇后的位置
    {
        if( j <= SIZE )
        {
            for(int i=1; i<=SIZE; i++)
            {
                if( check(i ,j, 0) && check(i ,j, 1) && check(i ,j, 2) )
                {
                    m_chessboard[i][j] = 1;

                    m_solution.insert(Pos(i, j));

                    run(j + 1);

                    m_chessboard[i][j] = 0;

                    m_solution.remove(m_solution.length() - 1);
                }
            }
        }
        else
        {
            m_count++;

            print();
        }
    }
public:
    QueueSolution()
    {
        init();
    }

    void run()
    {
        run(1);

        cout << "Total: " << m_count << endl;
    }
};

int main()
{
    QueueSolution<4> qs;

    qs.run();

    return 0;
}

测试四皇后问题的运行结果为:

(2, 1) (4, 2) (1, 3) (3, 4)
******
*  # *
*#   *
*   #*
* #  *
******

(3, 1) (1, 2) (4, 3) (2, 4)
******
* #  *
*   #*
*#   *
*  # *
******

Total: 2

(八皇后问题一共有92个解。)

3.小结

  • 递归是一种将问题分而自治的思想
  • 用递归解决问题首先要建立递归的模型
  • 递归解法必须要有边界条件,否则无解
  • 不要陷入递归函数的执行细节,学会通过代码描述递归问题
  • 程序运行后的栈存储区专供函数调用使用
  • 栈存储区用于保存实参,局部变量,临时变量,等
  • 利用栈存储区能够方便的实现回溯算法
  • 八皇后问题是栈回溯的经典应用

原文地址:https://www.cnblogs.com/PyLearn/p/10146924.html

时间: 2024-10-11 17:51:15

数据结构开发(15):递归的思想与应用的相关文章

用分治和递归的思想——寻找最大子数组

寻找最大子数组的问题可描述为 输入: 一个数组,一个低位,一个高位 输出: 数组中从低位到高位中,连续和最大是多少 首先能想到的最直接的办法是暴力解决,遍历所有可能的序列组合,如果有n个元素,则需遍历的子序列有,复杂度为n2,稍有些经验的就能马上意识到,有很多重复计算在里面,比如最长的子序列计算,包含了之前所有子序列的计算.接下来我们使用分治的思想求解这个最大子序列,前一篇博文提过,分治的思想是将大问题分解为同等类型的子问题,再将子问题求解,然后合并子问题得出原问题的解,其中用到了递归的思想,因

【Python之旅】第八篇:开发监控软件的思想与流程

最近两周时间里,一直都在学习监控软件的开发,虽然是简版的,可是在这个过程当中,对于要开发一个监控软件的大概框架和流程还真的学习了很多东西,而且也想,这些知识实在是很难通过看文章或者是书籍能学习得到,只有自己亲自去实践过,我想才可以慢慢体会到这中间的不易吧.而通过这样一个过程,发现自己在这方面的思想枷锁也慢慢地打开,也才慢慢体会到那种乐趣吧.这里,真的是非常感谢Alex老师非常精彩的讲解. 监控软件的大概流程如下:     当然,实际中学习的过程中并没有去监控MySQL或者是ngnix,而只是监控

数据结构与算法--递归

目录 1.递归的定义 2.求一个数的阶乘:n! 3.递归的二分查找 4.分治算法 5.汉诺塔问题 5.归并排序 6.消除递归 递归和栈 7.递归的有趣应用 ①.求一个数的乘方 ②.背包问题 ③.组合:选择一支队伍 8.总结 记得小时候经常讲的一个故事:从前有座山,山上有座庙,庙里有一个老和尚和一个小和尚,一天,老和尚给小和尚讲了一个故事,故事内容是“从前有座山,山上有座庙,庙里有一个老和尚和一个小和尚,一天,老和尚给小和尚讲了一个故事,故事内容......” 什么是递归,上面的小故事就是一个明显

【数据结构】 非递归前中后序遍历二叉树

数据结构学的递归了,深入了解后写一个三序非递归的版本. //测试数据:abd##eg##h##c#f## #include <cstdio> #include <iostream> typedef char ElemType; typedef struct Node { ElemType elem; struct Node *lchild,*rchild; }Node,*BiTree; typedef struct{ BiTree *base; BiTree *top; int s

[学习笔记]面向对象开发中的一些思想和原则

摘自<Java与模式>作者阎宏 面向对象的可复用设计的第一块基石:开闭原则(一个软件实体应当对扩展开放,对修改关闭) "开-闭"原则的手段和工具: 1)里氏代换原则:任何基类出现的地方,子类一定可以出现: 2)依赖倒转原则:要依赖于抽象,不要依赖于实现: 3)合成\聚合复用原则:要尽量使用合成\聚合,而不是继承关系达到复用的目的: 4)迪米特法则:一个软件实体应该与尽可能少的其它实体发生相互作用: 5)接口隔离原则:应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口

漫谈递归:递归的思想 --转载

为什么要用递归 编程里面估计最让人摸不着头脑的基本算法就是递归了.很多时候我们看明白一个复杂的递归都有点费时间,尤其对模型所描述的问题概念不清的时候,想要自己设计一个递归那么就更是有难度了. 很多不理解递归的人(今天在csdn里面看到一个初学者的留言),总认为递归完全没必要,用循环就可以实现,其实这是一种很肤浅的理解.因为递归之所以在程序中能风靡并不是因为他的循环,大家都知道递归分两步,递和归,那么可以知道递归对于空间性能来说,简直就是造孽,这对于追求时空完美的人来说,简直无法接接受,如果递归仅

开发设计的一些思想总结

尽量预测所有可能面临的问题,按照等级划分并建立蝴蝶效应的树状结构图. 日志系统是为运行期提供的,当然一些复杂的调试可能用得上.但日志是要提供有用的信息,而非毫无理由的try catch.try catch往往为了你不能预期且容易出问题的地方存在. 面向对象编程的优异在于便捷类重用,核心关键在于面向抽象编程. 越抽象的东西越不易变,所以核心的设计应该抽象出来. 面向过程的优异之处在于方法的重用方法重用,核心关键在于提炼方法策略便于重用 不要做重复的事情. 工作如此,同样编码设计亦如此. 当我们在两

数据结构二叉树的递归与非递归遍历之 实现可编译(1)java

前一段时间,学习数据结构的各种算法,概念不难理解,只是被C++的指针给弄的犯糊涂,于是用java,web,javascript,分别去实现数据结构的各种算法. 二叉树的遍历,本分享只是以二叉树中的先序遍历为例进行说明,中序遍历和后序遍历,以此类推! 二叉树递归与非递归遍历的区别,虽然递归遍历,跟容易读懂,代码量少,运算快,但是却容易出现溢出的问题,所以所以非递归遍历,在处理千万级的运算量时会先的很有用处. 二叉树的先序遍历:先访问根节点,再访问先后访问左右节点.如图: 二叉树的递归遍历之java

[python] python django web 开发 —— 15分钟送到会用(只能送你到这了)

1.安装python环境 1.1 安装python包管理器: wget https://bootstrap.pypa.io/get-pip.py sudo python get-pip.py ? 1.2 安装python虚拟环境virtualenv virtualenvwrapper 首先说明下为什么要装这两个包: First, it's important to understand that a virtual environment is a special tool used to ke