该题目要求我们输出从每个叶子节点到根节点的路径,而我们只能从根节点遍历到叶子结点,这说明我们遍历的路径和题目要求的路径是逆序的,那么存储结点数据的容器必须遵循先进后出的规则,而我们发现栈就很好地符合这一标准,那就可以首先确定是要用栈来存储结点数据。
首先不考虑如何输出从每个叶子节点到根节点的路径,简化下问题,我们先来考虑如何从根结点到一个叶子结点的问题,假设有一层数为h的二叉树,如何能走到该二叉树第h层的最左边的结点,这很简单,从根结点开始遍历,用栈存储路径上每个结点的数值,有左子树往左走,没有左子树再往右子树走,如果都没有左右子树说明到了叶子结点了,那么我们的路径就完整了,就可以打印栈的内容了,代码实现如下:
void RouteToRoot(BiTree root, SqStack* stack) {
if (root) {
stack_push(stack, root->data);//只要root不为空,就将root的值入栈
if (!root->left_child&&!root->right_child)printStack(stack);
//当root没有左右孩子时,是叶子结点,此时叶子结点到根结点的路径已经完整
//则应打印栈的所有元素
else {
//当结点不是叶子结点时,先往左子树走
RouteToRoot(root->left_child, stack);
//再往右子树走
RouteToRoot(root->right_child, stack);
}
}
}
遍历一条路径是可以了,那么如何遍历该二叉树的所有路径呢?这不是很简单吗,遍历完一条路径后返回根节点再遍历一次,但是这样会出现一个新问题,如何确定我们当前遍历的路径不是之前已经遍历过的路径呢?这个问题有很多种办法解决,比如说存储遍历过的路径的数值,再将之与当前遍历的路径比较来确定,但这样会导致算法复杂度和空间复杂度很高,排除这种算法,如下图所示。
1.假设我们遍历到了叶子结点d,此时这条路径便已经完整了,而结点b还有另一个右孩子结点e,那么遍历到d的路径和遍历到e的路径只差了最后一个结点,那么我们只需要把结点d出栈,再把结点e入栈,遍历到e的路径不就完整了吗,所以每次遍历到叶子结点我们将栈打印完后,只需将该叶子结点出栈。
2.假设我们此时已经遍历完结点e并出栈了,此时b的左右孩子结点已经都遍历完了,不再被需要了,那么我们如何遍历到c结点呢,只需要将结点b出栈,那么a的左子树已经遍历完,此时再往右子树走,也就是c点走,这样我们就能获取遍历到c结点的路径。
总结一下,结点出栈有两种情况,一是叶子结点且路径已被打印过,二是该结点的左右孩子结点都被遍历过了,下面代码实现。