本文整理自@code_peak的《线索二叉树的详细实现(C++)》文章,源码可在原贴查看:https://blog.csdn.net/code_peak/article/details/120124063,此文整理用于个人复习。
线索二叉树的概念
线索化的目标:使二叉树中的每个结点能够快速地找到其中序遍历的前驱结点或后继结点,从而能够快速地进行遍历。
直接思想:使每个结点增加两个指针,新的左指针指向其前驱结点,右指针指向其后继结点。但这么做会为每个结点都增加两个新的指针,大大增加了存储开销。
优化思想:对于n个结点的二叉树,其共有n+1个空指针(总共2n个指针,只使用了n-1个),如果某个结点的左指针为空,则使其指向前驱结点,若右指针为空,则指向后继结点,以充分地利用可用资源。
左右孩子和前驱后继的区分:在遍历过程中,如何区分一个结点的左右指针指向的是孩子结点还是前驱后继结点呢?可以通过为每个结点增加两个布尔类型的标识变量实现,假设为ltag和rtag,若ltag==0,则代表指向的是左孩子,若ltag==1,则代表指向的是前驱结点,rtag同理。
需要注意的一点:对于中序遍历的首个结点,其ltag=1,但leftchild=NULL,标识其为首个结点,对于中序遍历的末尾结点,其rtag=1, 但rightchild=NULL,标识其为末尾结点。
二叉树的中序线索化
中序线索化,顾名思义为通过中序遍历,将二叉树转化为线索二叉树。
1、递归终止条件:若当前结点为NULL,直接返回;
2、对左孩子递归;
3、处理当前结点,若其左孩子为空,则置ltag=1,并使leftChild=pre(此处pre结点置上一个处理的结点,初始为NULL);若pre不为空,置pre的rtag=1,并使pre的rightChild=cur(cur即指当前结点);
4、对右孩子递归;
//二叉树的中序线索化,pre初始化为NULL
void ThreadedTree(ThreadNode *cur, ThreadNode* pre)
{
if (cur == NULL) {
return ;
}
ThreadedTree(cur->leftChild, pre); // 左
//中
if (cur->leftChild == NULL) {
// 左孩子不为空,则线索化为前驱
cur->leftChild = pre;
cur->ltag = 1;
}
if (pre != NULL && pre->rightChild == NULL) {
//pre的右孩子不为空,则线索化为后继
pre->rightChild = cur;
pre->rtag = 1;
}
pre = cur;
ThreadedTree(cur->rightChild, pre); //右
}
线索二叉树的遍历
前序遍历
1、访问根节点;
2、若有左子树,则访问左子树;
3、若无左子树但有右子树,则访问右子树;
4、左左右子树都没有,则寻找下一个结点:
4.1、此时其右指针指向的是中序遍历中的后继结点,该后继结点一定是其某一代父节点(因为此时该结点所代表的左子树遍历到头了,该回到某个父节点本身了);
4.2、如果4.1中的这个“某一代父节点”有右孩子,则该右孩子为本次前序遍历的下一个结点,若无,则根据线索化的右指针继续寻找中序的后继,直至找到某一代父节点有右孩子,或回到根节点(代表遍历结束)。
//中序线索化二叉树的前序遍历的算法
void PreOrder(ThreadNode* root)
{
while (root != NULL)
{
cout << root->val << endl; //中
// 左
if (root->ltag == 0) {
root = root->leftChild;
} else if (root->rtag == 0) {
// 右
root = root->rightChild;
} else {
// 如果左右孩子均为空,则按照上文4的情况处理,找到有右孩子的中序后继
while (root != NULL && root->rtag == 1) {
root = root->rightChild;
}
if (root != NULL) {
root = root->rightChild; //如果没找到,结束
}
}
}
}
中序遍历
中序遍历需要先访问左子树再访问根节点,因此需要一个寻找最左结点(即中序遍历第一个结点)的操作,同时由于线索二叉树本身就是中序线索化的,因此寻找下一个结点时,若右子树不为空,则寻找右子树的最左结点,右子树为空则按照线索寻找即可。
1、寻找最左结点函数
//寻找最左结点,也即中序遍历首结点
ThreadNode* leftest(ThreadNode* root)
{
while (root->ltag == 0) {
root = root->leftChild;
}
return root;
}
2、寻找下一个结点
//寻找中序遍历的下一个结点
ThreadNode* getNext(ThreadNode* root)
{
if (root->rtag == 0) {
// 若结点右子树不为空,则寻找其右子树的最左结点
return leftest(root->rightChild);
} else {
// 若右子树为空,则返回右子树的线索
return root->rightChild;
}
}
3、中序遍历
//中序线索化二叉树的中序遍历的算法
void InOrder(ThreadNode* root)
{
for (root = leftest(root); root != NULL; root = getNext(root))
{
cout << root->data << " ";
}
}
后序遍历
=.= 还没学会,还请路过的大佬指点
线索二叉树寻找父节点
寻找父节点有两种情况:
1、若为左图的情况,则以cur结点为根的子树的后继节点是parent,即需要找到以cur为根的子树的最右下的结点,并访问其后继线索为parent;
2、若为右图的情况,则以cur结点为根的子树的前驱结点是parent,即需要找到以cur为根的子树的最坐下的结点,并访问其前驱线索为parent。
ThreadNode* findParent(ThreadNode* cur, ThreadNode* root)
{
// 若结点为根节点,则返回NULL
if (cur == root) {
return NULL;
}
ThreadNode* p;
// 判断是否为左图的情况,先找到cur的最右下结点
for (p = cur; p->rtag == 0; p = p->rightChild);
// 若右下结点的后继不为空,则判断该后继是否为父节点,若不是,则父节点在其左子树中
if (p->rightChild != NULL)
for (p = p->rightChild; p != NULL && p->leftChild != cur && p->rightChild != cur; p = p->leftChild)
// 判断是否为右图的情况,先找到cur的最左下结点
for (p = cur; p->ltag == 0; p = p->leftChild);
// 若左下角结点的前驱不为空,则判断该结点是否为父节点,若不是,则父节点在其右子树中
if (p->leftChild != NULL) {
for (p = p->leftChild; p != NULL && p->leftChild != cur && p->rightChild != cur; p = p->rightChild);
}
return p;
}