zoukankan      html  css  js  c++  java
  • 数据结构开发(15):递归的思想与应用

    0.目录

    1.递归的思想

    2.递归的应用

    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 != '' )
        {
            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 == '' )
        {
            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 == '' )
        {
            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.小结

    • 递归是一种将问题分而自治的思想
    • 用递归解决问题首先要建立递归的模型
    • 递归解法必须要有边界条件否则无解
    • 不要陷入递归函数的执行细节,学会通过代码描述递归问题
    • 程序运行后的栈存储区专供函数调用使用
    • 栈存储区用于保存实参,局部变量,临时变量,等
    • 利用栈存储区能够方便的实现回溯算法
    • 八皇后问题是栈回溯的经典应用
  • 相关阅读:
    11 数值的整数次方
    10 二进制中1的个数
    6 重建二叉树
    5 从尾到头打印链表
    计算机网络面试题
    Http和Https的区别
    UVALive 7749 Convex Contour (计算几何)
    Gym 101190H Hard Refactoring (模拟坑题)
    UVa 11324 The Largest Clique (强连通分量+DP)
    HDU 6006 Engineer Assignment (状压DP)
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10146924.html
Copyright © 2011-2022 走看看