递归与分治

递归与分治

一、斐波那契(Fibonacci)数列的递归实现

他讲的一个故事:
如果说兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。假设所有兔子都不会死去,能够一直干下去,那么一年以后可以繁殖多少对兔子呢?

兔子的繁殖.png

用数学函数来定义:

使用函数定义.png

1.使用迭代来实现打印出前40位斐波那契数列数

#include <stdio.h>

int main()
{
    int i;
    int a[40];

    a[0] = 0;
    a[1] = 1;
    printf("%d %d ", a[0], a[1]);

    for( i=2; i < 40; i++ )
    {
        a[i] = a[i-1] + a[i-2];
        printf("%d ", a[i]);
    }

    return 0;
}

2.使用递归来实现打印出前40位斐波那契数列数

递归事实上就是函数自己调用自己,代码实现:

int Fib(int i)
{
    if( i < 2 )
        return i == 0 ? 0 : 1;
    return Fib(i-1) + Fib(i-2);
}
斐波那契数列的递归实现.png

代码实现:

#include <stdio.h>

int Fib(int i)
{
    if( i < 2 )
    {
        return i == 0 ? 0 : 1;
    }
    
    return Fib(i-1) + Fib(i-2);
}

int main()
{
    int i;
    for( i=0; i < 40; i++ )
    {
        printf("%d ", Fib(i));
    }
    
    return 0;
}

二、递归定义

在高级语言中,函数自己调用和调用其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称作递归函数。

注意:每个递归定义必须至少有一个条件,当满足这个条件时递归不再进行,即函数不再调用自身而是返回值。

之前的Fbi函数结束条件是:i < 2

对比两种实现斐波那契的代码,迭代和递归的区别是:
迭代使用的是循环结构,
递归使用的是选择结构。

  • 使用递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。
  • 但大量的递归调用会建立函数的副本,会消耗大量的时间和内存,而迭代则不需要此种付出。

递归函数分为调用和回退阶段,递归的回退顺序是它调用顺序的逆序。

举个例子,计算n的阶乘n!

n的阶乘.png

则递归算法如下:

int  factorial( n )
{
    if( 0 == n ){
        return 1;
    }else{
        return  n * factorial( n - 1 );
    }   
}

假设n的值传入是5,那么:

递归示意图.png

三、实例分析

编写一个递归函数,实现将输入的任意长度的字符串反向输出的功能。例如输入字符串HCX,则输出字符串XCH。

分析:
要将一个字符串反向地输出,一般采用的方法是将该字符串存放到一个数组中,然后将数组元素反向的输出即可。这道题要求输入是任意长度,所以不用递归的话,实现起来会比较麻烦(当然也可以用动态申请内存)。

递归它需要有一个结束的条件,那么可以将“#”作为一个输入结束的条件。

