1递归(Recursion)
递归就是函数自己调用自己的能力。函数以自己视角来看待问题,问题被一系列的自己所表述。
如果一个问题是递归,那么它有一个或多个简单基线(base case)来停止递归自己,其他分线都是一系列的同类小问题。这类问题使用递归函数很容易解决。
比如计算n!
a 首先存在简单基线(停止条件),不需要递归计算。0! = 1;
b 其他分线都能被这样解决:
-首先解决靠近基线的较小的问题得到较小问题解。
-然后在小问题解上执行额外的计算得到较大问题的解。
2尾递归(Tail recursion)
如果一个递归函数,除了返回值,其他什么额外操作都不做,那么它就是尾递归的函数。
比如n!
形式这样不是尾递归,因为函数返回还做了n*操作。所以函数调用的stack frame就会保存用来做n*操作
factorial(n) {
if (n == 0) return 1;
return n * factorial(n - 1);
}
而形式:
factorial1(n, accumulator) {
if (n == 0) return accumulator;
return factorial1(n - 1, n * accumulator);
}
是尾递归,因为函数返回不做其他操作。所以stack frame 不需要保留
尾递归可以等价于如下形式
factorial1(n, accumulator) {
beginning:
if (n == 0) return accumulator;
else {
accumulator *= n;
n -= 1;
goto beginning;
}
}
或者
factorial1(n, accumulator) { while (n != 0) { accumulator *= n; n -= 1; } return accumulator; }
=====================================================================
3 树(递归数据结构)
树是由 n 个结点组成的有限集合
如果 n = 0, ,称为空树;如果 n > 0, 则 :
• 有一个特定的称之为根(root)的结点。
• 除根以外的其它结点划分为 m 个互不相交的有限集合,每个集合又是一棵树。
-a 度:结点拥有的子树数称为结点的度
-b层数:从根开始定义起,根为第1层,根的子节点为第2层,以此类推
树中节点的最大层树即为树的高度,或者深度。
-c高度:从该节点起到叶子节点的最长简单路径的边数。(简单路径:无重复边的路径)
-d路径和路径长度: 在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
-e结点的权及带权路径长度: 若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。
-f树的带权路径长度: 树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
树和链表一样是基于节点node的数据结构。结构里节点指向下一个节点。只是链表只指向一个节点,树可以指向任意多个节点。
(1)表示方法
在存储结点信息的同时,附加两个分别指向该结点最左孩子和右邻兄弟的指针域leftmostchild和rightsibling,即可得树的孩子兄弟链表表示。
typedef struct node // 结点类型
{
struct node * lchild ; // 左孩子指针
ElemType data ; // 抽象数据元素类型
struct node * rchild ; // 右孩子指针
}node, *pnode;
注意:
这种存储结构的最大优点是:它和二叉树的二叉链表表示完全一样。可利用二叉树的算法来实现对树的操作。
这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
其中:
ltag和rtag是增加的两个标志域,用来区分结点的左、右指针域是指向其左、右孩子的指针,还是指向其前趋或后继的线索。
![](http://student.zjzk.cn/course_ware/data_structure/web/shu/shu6.41.gif)
![](http://student.zjzk.cn/course_ware/data_structure/web/shu/shu6.42.gif)