递归算法造成的问题分析与解决

    递归,在编码中应该算是一种很常见的算法了。之前在学习C语言的时候,也同样了解过一些基本的算法,比如斐波那契。在学习的时候,对算法这种编程技巧就有了一种浓浓的敬畏之心,因为觉得会算法的人就很厉害了,可以把很长的代码块通过一段简短的算法解决并得到想要的结果。

今天在实际工作中也遇到了算法中一些问题。整理一下,形成今天的内容【算法中的[递归][1]算法】。

[Meting autoplay=“true”]

[Music server=“xiami” id=“1774428011” type=“song”/]

[/Meting]

什么是递归

借用百科的一段话来表述就是:

一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。

递归的能力

同样引用百科的一句话,个人觉得非常经典:

用有限的语句来定义对象的无限集合;

这句话什么意思呢,通俗点来理解就是,我程序只有一套,但是我可以通过递归(自身调用自身)的特性,不管你有多少个值,我都能妥妥的给你按照特定的程序逻辑处理喽。(就是这么强势,嘿嘿!)

自己之前对递归的理解就是自己调用自己,通过多次的自己调用自身,通过同一套程序方法,来达到解决问题的目的;这种方法可以明显的减少代码量,而且灵活,尤其是在多重循环的时候,可以采用递归来替代。但是这种方法也有缺点,就是增加了程序了运行速度,而且有时候可能会因为编码不当,造成死循环、栈溢出等问题。但是只要用好,解决问题还是不差的;

问题所在

今天在工作中,遇到一个把无限分类的多维数组转换成html树的时候,就遇到了点小麻烦,可能是因为一时马虎,当局者迷的缘故,自己就像掉进死循环里,一直出不来,后来,也是在请教身边的朋友后,才得到解决,下面我们来看一下出现了什么问题(其实问题已经提在了[SF社区][2]上,问题标题是[多维数组分类树 组合html树的问题?(递归)][3],有兴趣的小伙伴可以去看下):

数组结构

最初的数组结构是一个无限分类的多维数组:

数组结构

由上图可以看到,这个数组的childs下标里面对应的就是子分类,分类可以有无限个。我们要把它组装成下图的理想形态:

理想输出

虽然看着很简单,但是实际上走了不少弯路,最后卡在了一个点上,始终没出来。我最开始的递归方法是:

问题代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

function creatHtmlTree($tree)

{

    // 生命一个静态变量

    static $htmlTree;

    $htmlTree .= '<ul>';

    foreach ($tree as $key => $value) {

        $htmlTree .= "<li><i class='icon-folder-open'></i>{$value['name']} <a href=''>Goes somewhere</a>";

        if (isset($value['childs']) && is_array($value['childs'])) {

            // 每次的结果累加到静态变量上

            $html = creatHtmlTree($value['childs']);

            $htmlTree .= $html;

        } 

        $htmlTree .= "</li>";

    }

    $htmlTree .= "</ul>";

    return $htmlTree;

}

通过测试得到了下图的错误内容:

错误输出

问题分析

我们可以看到,它给$htmlTree这个变量给了多余的值,通过求教才明白,我的代码中

1
2
3
4

static $htmlTree;

$htmlTree .= '<ul>';

以及if里的

1
2
3
4

$html = creatHtmlTree($value['childs']);

$htmlTree .= $html;

代码逻辑写的有问题,问题在于,既然设定了$htmlTree为静态变量,那么在递归中的每一次计算中,都默认已经$htmlTree赋予了最后的计算结果,我在if里又把结果加了一次,所以才造成了输出出现问题的情况,那么如何改成呢?只需把:

1
2
3
4

$html = creatHtmlTree($value['childs']);

$htmlTree .= $html;

改为:

1
2

creatHtmlTree($value['childs']);

即可。这样,他在递归运算的时候就可以通过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

$htmlTree .= '<ul>';



$htmlTree .= "<li><i class='icon-folder-open'></i>{$value['name']} <a href=''>Goes somewhere</a>";



$htmlTree .= "</li>";



$htmlTree .= "</ul>";

这四行代码来给$htmlTree累加数值就可以了。

解决

来看一下,最终形态的递归方法是什么样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

// 递归运算创建html树结构

function creatHtmlTree($tree)

{

    // 声明静态变量

    static $htmlTree;

    $htmlTree .= '<ul>';

    foreach ($tree as $key => $value) {

        // 给静态$htmlTree变量累加值

        $htmlTree .= "<li><i class='icon-folder-open'></i>{$value['name']} <a href=''>Goes somewhere</a>";

        if (isset($value['childs']) && is_array($value['childs'])) {

            creatHtmlTree($value['childs']);

        } 

        $htmlTree .= "</li>";

    }

    // 赋值ul闭合标签

    $htmlTree .= "</ul>";

    return $htmlTree;

}

