二叉树由一个根节点加上两个互不相交的左右子树构成,或者是一个没有结点的空树。
- 每个结点最多有两个子结点。
- 左子树和右子树不可互换,是有序的。
例如,结点A 为根节点,以结点B 为根节点的子树为左子树,以结点C为根节点的子树称为右子树。
二叉树的5种基本形态
- 空树
- 只含根节点
- 右子树为空
- 左子树为空
- 左右子树都不为空
满二叉树和完全二叉树
- 满二叉树 —>> 每一层都充满结点
- 叶子结点只能在最后一层
- 非叶子结点的度一定是2
- 在同样深度的二叉树中
- 满二叉树的结点数最多(因为每个结点都有两个孩子);
- 拥有的叶子结点的个数也最多(因为每个结点都有两个孩子,一直到最后一层叶子结点,必然是最多的);
- 在相同结点数的树中,满二叉树的深度最小。
- 完全二叉树 —>> 最后一层结点可以不满,但必须集中在左边。
- 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
- 某结点的度如果为1,则它只有左孩子。
- 叶子结点只能出现在最后两层。
- 相同结点的树中,完全二叉树的深度最小。
二叉树的性质
- 性质1
第 a 层的节点数,至多为 2a-1 - 性质2
深度为 k 的二叉树,节点数至多为 2k - 1 个 - 性质3
若 nk 代表度(孩子个数)为 k 的结点个数,则 n0 = n2 + 1(叶子节点数 = 2度结点树 + 1) - 性质4
具有 n 个结点的完全二叉树,深度 k = log2n + 1 - 性质5
将具有 n个结点的完全二叉树按照一维数组方式从左到右,从上层到子层,从0开始编号,编号为j的结点- 根节点编号为0
- 左子树根节点为 2j + 1,右子树为 2(j + 1)
- 判断有子树(即是判断是否有左子树)
2j + 1 ≤ n - 1,推导出 2j + 1 < n - 判断左右子树都有(即是判断有右子树)
2(j + 1) ≤ n - 1
二叉树的存储方式
- 顺序存储
将二叉树转换为完全二叉树进行存储。- 顺序存储的方式缺陷
- 转换为完全二叉树,浪费空间
- 新一层数据的插入不方便。
- 删除非叶子节点不方便。
- 顺序存储的方式缺陷
- 链式存储方式
二叉树的链式表示
- 由于顺序存储方式劣势比较明显,平常用到的表示方式都是链式表示。
二叉树的存储结构
- 采用链式存储的方式,结点只存单个元素值,节点之间通过指针联系,维持集合的关系。
- 这里定义了左右孩子指针的bool标记变量,标记是否用来存放前驱和后继。
```c
// 定义返回状态及返回码
define Status int
define OK 1
define ERROR 0
// 定义存储的元素类型define ElementType int
// 二叉树结点结构定义 typedef struct BinaryTreeNode { // 数据域 BiTreeNodeElementType data; // 指向左孩子的指针 BinaryTreeNode left; // 左指针功能标记,为true则指向前驱 bool ltag = false; // 指向右孩子的指针 BinaryTreeNode right; // 右指针功能标记,为true则指向后继 bool rtag = false; }BTNode, *BinaryTree;
> 二叉树的常用操作
- 初始化
- 拿到一个指向根结点的指针,即可确定一个二叉树
```c
/* 01_二叉树——初始化*/
Status initBiTree_T(BinaryTree& biTree)
{
biTree = NULL;
return OK;
}
销毁
- 销毁一个二叉树结点是通过指向该结点的指针进行的。
- 如果结点为空,则不需要进行操作
- 如果结点不为空,则需要先删除左右孩子结点,再删除该结点
/* 02_二叉树——销毁*/
Status destroyBiTree_T(BinaryTree& biTree)
{
// 如果结点不为空
if (biTree)
{
biTree->parent = NULL;
// 先删除左右子树
if (destroyBiTree_T(biTree->left) && destroyBiTree_T(biTree->right))
{
return OK;
}
// 再删除该结点
delete biTree;
}
return OK;
}
- 销毁一个二叉树结点是通过指向该结点的指针进行的。
判空(是否为空树)
- 即是判断根节点是否为空
/* 03_二叉树——判空*/
Status emptyBiTree_T(BinaryTree biTree)
{
return biTree == NULL ? OK : ERROR;
}
- 即是判断根节点是否为空
求深度
- 利用树的递归,采用累加的方式求得。
- 如果一个结点不为空,则该层的深度为1
- 一个结点的深度,即是其左结点深度,与右节点的深度的较大者,加上本层的深度1。 ```c / 04_二叉树——深度/ int treeDepth_T(BinaryTree biTree) { // 该结点为空,则该层深度为0 if (!biTree) { return 0; } // 否则,该层深度有1,要向上累加 else { int leftDepth = treeDepth_T(biTree->left); int rightDepth = treeDepth_T(biTree->right); // 左右子树的深度,加上本层的深度1,即是该结点的深度 return 1 + max(leftDepth, rightDepth); } }
/返回两个数较大的一个/ int max(int a, int b) { return a > b ? a : b; }
- 是否叶子结点
- 左右孩子都不存在,即是叶子结点
```c
/* 05_二叉树——是否是叶子结点*/
Status leafBiNode_T(BinaryTree biTree)
{
// 左右孩子都不存在,即为叶子结点
return (biTree->left == NULL && biTree->right == NULL) ? OK : ERROR;
}
求父节点
- 如果当前结点为空或,返回空。
- 通过遍历左右子树找其父结点,若找到,则将父结点指针回溯。
/* 06_二叉树——求父结点*/
BinaryTree parentBiNode_T(BinaryTree biTree, BinaryTree target)
{
if (!biTree)
{
return NULL;
}
if (biTree->left == target || biTree->right == target)
{
return biTree;
}
// 若左子树未找到,则找右子树
BinaryTree parent = parentBiNode_T(biTree->left, target);
if (!parent)
{
parent = parentBiNode_T(biTree->right, target);
}
// 将结果回溯上去
return parent;
}
深度优先遍历方式
- 递归的前中后序遍历
- 递归方式非常简单,注意好控制终止条件,按定义的遍历顺序即可。 ```c / 08二叉树——先序遍历递归/ void preOrderTraverse_Recur(BinaryTree biTree) { if (!biTree) { return; } visit(biTree); preOrderTraverse_Recur(biTree->left); preOrderTraverse_Recur(biTree->right); }
- 递归的前中后序遍历
/ 09二叉树——中序遍历递归/ void inOrderTraverse_Recur(BinaryTree biTree) { if (!biTree) { return; } inOrderTraverse_Recur(biTree->left); visit(biTree); inOrderTraverse_Recur(biTree->right); }
/ 10二叉树——后序遍历递归/ void postOrderTraverse_Recur(BinaryTree biTree) { if (!biTree) { return; } postOrderTraverse_Recur(biTree->left); postOrderTraverse_Recur(biTree->right); visit(biTree); }
- 先序遍历_非递归(根 –> 左 --> 右)
```c
/* 12_二叉树——先序遍历_非递归*/
void preOrderTraverse_NonRecur(BinaryTree biTree)
{
if (!biTree)
{
return;
}
// 采用栈存放当前结点的引用,用于左子树遍历完成,找到其右子树
SqStack nodeStack;
initStack_Sq(nodeStack);
BinaryTree popped = NULL;
// 外层:每一次循环完成一次左分支
while (!stackEmpty_Sq(nodeStack) || biTree)
{
// 对左分支的迭代,是一个访问和压栈的过程
while (biTree)
{
// 先序遍历的关键,优先访问根结点
visit(biTree);
push_Sq(nodeStack, biTree);
// 对左结点的迭代
biTree = biTree->left;
}
// 一次左分支完成
if (!stackEmpty_Sq(nodeStack))
{
// 左分支结束,则将其父结点出栈
popped = (BinaryTree)malloc(sizeof(BinaryTreeNode));
pop_Sq(nodeStack, popped);
// 迭代到待访问的右孩子,下次循环执行访问
biTree = popped->right;
}
}
destroyStack_Sq(nodeStack);
}
- 按照先序遍历规则,遇到任意一个非空结点,即优先访问,所以过程为:
- 访问它;
- 将它压栈;
- 迭代其左孩子;
- 访问完成左孩子,
- 将栈顶元素(以上的非空结点)出栈;
- 通过该出栈元素继续迭代其右孩子。
- 过程控制
- 将非空的控制放到while循环条件中,所以循环体内不用考虑当前结点的左右孩子非空性质。
- 入栈的结点是根结点。
- 内层循环,一次循环表示一次赋值到其左孩子的过程。
- 外层循环是控制遍历过程。一次循环代表一个结点的左子树的完成过程。以栈的非空性和当前结点的非空性共同决定是否还需继续遍历。
中序遍历_非递归(左 —> 中 —> 右)
/* 13_二叉树——中序遍历_非递归*/
void inOrderTraverse_NonRecur(BinaryTree biTree)
{
if (!biTree)
{
return;
}
SqStack nodeStack;
initStack_Sq(nodeStack);
BinaryTree popped = NULL;
while (!stackEmpty_Sq(nodeStack) || biTree)
{
// 遇到左孩子即压栈
while (biTree)
{
push_Sq(nodeStack, biTree);
biTree = biTree->left;
}
if (!stackEmpty_Sq(nodeStack))
{
// 左孩子完毕,则栈顶为最后一个左孩子,进行输出
popped = (BinaryTree)malloc(sizeof(BinaryTreeNode));
pop_Sq(nodeStack, popped);
visit(popped);
// 继续迭代右子树的左孩子
biTree = popped->right;
}
}
destroyStack_Sq(nodeStack);
}
- 按照中序遍历规则,遇到任意非空结点,需要将左子树访问完成才能访问它,所以过程为:
- 压栈;
- 迭代其左孩子。
- 左孩子迭代完成。
- 栈顶元素为该非空结点,出栈,访问它;
- 迭代到其右孩子,作为下一个非空结点的迭代。
- 过程控制
- 同先序遍历一样,非空的控制放到while循环体条件,循环体内不用考虑非空性。
- 根据中序的规则,对结点的访问放到迭代完成其左子树之后。
后序遍历 (左 —> 右 —> 中)
/* 14_二叉树——后序遍历_非递归*/
void postOrderTraverse_NonRecur(BinaryTree biTree)
{
if (!biTree)
{
return;
}
// 存任意非空结点的栈
SqStack nodeStack;
initStack_Sq(nodeStack);
push_Sq(nodeStack, biTree);
// 标记前一次访问的结点
BinaryTree pre = NULL;
while (!stackEmpty_Sq(nodeStack))
{
// 获取栈顶元素,不出栈,分两种情况讨论
getTop_Sq(nodeStack, biTree);
/**
* 第一大类:
* 1. 不存在左右结点;
* 2. 前一次访问的结点不为空,且前一次访问的结点【等于当前结点的左孩子】或【右孩子】
*/
if ((!biTree->left && !biTree->right)
|| (pre != NULL
&& (pre == biTree->left || biTree == biTree->right)
))
{
visit(biTree);
BinaryTree bt = NULL;
pop_Sq(nodeStack, bt);
pre = biTree;
}
// 第二大类情况,有孩子未被访问,先右后左的优先级入栈其孩子
else
{
// 优先入栈右结点
if (biTree->right)
{
push_Sq(nodeStack, biTree->right);
}
if (biTree->left)
{
push_Sq(nodeStack, biTree->left);
}
}
}
}
- 规则:遇到任意一个结点,必须优先访问其左子树,其次访问右子树,最后再访问它本身。
- 难题:对于左右子树的访问,都有访问完成回溯的情况:
- 对于有左右子树的时候,左子树访问完成的回溯,当前结点不能出栈并访问;
- 右子树访问完成,才能进行出栈和访问。
- 需要区分回溯的情况,是从左子树回溯的,还是从右子树回溯的。
- 情况划分:根据这种规则,将遇到一个非空结点的情况划分如下:
- 无左右孩子 –> 可立即访问
- 有孩子,但孩子都访问完成 –> 可立即访问
- 有孩子未被访问。 –> 不可立即访问
- 实现方案:
- 全局设置一个指针,记录上一次被访问的结点。
- 遇到一个结点
- 可立即访问 –>
- 访问它;
- 将栈顶出栈,赋值为上一次遍历的结点;
- 不可立即访问 -> 按优先右子树,再左子树进行压栈,保证左子树的访问在右子树之前。
- 层次遍历(广度优先) ```c / 15_二叉树——层次遍历/ void breadthTraverse(BinaryTree biTree) { if (!biTree) { return; } // 存放子树根结点的队列 CircularQueue cq; initQueue_Cq(cq); enqueue_Cq(cq, biTree); // 保存出队列的元素 BinaryTree deQueued = NULL;
- 可立即访问 –>
while (!queueEmpty_Cq(cq)) { // 任何结点都在出队列时候进行访问。 dequeue_Cq(cq, deQueued); visit(deQueued); if (deQueued->left) {
enqueue_Cq(cq, deQueued->left);
} if (deQueued->right) {
enqueue_Cq(cq, deQueued->right);
} } } ```
首先将根结点入队列,作为迭代的开始。
- 迭代的结点永远从队列出队元素开始。
- 从队列中取出一个结点,访问它,并将它的孩子入队列。
最终队列为空,层次遍历完毕。
- 是否完全二叉树判断
/* 30_二叉树——是否完全二叉树*/
bool isCompleteBinary_T(BinaryTree biTree)
{
// 用来标记【只有左孩子】或者【无孩子】
bool flag = false;
// 空树,直接返回是
if (!biTree)
{
return true;
}
// 保存当前判断结点
BinaryTree cur = NULL;
// 用循环队列保存下一层待访问结点
CQueue cq;
initQueue_Cq(cq);
enqueue_Cq(cq, biTree);
// 队列非空则表示还有结点未被访问
while (!queueEmpty_Cq(cq))
{
dequeue_Cq(cq, cur);
if (flag)
{
if ((!cur->rtag && cur->right) || (!cur->ltag && !cur->left))
{
return false;
}
}
else
{
// 左右结点都存在
if ((!cur->ltag && cur->left) && (!cur->rtag && cur->right))
{
enqueue_Cq(cq, cur->left);
enqueue_Cq(cq, cur->right);
}
// 只存在右结点,则非完全
else if (!cur->rtag && cur->right)
{
return false;
}
// 对【只有左孩子】,【无孩子】情况,做标记
else if (!cur->ltag && cur->left)
{
enqueue_Cq(cq, cur->left);
flag = true;
}
else
{
flag = true;
}
}
}
// 判断到最后都没有中间返回,则它是完全二叉树
return true;
}
- 是否完全二叉树判断
首先,明确完全二叉树的定义,除了最后一层,其余每层都充满结点,最后一层的结点都集中在左边。
- 完全二叉树按层来判断,则需要参考层次遍历的方式,采用队列数据结构,将当前访问到的结点的孩子加入队列。
- 对是否最后一层的特殊判断,设置一个标记变量flag。
- 若队列当前结点只有左孩子或者没有孩子,标记true。此后出现的结点按完全二叉树定义,应该都没有孩子,若出现了孩子,则不是完全二叉树。
非最后一层。
- 左右孩子都有,则直接入队。
- 只有右孩子,则非完全,直接返回。
- 只有左孩子,按规则,达到最后一层。
- 左孩子入队;
- 标记置为true;
- 无孩子,也是最后一层,标记置为true。
- 叶子结点个数
/* 16_二叉树——叶子个数*/
int leafCount_T(BinaryTree biTree)
{
// 遇到空结点,则个数不变
if (!biTree)
{
return 0;
}
// 没有左右孩子,则它是叶子结点
else if (!biTree->left && !biTree->right)
{
return 1;
}
// 否则,求它的子树的叶子结点个数
else
{
return leafCount_T(biTree->left) + leafCount_T(biTree->right);
}
}
- 叶子结点个数
当前为空结点,则个数增加 0;
- 当前非空,无左右孩子,则个数增加 1;
当前非空,有孩子,则叶子个数为其左孩子叶子个数 + 右孩子叶子个数。
- 第 k 层的结点个数
/* 17_二叉树——第k层结点数目*/
int getKLevel_T(BinaryTree biTree, int k)
{
// 以根结点开始的第 k 层 => 以根结点的子孩子开始的第 k-1 层
if (!biTree)
{
return 0;
}
// 递归结束条件:1即为当前层级,以当前结点为开始的第一层,即当前根结点,只有1个
if (k == 1)
{
return 1;
}
return getKLevel_T(biTree->left, k - 1) + getKLevel_T(biTree->right, k - 1);
}
- 第 k 层的结点个数
以根结点开始的第k层 => 以根结点的孩子开始的第 k - 1层。
注意控制递归终止条件。
- 两个二叉树结构是否相同 ```c / 18_二叉树——结构是否相同/ bool structureCompare_T(BinaryTree bt1, BinaryTree bt2) { /**
- 总的思路是,遇到不同的即返回false,否则一直比较,比较完成返回true */ // 到最终两个一样为空 if (!bt1 && !bt2) { return true; } // 当一个为空,另一个不为空的时候 else if ( (!bt1 && bt2) || (!bt2 && bt1) ) { return false; } // 都不为空,则比较其孩子 bool same = structureCompare_T(bt1->left, bt2->left); same = same && structureCompare_T(bt1->right, bt2->right); return same; } ```
等价于比较结点有或没有的情况。
- 遇到一个有另一个没有的情况返回false。
- 比较到最后都相同则返回true。
- 求一个二叉树的镜像
/* 19_二叉树——求镜像*/
void treeMirror_T(BinaryTree biTree)
{
// 左右孩子互换,从上到下
if (!biTree)
{
return;
}
// 先交换左右孩子根结点
BinaryTree temp = biTree->left;
biTree->left = biTree->right;
biTree->right = temp;
// 再对左右孩子分别进行互换
treeMirror_T(biTree->left);
treeMirror_T(biTree->right);
}
- 求一个二叉树的镜像
求两个结点的最低公共祖先结点
- 首先确定计算参数,在一个树中求两个结点的最低公共祖先结点。入参为指向确定树的根结点的指针和指向两个结点的指针。
- 可能的情况有
- 直系关系。一个结点直接是另一个结点的直系祖先。
- 旁系关系。两个结点没有直接继承关系。
算法描述。
/* 21_二叉树——找最低公共祖先*/
BinaryTree findLCA_T(BinaryTree biTree, BinaryTree bt1, BinaryTree bt2)
{
// 首先对当前根结点进行判断
// 如果当前根结点为空,或是等于两个待判断结点中的任意一个,则当前根结点即是最低公共祖先
if (!biTree)
{
// 这个空结果,影响下面递归的判断
return NULL;
}
if (biTree == bt1 || biTree == bt2)
{
return biTree;
}
// 否则分别在根结点的左右子树进行查找,两边都有一个结果,可能为空
BinaryTree left = findLCA_T(biTree->left, bt1, bt2);
BinaryTree right = findLCA_T(biTree->right, bt1, bt2);
// 分别在当前根结点结点两边
if (left && right)
{
return biTree;
}
// 在其中一边,取非空的一边
return left ? left : right;
}
- 运用递归的思想,即可以先求出两个结点对于当前根结点的左右子树的关系。
- 两个结点对左子树和右子树都有关系。
- 那么此时的根结点即是两个结点的最低公共祖先。
- 两个结点都只与左子树或右子树有关系。
- 那么即是求所在的子树和两个结点的关联关关系。
- 两个结点对左子树和右子树都有关系。
- 递归终止条件控制
- 当前根结点为空,则所求的最低公共祖先不存在,为空。
- 当前根结点等于结点中的一个,那么当前结点即是所求的最低公共祖先结点。
二叉树任意两个结点的距离 ```c / 22_二叉树——结点距离/ int nodeDistance_T(BinaryTree biTree, BinaryTree bt1, BinaryTree bt2) { // 先找到最低公共父结点 BinaryTree lca = findLCA_T(biTree, bt1, bt2);
// 再求出LCA结点到两个结点距离,求和 int left = levelDist_T(lca, bt1); int right = levelDist_T(lca, bt2);
return left + right; }
/ 23_二叉树——结点相隔层数/ int levelDist_T(BinaryTree biTree, BinaryTree bt) { // 用 -1 标识没找到 if (!biTree) { return -1; } // 找到了,当前层,层数不变 if (biTree == bt) { return 0; }
// LCA结点和目标结点是直系关系。从LCA结点往下找
int levelDist = levelDist_T(biTree->left, bt);
// 若左子树没有找到,则找右子树
if (levelDist == -1)
{
levelDist = levelDist_T(biTree->right, bt);
}
// 找到了,则回溯
if (levelDist != -1)
{
return levelDist + 1;
}
// 左右子树都没找到
return -1;
}
- 等价于**LCA结点**到两个结点的距离**(两个直系距离)之和**,其中每隔一个父子层级关系,距离为 1 。
- LCA结点与目标结点的直系距离,则需要运用递归思想,从LCA结点向下找目标结点。
- 迭代的结点是第一个参数(当前根结点)
- 如果左子树找不到,则找右子树
- 找到之后需要回溯,加上当前的层级距离为 1。
- 结点的所有祖先
```c
/* 24_二叉树——结点所有祖先*/
bool allAncestors_T(BinaryTree biTree, BinaryTree bt)
{
// 利用求两个直系结点距离的思路
// 如果到最叶子结点都没有找到,则返回false,找到则返回true
if (!biTree)
{
return false;
}
if (biTree == bt)
{
return true;
}
// 从左右子树找
if (allAncestors_T(biTree->left, bt) || allAncestors_T(biTree->right, bt))
{
// 找到时候访问,并进行回溯
visit(biTree);
return true;
}
// 左右子树都每找到
return false;
}
- 可以参考两个结点的直系距离的思路。实际也是递归,从上往下找的过程。
- 在回溯的时候对父结点进行访问,则回溯到根结点,所有的祖先已经被访问到了。
线索化二叉树
中序线索化及遍历
中序线索化 ```c / 26_二叉树——中序线索化/ void inThreading_T(BinaryTree& bt, BinaryTree& pre) { if (!bt) { return; } if (!bt->ltag) { inThreading_T(bt->left, pre); }
// 进行中序的线索化操作 // 遇到左孩子为空,即赋值为前置 if (!bt->left || bt->ltag) { bt->ltag = true; bt->left = pre; } // 若前置结点非空,且没有右孩子 if (pre && (!pre->right || pre->rtag)) { pre->rtag = true; pre->right = bt; } // 当前结点 => 下一次迭代的前置结点 pre = bt;
if (!bt->rtag) { inThreading_T(bt->right, pre); } }
- 线索化利用了中序递归遍历的逻辑,将对结点的访问改为线索连接的过程。
- 线索连接,在两个参数的基础上做递归:
- 当前结点
- 前一个被访问的结点
- 线索连接过程要点<br />* 仅对**非线索指针**进行线索化,否则造成死循环。<br />* **当前连接前驱**。当前结点,左孩子为空,则置为前驱线索,指向前驱;<br />* **前驱连接当前**。若前驱节点的右孩子为空,则将当前结点置为其前驱的后继(需判断第一个结点,前驱为空的情况)<br />* **迭代pre变量**。线索连接完成,当前结点迭代为下一次访问的前驱。<br />* 为了兼容**多次线索**或**多种线索**的过程。增加对**当前结点左标记**与**前驱结点右标记**的判断
- 中序线索遍历
```c
/* 29_二叉树——中序线索遍历*/
void inThreadTraverse_T(BinaryTree biTree)
{
if (!biTree)
{
return;
}
// 迭代的变量,保存当前及其上一次的结点
BinaryTree cur = biTree;
// 结点为空则迭代结束(线索作用)
while (cur)
{
// 找到从根节点开始的左子树的叶子结点【中序第一个结点】
while (cur->left && !cur->ltag)
{
cur = cur->left;
}
visit(cur);
// 若结点没有右子树,只有后继,则直接输出它
while (cur->rtag)
{
visit(cur->right);
cur = cur->right;
}
// 有右子树,则按照中序遍历规则,必须先访问完成【右子树根结点的左子树 cur->right-> left】才能访问【右子树的根结点 cur->right】
// 迭代其右子树,重新进入上面的逻辑
cur = cur->right;
}
}
- 按照中序的规则,优先访问完成左子树。所以每遇到一个结点,先找到**左子树的最左叶子**。
- 对于结点右孩子的访问情况
- 节点的**右孩子**,在**遍历完成之前的迭代过程**都是**非空**的(线索的作用)
- 区分结点**右孩子**作为**线索**和**子树**的区别。
- **作为线索**,则该线索指向的结点的左子树是不能再被访问的(**当前结点**即是从**线索指向结点的左孩子**迭代下来的)
- **作为子树**,则按中序规则,序优先迭代左子树,即回到主循环中。
前序线索化及遍历
前序线索化
/* 25_二叉树——前序线索化*/
void preThreading_T(BinaryTree& bt, BinaryTree& pre)
{
if (!bt)
{
return;
}
// 进行前序线索化
if (!bt->left || bt->ltag)
{
bt->ltag = true;
bt->left = pre;
}
if (pre && (!pre->right || pre->rtag))
{
pre->rtag = true;
pre->right = bt;
}
pre = bt;
// 孩子不作为线索,才执行
if (!bt->ltag)
{
preThreading_T(bt->left, pre);
}
if (!bt->rtag)
{
preThreading_T(bt->right, pre);
}
}
- 利用前序递归遍历的逻辑,将访问过程改为线索化过程。
- 主逻辑同中序线索化,同样方法兼容多种和多次线索的过程。
- 只对非线索指向的结点进行线索化,避免死循环。
前序线索遍历
/* 28_二叉树——前序线索遍历*/
void preThreadTraverse_T(BinaryTree biTree)
{
if (!biTree)
{
return;
}
// 迭代的变量
BinaryTree cur = biTree;
// 结点为空则代表结束
while (cur)
{
visit(cur);
// 优先访问当前结点
while (cur->left && !cur->ltag)
{
cur = cur->left;
visit(cur);
}
cur = cur->right;
}
}
- 按前序遍历的思路,优先访问根结点。
- 顺着线索访问即可,不用关注右结点是子树还是线索。
- 后序线索化及遍历
- 后序线索化逻辑同前序和中序一致。
- 由于后序线索化对遍历的帮助不大,所以不再纠结于后序的线索遍历。