整體介紹
線索二叉樹是鏈表表示的樹,它是利用了二叉樹未被使用的 n + 1個閑置的指針構(gòu)成的樹;
根據(jù)二叉樹的三種遍歷方式構(gòu)成了三種不同的線索二叉樹;
二叉樹的遍歷只能從根結(jié)點開始依次遍歷,而構(gòu)建了線索二叉樹后,就可以從二叉樹中任何一個結(jié)點進行遍歷,這就是線索化最大的意義了;
實際上線索二叉樹的應用面是很窄的,但是學習它最重要的意義還是理解它的這種思想;
就是將閑置的空間充分利用起來,這應該是最重要的意義了;
線索二叉樹的存儲結(jié)構(gòu)
與二叉樹結(jié)構(gòu)唯一的不同就是多了是否線索化的標識
typedef struct ThrBiTreeNode {
ElemType data;
struct ThrBiTreeNode *lchild, *rchild;
//這里是和普通的二叉樹的區(qū)別,用來標識該結(jié)點的左右指針是否被線索化
//1表示線索化了,0表示未線索化
int ltag, rtag;
}ThrBiTreeNode, *ThrBiTree;
前(先)序線索二叉樹
1、創(chuàng)建
這里是使用遞歸的方式創(chuàng)建的,非遞歸方式可以通過參考二叉樹的非遞歸現(xiàn)實
void createPreTree(ThrBiTree root) {
ThrBiTree pre = NULL;
if (NULL == root) {
return;
}
//通過遞歸的方式進行前序線索化
preThread(root, pre);
//如果遞歸完成最后一個結(jié)點的后繼結(jié)點因為空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void preThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//遞歸出口
if (NULL == cur) {
return;
}
//如果當前結(jié)點沒有左孩子結(jié)點就將該指針線索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果當前結(jié)點沒有右孩子結(jié)點就線索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//記錄當前結(jié)點的前驅(qū)結(jié)點,方便線索化處理
pre = p;
//由于當前結(jié)點的左孩子已被線索化,故這里需要判斷,只線索化未被線索化的結(jié)點
if (p -> ltag = 0) {
preThread(p -> lchild, pre);
}
//右孩子的線索化
preThread(p -> rchild, pre);
}
2、遍歷
由于前序遍歷(根左右)和二叉樹本身的特點,所以從一棵樹的根結(jié)點無法找到其前驅(qū),只能找到后繼,因此無法通過前驅(qū)完成逆向遍歷,只能通過后繼順序遍歷樹;
//尋找后繼結(jié)點
ThrBiTreeNode *findNextNode(ThrBiTreeNode *p) {
//如果右孩子沒有被線索化
if (0 == p -> rtag) {
//存在左孩子則,后繼為左孩子
if (0 == p -> ltag && NULL != p -> lchild) {
return p -> lchild;
}
//左孩子不存在,則后繼為右孩子
if (NULL != p -> rchild) {
return p -> rchild;
}
} else {//被線索化了,直接返回
return p -> rchild;
}
}
//前序遍歷
void preOrder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
//當前結(jié)點最先被遍歷,然后查找后繼結(jié)點依次遍歷,為空就結(jié)束
for (ThrBiTreeNode *cur = p; cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}
中序線索二叉樹
1、創(chuàng)建
void createInTree(ThrBiTree root) {
ThrBiTreeNode *pre = NULL;
if (NULL == root) {
return;
}
//通過遞歸的方式進行中序線索化
inThread(root, pre);
//如果遞歸完成最后一個結(jié)點的后繼結(jié)點因為空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void inThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//遞歸出口
if (NULL == cur) {
return;
}
//左孩子的線索化
inThread (cur -> lchild, pre);
//如果當前結(jié)點沒有左孩子結(jié)點就將該指針線索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果當前結(jié)點沒有右孩子結(jié)點就線索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//記錄當前結(jié)點的前驅(qū)結(jié)點,方便線索化處理
pre = p;
//右孩子的線索化
inThread(p -> rchild, pre);
}
2、遍歷
中序遍歷即可以找到前驅(qū)結(jié)點,也可以找到后繼結(jié)點,因此可以正向和反向遍歷
a、正向遍歷
//找到當前結(jié)點中序遍歷的第一個被遍歷的結(jié)點
ThrBiTreeNode * findFirstNode(ThrBiTreeNode *cur) {
//中序遍歷(左根右),因此要找到子樹中最后一個左孩子結(jié)點,即為第一個要遍歷的結(jié)點
while(p -> ltag == 0) {
p = p -> lchild;
}
return p;
}
//找到當前結(jié)點的后繼結(jié)點
ThrBiTreeNode * findNextNode(ThrBiTreeNode *cur) {
//如果當前結(jié)點的右指針未被線索化,就調(diào)第一個方法尋找
if (cur -> rtag == 0) {
return findFirstNode(cur -> rchild);
} else {//被線索化就直接返回
return cur -> rchild;
}
}
void inorder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
for (ThrBiTreeNode * cur = findFirstNode(p); cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}
b、逆向遍歷
//找到當前結(jié)點中序遍歷的最后一個被遍歷的結(jié)點
ThrBiTreeNode * findLastNode(ThrBiTreeNode *cur) {
//中序遍歷(左根右),因此要找到子樹中最后一個右孩子結(jié)點結(jié)點,即為最后要遍歷的結(jié)點
while(p -> rtag == 0) {
p = p -> rchild;
}
return p;
}
//找到當前結(jié)點的前繼結(jié)點
ThrBiTreeNode * findPreNode(ThrBiTreeNode *cur) {
//如果當前結(jié)點的左指針未被線索化,就調(diào)第一個方法尋找
if (cur -> ltag == 0) {
return findFirstNode(cur -> lchild);
} else {//被線索化就直接返回
return cur -> lchild;
}
}
void inorder(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
for (ThrBiTreeNode * cur = findLastNode(p); cur != NULL; cur = findPreNode(cur)) {
visit(cur);
}
}
后序線索二叉樹
1、創(chuàng)建
void createPostTree(ThrBiTree root) {
ThrBiTreeNode *pre = NULL;
if (NULL == root) {
return;
}
//通過遞歸的方式進行后序線索化
postThread(root, pre);
//如果遞歸完成最后一個結(jié)點的后繼結(jié)點因為空
if (NULL == pre -> rchild) {
pre -> rtag = 1;
}
}
void postThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
//遞歸出口
if (NULL == cur) {
return;
}
//左孩子的線索化
postThread (cur -> lchild, pre);
//右孩子的線索化
postThread(p -> rchild, pre);
//如果當前結(jié)點沒有左孩子結(jié)點就將該指針線索化
if (NULL == cur -> lchild) {
cur -> lchild = pre;
cur -> ltag = 1;
}
//如果當前結(jié)點沒有右孩子結(jié)點就線索化
if (NULL != pre && NULL != pre -> rchild) {
pre -> rchild = cur;
pre -> rtag = 1;
}
//記錄當前結(jié)點的前驅(qū)結(jié)點,方便線索化處理
pre = p;
}
2、遍歷
由于后序遍歷(左右根)和二叉樹本身的特點,所以從一棵樹的根結(jié)點無法找到其后繼,只能找到前驅(qū),因此只能通過前驅(qū)結(jié)點逆序遍歷樹;
//尋找前驅(qū)結(jié)點,要么為左孩子要么為右孩子
ThrBiTreeNode *findPreNode(ThrBiTreeNode *p) {
//如果左孩子沒有被線索化,左孩子被線索化后就是前驅(qū)了
//這個條件就是如果當前結(jié)點無法直接找到前驅(qū)應該怎么處理
if (0 == p -> ltag) {
//如果有右孩子,則右孩子為前驅(qū)
if (0 == p -> rtag && NULL != p -> rchild) {
return p -> rchild;
}
//如果沒有右孩子卻有左孩子則前驅(qū)為左孩子
if (NULL != p -> lchild) {
return p -> lchild;
}
} else {//被線索化了,直接返回
return p -> lchild;
}
}
//后續(xù)逆向遍歷
void postOrderReverse(ThrBiTreeNode *p) {
if (NULL == p) {
return;
}
//當前結(jié)點最先被遍歷,然后查找前驅(qū)結(jié)點依次遍歷,為空就結(jié)束
for (ThrBiTreeNode *cur = p; cur != NULL; cur = findNextNode(cur)) {
visit(cur);
}
}