void print()
{
    char a;
    scanf(“%c”, &a);
    if( a !=‘#’)  print();
    if( a !=‘#’)  printf(“%c”, a);
}

假设从屏幕上输入字符串:ABC#

ABC#执行.png

四、分治思想

分而治之的思想古已有之,秦灭六国,统一天下正是采取各个击破、分而治之的原则。

分治思想在算法设计中也是非常常见的,当一个问题规模较大且不易求解的时候,就可以考虑将问题分成几个小的模块,逐一解决。

1.折半查找算法的递归实现

折半查找法是一种常用的查找方法,该方法通过不断缩小一半查找的范围,直到达到目的,所以效率比较高。

题目:有一个数组A[10],里面存放了10个整数,顺序递归。A[10]={2,3,5,7,8,10,12,15,19,21} ,任意输入一个用数字n,用折半查找法找到n位于数组中的位置。如果n不属于数组A,显示错误提示。要求用递归的方法实现折半查找。

#include <stdio.h>  
int bin_search(int key[],int low, int high,int k)  
{  
    int mid;  
    if(low>high)  
        return -1;  
    else{  
        mid = (low+high) / 2;  
        if(key[mid]==k)  
            return mid;  
        if(k>key[mid])  
            return bin_search(key,mid+1,high,k);        /*在序列的后半部分查找*/  
        else  
            return bin_search(key,low,mid-1,k);            /*在序列的前半部分查找*/  
    }  
}  
  
int main()  
{  
    int n , i , addr;  
    int A[10] = {2,3,5,7,8,10,12,15,19,21};  
    printf("The contents of the Array A[10] are\n");  
    for(i=0;i<10;i++)  
    printf("%d ",A[i]);                                /*显示数组A中的内容*/  
    printf("\nPlease input a interger for search\n");  
    scanf("%d",&n);                                /*输入待查找的元素*/  
    addr = bin_search(A,0,9,n);  
    if(-1 != addr)                                /*查找成功*/  
    printf("%d is at the %dth unit is array A\n ",n,addr);  
    else printf("There is no %d in array A\n",n);            /*查找失败*/  
    getchar();  
    return 0;  
}  

五、汉诺塔问题

在世界中心贝拿勒斯的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。

汉诺塔问题.jpg

思路:
这其实也是一个经典的递归问题。
我们可以做这样的考虑:

  1. 先将前63个盘子移动到Y上,确保大盘在小盘下。
  2. 再将最底下的第64个盘子移动到Z上。
  3. 最后将Y上的63个盘子移动到Z上。

由于每次只能移动一个圆盘,所以在移动的过程中显然要借助另外一根针才行。
也就是说第1步将1~63个盘子借助Z移到Y上,第3步将Y针上的63个盘子借助X移到Z针上。
那么我们把所有新的思路聚集为以下两个问题:
问题一:将X上的63个盘子借助Z移到Y上;
问题二:将Y上的63个盘子借助X移到Z上。

解决上述两个问题依然用相同的方法:
问题一的圆盘移动步骤为:

  1. 先将前62个盘子移动到Z上,确保大盘在小盘下。
  2. 再将最底下的第63个盘子移动到Y上。
  3. 最后将Z上的62个盘子移动到Y上。

问题二的圆盘移动步骤为:

  1. 先将前62个盘子移动到X上,确保大盘在小盘下。
  2. 再将最底下的第63个盘子移动到Z上。
  3. 最后将X上的62个盘子移动到Y上。

代码实现:

#include <stdio.h>

// 将 n 个盘子从 x 借助 y 移动到 z
void move(int n, char x, char y, char z)
{
    if( 1 == n )
    {
        printf("%c-->%c\n", x, z);
    }
    else
    {
        move(n-1, x, z, y);             // 将 n-1 个盘子从 x 借助 z 移到 y 上
        printf("%c-->%c\n", x, z);      // 将 第 n 个盘子从 x 移到 z 上
        move(n-1, y, x, z);             // 将 n-1 个盘子从 y 借助 x 移到 z 上
    }
}    

int main()
{
    int n;

    printf("请输入汉诺塔的层数: ");
    scanf("%d", &n);
    printf("移动的步骤如下: \n");
    move(n, 'X', 'Y', 'Z');

    return 0;
}

六、八皇后问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型例题;此处使用递归解决。

该问题是十九世纪著名的数学家高斯1850年提出:
在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

正确的结果应该是92种

以下是其中一种解法:


八皇后问题.jpg

代码实现:

#include <stdio.h>

int count = 0;

int notDanger( int row, int j, int (*chess)[8] )
{
    int i, k, flag1=0, flag2=0, flag3=0, flag4=0, flag5=0;

    // 判断列方向
    for( i=0; i < 8; i++ )
    {
        if( *(*(chess+i)+j) != 0 )
        {
            flag1 = 1;
            break;
        }
    }

    // 判断左上方
    for( i=row, k=j; i>=0 && k>=0; i--, k-- )
    {
        if( *(*(chess+i)+k) != 0 )
        {
            flag2 = 1;
            break;
        }
    }

    // 判断右下方
    for( i=row, k=j; i<8 && k<8; i++, k++ )
    {
        if( *(*(chess+i)+k) != 0 )
        {
            flag3 = 1;
            break;
        }
    }

    // 判断右上方
    for( i=row, k=j; i>=0 && k<8; i--, k++ )
    {
        if( *(*(chess+i)+k) != 0 )
        {
            flag4 = 1;
            break;
        }
    }

    // 判断左下方
    for( i=row, k=j; i<8 && k>=0; i++, k-- )
    {
        if( *(*(chess+i)+k) != 0 )
        {
            flag5 = 1;
            break;
        }
    }

    if( flag1 || flag2 || flag3 || flag4 || flag5 )
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

// 参数row: 表示起始行
// 参数n: 表示列数
// 参数(*chess)[8]: 表示指向棋盘每一行的指针
void EightQueen( int row, int n, int (*chess)[8] )
{
    int chess2[8][8], i, j;

    for( i=0; i < 8; i++ )
    {
        for( j=0; j < 8; j++ )
        {
            chess2[i][j] = chess[i][j];
        }
    }

    if( 8 == row )
    {
        printf("第 %d 种\n", count+1);
        for( i=0; i < 8; i++ )
        {
            for( j=0; j < 8; j++ )
            {
                printf("%d ", *(*(chess2+i)+j));
            }
            printf("\n");
        }
        printf("\n");
        count++;
    }
    else
    {
        for( j=0; j < n; j++ )
        {
            if( notDanger( row, j, chess ) ) // 判断是否危险
            {
                for( i=0; i < 8; i++ )
                {
                    *(*(chess2+row)+i) = 0;
                }
                
                *(*(chess2+row)+j) = 1;

                EightQueen( row+1, n, chess2 );
            }
        }
    }
}

int main()
{
    int chess[8][8], i, j;

    for( i=0; i < 8; i++ )
    {
        for( j=0; j < 8; j++ )
        {
            chess[i][j] = 0;
        }
    }

    EightQueen( 0, 8, chess );

    printf("总共有 %d 种解决方法!\n\n", count);

    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容