线索化二叉树
在一些项目中需要频繁的遍历二叉树,但是二叉树的遍历比单链表的遍历复杂多了,并且递归总是会有额外开销。。。
能不能像链表那样方便的快速遍历二叉树呢?
线索化二叉树指的是将二叉树中的结点进行逻辑意义上的“重排列”,使其可以线性的方式访问每一个结点。
二叉树线索化之后每个结点都有一个线性下标,通过这个下标可以快速访问结点,而不需要遍历二叉树。这个感觉很像组织链表,但是大家要知道组织链表结点之间的先后关系是没有任何逻辑关系的,只是为了插入、删除等操作方便。
那么我们应该怎样线索化二叉树呢?方法有二:
线索化方法1:
利用结点中的空指针域,使其指向后继结点。这也是教科书式的方法,许多教科书都会这么讲。
算法思想:
1.初始化位置指针p = NULL;
2.前序遍历二叉树:
2.1.若p不为空,将p->left指向当前结点,并将p置为NULL;
2.2.若当前结点的左子树为空时,将p指向当前结点。
由以上算法我们可知,线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只有在遍历才
能得到,因此线索化的过程即为在遍历的过程中修改空指针的过程。
实现代码如下:
// 通过空指针线索化二叉树
void thread_via_left(BTreeNode* root, BTreeNode** pp)
{
// 入口参数合法性检查OK
if( (root != NULL) && (pp != NULL) )
{
// 若pp不为空,将pp->left指向当前结点,并将pp置为NULL
if( *pp != NULL )
{
(*pp)->left = root;
*pp = NULL;
}
// 若当前结点的左子树为空时,将pp指向当前结点
if( root->left == NULL )
{
*pp = root;
}
// 递归线索化(遍历)左右子树
thread_via_left(root->left, pp);
thread_via_left(root->right, pp);
}
}
既然是前序遍历,首先就得有前序遍历的框架,即1.访问根结点中的数据;2.前序遍历左子树;3.前序遍历右子树。
线索化方法2:
利用线性表保存二叉树的遍历顺序。此方法异常简单。
算法思想:(感谢国嵌嵌入式工作者们)
1.初始化顺序表s1;
2.前序遍历二叉树,遍历过程中将当前结点插入顺序表s1。
实现代码如下:
// 通过顺序表线索化二叉树
void thread_via_list(BTreeNode* root, SeqList* list)
{
// 入口参数合法性检查OK
if( (root != NULL) && (list != NULL) )
{
// 在顺序表尾部插入顺序表,访问根结点数据
SeqList_Insert(list, (SeqListNode*)root, SeqList_Length(list));
// 线索化(前序遍历)左子树
thread_via_list(root->left, list);
// 线索化(前序遍历)右子树
thread_via_list(root->right, list);
}
}
测试验证代码如下:
int main(int argc, char *argv[])
{
// 创建二叉树
BTree* tree = BTree_Create();
// 定义二叉树结点指针
BTreeNode* current = NULL;
BTreeNode* p = NULL;
// 定义顺序表变量
SeqList* list = NULL;
// 定义循环变量
int i = 0;
// 定义二叉树内容
struct Node n1 = {{NULL, NULL}, 'A'};
struct Node n2 = {{NULL, NULL}, 'B'};
struct Node n3 = {{NULL, NULL}, 'C'};
struct Node n4 = {{NULL, NULL}, 'D'};
struct Node n5 = {{NULL, NULL}, 'E'};
struct Node n6 = {{NULL, NULL}, 'F'};
// 插入二叉树
BTree_Insert(tree, (BTreeNode*)&n1, 0, 0, 0);
BTree_Insert(tree, (BTreeNode*)&n2, 0x00, 1, 0);
BTree_Insert(tree, (BTreeNode*)&n3, 0x01, 1, 0);
BTree_Insert(tree, (BTreeNode*)&n4, 0x00, 2, 0);
BTree_Insert(tree, (BTreeNode*)&n5, 0x02, 2, 0);
BTree_Insert(tree, (BTreeNode*)&n6, 0x02, 3, 0);
// 显示整体二叉树
printf("Full Tree: \n");
BTree_Display(tree, printf_data, 4, '-');
printf("Thread via List:\n");
// 验证顺序表线索化二叉树
list = SeqList_Create(BTree_Count(tree));
thread_via_list(BTree_Root(tree), list);
for(i=0; i<SeqList_Length(list); i++)
{
printf("%c, ", ((struct Node*)SeqList_Get(list, i))->v);
}
printf("\n");
// 验证结点空指针线索化二叉树
printf("Thread via Left:\n");
current = BTree_Root(tree);
thread_via_left(current, &p);
while( current != NULL )
{
printf("%c, ", ((struct Node*)current)->v);
current = current->left;
}
printf("\n");
// 销毁二叉树
BTree_Destroy(tree);
return 0;
}
通过上面代码比较发现,利用结点空指针线索化的方法会破坏树的结构,同时,利用结点空指针线索化二叉树之后不能够再恢
复。当然上面的问题可以通过在树结点中加入一个线索化指针而得以解决,然而线索化指针的加入又会浪费内存空间,不够灵
活。链表线索化方法不会破化树的结构,不需要线索化时销毁链表即可;链表线索化方法可以很容易的以任何一种遍历顺序对二
叉树进行线索化 。
有的人会问第一种线索化二叉树是否可以实现其他遍历方法来实现,当然可以,不过由于会破坏二叉树结构,并且不能在复位且
会浪费内存空间,所以不再讲述。
线索化二叉树整体实现代码:线索化二叉树代码C实现