图1
如图可以得到该二叉树的
先序:1 2 4 5 7 8 10 3 6 9
中序:4 2 7 5 10 8 1 3 9 6
后序:4 7 10 8 5 2 9 6 3 1
给出该线索二叉树的结构体:
typedefint ElemType;
// 线索二叉树结构体
typedefstruct Clue_binary_tree_node {
ElemType data; // 数据域
struct Clue_binary_tree_node *l_child,*r_child; // 左孩子、右孩子
int l_tag = 0, r_tag = 0; // 标记
}Clue_binary_tree_node, *Clue_binary_tree;
先来分析先序的情况:
图2
(1)最简单的一种情况就是,它是叶子结点(如4、7、10),即无左右孩子,则,它的l_tag及r_tag全为1(默认为0是代表它有左右孩子),此时求前驱和后继只需要返回它的左右孩子即可,即:
if (1== bt -> l_tag) {
return bt -> l_child;
} (后继同理)
(2)该结点有一个孩子(如8、3、6)
在考虑线索的情况下,先判断它的孩子是左孩子还是右孩子,如果是左孩子,那么它的后继,可以直接根据r_child求出,如果是右孩子,那么它的前驱,可以直接根据l_child求出。剩下另一半(前驱或后继)可以根据不考虑线索的情况下的求法求出。
在不考虑线索的情况下,先说后继,因为此时是先序(先根后左再右),则无论该结点是有左孩子还是右孩子,它的后继都是它的孩子。再说前驱,同样因为是先序(先根后左再右),如果它位于左孩子的位置,则它的顺序就应该在它的父亲结点之后,即它的前驱就是它的父亲结点;如果它位于右孩子的位置,则它的顺序就应该在它的左兄弟结点之后(若存在左兄弟,否则就是它的父亲结点),因为它的左兄弟结点可能有多个子孙,同样满足这个情况,故,该结点应该位于左兄弟的子孙中(如果没有子孙就是左兄弟)最下且最靠右的结点的后面,例如:
图3
如图中结点3的前驱就是它兄弟结点2的子孙中最下面且最靠右的结点10,同理结点6的前驱是结点3,因为结点6没有左兄弟,故它的前驱就是它的父亲结点。结点8的前驱是结点7,因为它的左兄弟结点7没有子孙,故它的前驱就是它的左兄弟。
满足如下图的样式
图4
图中蓝色的线最多连到哪,它的前驱就到哪(最后可能是左孩子也可能是右孩子)。
(3)该结点有2个孩子(如结点2、5),发现,它的前驱,同样满足上述规则,当它为左孩子时,它的前驱就是它的父亲结点,当它是右孩子时,它的前驱就是它的左兄弟子孙中最靠下靠右的结点。它的后继,更简单,就是它的左孩子。
总结:当线索二叉树是先序线索二叉树时,要你求某一个结点的前驱时,分两种大情况:
1. 它的(l_tag == 1),直接返回 l_child 即可
2. 它的(l_tag != 1),沿着图4的轨迹找即可
要你求某一个结点的后继时,分两大种情况:
1. 它的(r_tag == 1),直接返回 r_child 即可
2. 它的(r_tag != 1),有左孩子则是左孩子,没有左孩子则是右孩子。
bool find_node_parent(Clue_binary_tree target, Clue_binary_tree root, Clue_binary_tree &temp);
Clue_binary_tree get_pre_node(Clue_binary_tree bt, Clue_binary_tree root);
Clue_binary_tree get_suc_node(Clue_binary_tree bt);
// 求前驱
Clue_binary_tree get_pre_node(Clue_binary_tree bt, Clue_binary_tree root) {
// 如果该结点为空,则返回空
if (NULL == bt) {
return NULL;
}
// 如果该节点的左孩子存储的就是它的前驱,则返回左孩子
if (1 == bt -> l_tag) {
return bt -> l_child;
}
Clue_binary_tree temp = NULL;
// 如果找不到该节点的父亲节点,则该节点为根,根在先序遍历中是不可能有前驱
if (!find_node_parent(bt, root, temp)) {
return NULL;
}
// 此时temp即为bt的父亲结点,如果它的左孩子为空,则它就是该结点的前驱
if (!temp -> l_child) {
return temp;
} else {
temp = temp -> l_child
while(temp -> r_child) {
temp = temp -> r_child;
}
if (temp -> l_child) return temp -> l_child;
else return temp;
}
}
// 求后继
Clue_binary_tree get_suc_node(Clue_binary_tree bt) {
if (NULL == bt) {
return NULL;
}
if (1 == bt -> r_tag) {
return bt -> r_child;
}
if (bt -> l_child) {
return bt -> l_child;
} else {
return bt -> r_child;
}
}
bool find_node_parent(Clue_binary_tree target, Clue_binary_tree root, Clue_binary_tree &temp) {
if ((root -> l_child == target) || (root -> r_child == target)) {
temp = root;
return true;
} else {
if (find_node_parent(target, root -> l_child, temp)) return true;
if (find_node_parent(target, root -> r_child, temp)) return true;
}
}
再来分析中序的情况 :
图5
如图5是中序线索二叉树
如果不用程序来求中序,简便方法:
正好就是它的中序序列。
下面还是分3种情况来分析:
(1) 叶子结点:因为它的左右孩子就是存储的它中序前驱后继的信息,故直接返回左右孩子即可得到该结点的前驱后继。
(2) 只有1个孩子的结点:先来分析如果它只有右孩子,因为中序(先左后根再右),若该结点只有右孩子,那么它的前驱一定是它的父亲结点(信息就存储在它的左孩子),但是它的后继,则不单纯是它的右孩子,应该是它右子孙中最靠左的那个结点;如果它只有左孩子,那么它的前驱则是它左子孙中最靠右的那个结点,它的后继的信息则存储在它的右孩子中。
(3) 有2个孩子的结点:它的前驱是它的左子孙中最靠右的那个结点,它的后继是它的右子孙中最靠左的那个结点。
总结:当线索二叉树是中序线索二叉树时,要你求某一个结点的前驱时,分两种大情况:
1. 它的(1 == l_tag)时,直接return l_child。
2. 它的 (0 == l_tag)时,找它的左子孙中最靠右的那个结点。
要你求某一个结点的后继时,分两大种情况:
3. 它的(1 == r_tag)时,直接return r_child
4. 它的(0 == r_tag)时,找它的右子孙中最靠左的那个结点。
具体代码实现如下:
// 求前驱
Clue_binary_tree get_pre_node(Clue_binary_tree bt) {
if (NULL == bt) {
return NULL;
}
if (1 == bt -> l_tag) {
return bt -> l_child;
}
Clue_binary_tree temp = bt -> l_child;
while (temp -> r_child) {
temp = temp -> r_child;
}
return temp;
}
// 求后继
Clue_binary_tree get_suc_node(Clue_binary_tree bt) {
if (NULL == bt) {
return NULL;
}
if (1 == bt -> r_tag) {
return bt -> r_child;
}
Clue_binary_tree temp = bt -> r_child;
while (temp -> l_child) {
temp = temp -> l_child;
}
return temp;
}