这样就可以解决了。同样还有另外一种方式,那就是通过返回值的方式,来进行递归运算:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

// 递归运算创建html树结构

function creatHtmlTree($tree)

{

    // $htmlTree为普通局部变量;

    $htmlTree .= '<ul>';

    

    foreach ($tree as $key => $value) {

        // 给变量$htmlTree累加值

        $htmlTree .= "<li><i class='icon-folder-open'></i>{$value['name']} <a href=''>Goes somewhere</a>";

        if (isset($value['childs']) && is_array($value['childs'])) {

            // 递归中每次的结果累加到$htmlTree

            $htmlTree .= creatHtmlTree($value['childs']);

        } 

        $htmlTree .= "</li>";

    }

    // 赋值ul闭合标签

    $htmlTree .= "</ul>";

    return $htmlTree;

}

通过这种返回值累加的算法,也同样可以得到想要的结果。

测试

今天为了测试和解决递归算法带来的问题,特意找了段代码进行测试,也是我下午一直在实验的demo,手痒痒的小伙伴,可以立马copy到本地亲自体验一下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

<?php 



$data = [

    ['id'=>1,'parentid'=>0,'name'=>'中国'],

    ['id'=>2,'parentid'=>0,'name'=>'美国'],

    ['id'=>3,'parentid'=>0,'name'=>'韩国'],

    ['id'=>4,'parentid'=>1,'name'=>'北京'],

    ['id'=>5,'parentid'=>1,'name'=>'上海'],

    ['id'=>6,'parentid'=>1,'name'=>'广西'],

    ['id'=>7,'parentid'=>6,'name'=>'桂林'],

    ['id'=>8,'parentid'=>6,'name'=>'南宁'],

    ['id'=>9,'parentid'=>6,'name'=>'柳州'],

    ['id'=>10,'parentid'=>2,'name'=>'纽约'],

    ['id'=>11,'parentid'=>2,'name'=>'华盛顿'],

    ['id'=>12,'parentid'=>3,'name'=>'首尔'],

];





 /**格式化数组输出**/

function p($arr)

{

    echo "<pre>";

    echo '========================开始========================';

    echo "</br>";

    if( $arr ){

        print_r($arr);

    } else {

        echo '此值为空';

    }

    echo "</br>";

    echo '========================结束========================';

    echo "</pre>";

}



/**

 * 多维数组树形结构

 */

function tree($data, $pid = 0)

{

    $children = [];

    foreach ($data as $key => $value) {



        if ($value['parentid'] == $pid) {

            $children[] = $value;

        }

    }

    if (empty($children)) {

        return null;

    }



    foreach ($children as $key => $value) {

        $chid = tree($data, $value['id']);

        if ($chid != null) {

            $children[$key]['childs'] = $chid;

        }

    }



    return $children;

}



// 递归运算创建html树结构

function creatHtmlTree($tree)

{

    // $htmlTree为普通局部变量;

    $htmlTree .= '<ul>';

    

    foreach ($tree as $key => $value) {

        // 给$htmlTree变量累加值

        $htmlTree .= "<li><i class='icon-folder-open'></i>{$value['name']} <a href=''>Goes somewhere</a>";

        if (isset($value['childs']) && is_array($value['childs'])) {

            // 递归中每次的结果累加到$htmlTree

            $htmlTree .= creatHtmlTree($value['childs']);

        } 

        $htmlTree .= "</li>";

    }

    // 赋值ul闭合标签

    $htmlTree .= "</ul>";

    return $htmlTree;

}





$tree = tree($data);

$htmlTree = creatHtmlTree($tree);



p($tree);

p($htmlTree);

总结

算法,这门技巧,是我向往的高级玩意儿。觉得它挺炫的,在开头我就有提到,可以用极短的代码解决复杂的业务程序,大大减少的代码量。但它同样也像一颗隐形炸弹一样,也充满着威胁。所以,在之后的递归算法中,应该小心谨慎,避免出现问题。

好了,今天就分享到这里,以上。

补充

在百科里看到递归解释的两句话,也同样经典,奉上:

  1. 递归需要有边界条件、递归前进段和递归返回段

  2. 当边界条件不满足时,递归前进;当边界条件满足时,递归返回

这大概说的就是递归的运行条件吧。

完。