数据结构—线索二叉树
1.二叉链表中空间资源的浪费
我们利用节点建立了二叉链表,但是我们发现二叉链表中存在这许多空指针,那么这部分空间就被浪费了,我们应该想办法解决这个问题
假设有一个节点个数为n的二叉链表,那么其中就有2n个指针域,n个节点就会有n-1个分支线树,也就是说就会有2n-(n-1)=n+1个空指针域。如上图,十个节点的二叉树就会有十一个空指针域。
我们在上一篇博客中也说过,有三种遍历二叉树的方法(前序,中序,后序),不管是哪一种遍历方法,在遍历时都会产生特定的前后顺序。但是,实际在树的结构中,我们只知道一个节点的两个孩子节点的指针,但是不知道遍历时真正的前驱和后继,所以我们干脆在第一次遍历时就利用这些空指针域,让其指向该节点的前驱或者后继。
- 我们把这种指向前驱和后继的指针称为线索,加上线索的二叉 链表称为线索链表,相应的二叉树称为线索二叉树
2.线索化
对于线索二叉树,我们有这样的规定:
- 如果一个节点的左孩子节点为空,则让该指针指向其前驱节点
- 如果一个节点的右孩子节点为空,则让该指针指向其后继节点
例如上面的二叉树,我们可以用中序遍历的方法使其线索化:
中序遍历顺序:H,D,I,B,J,E,A,F,C,G
在这次遍历建立了线索二叉树后,我们再此遍历就不需要使用之前的遍历方法了,因为我们已经将二叉树中叶子节点中的空指针充分利用,其实等于将我们的二叉树变成了一个双向链表,这样我们的插入删除节点,查找节点都更方便。
但现在有一个问题,我如何直到一个节点的左孩子指针是指向它的左孩子还是它的前驱呢?同理,我也没法直到这个节点的右孩子指针是指向其右孩子节点还是其后继。
wile解决这个问题,我们设立如下的数据结构:
typedef enum {Link,Thread} PointerTag;//孩子指针域状态(指向孩子节点或前后驱)
typedef struct BiThrNode
{
int data;
BiThrNode *lchild,*rchild;
PointerTag LTag; //左孩子指针状态
PointerTag RTag; //右孩子指针状态
} BiThrNode,*BiThrTree;
我们给每个节点新增两个枚举类型的状态标记,若该标记为Link,则表名:该指针指向的是该节点的孩子节点。若该标记为Thread,则表示该指针指向的是这个节点的前驱或后继。
接下来就是线索化的过程,下面是中序线索化的核心代码:
BiThrTree pre;//前驱节点(全局变量)
void InThreading(BiThrTree T)
{
if(T)
{
InThreading(T->lchild);
if(!T->lchild)
{
T->LTag = Thread;
T->lchild = pre;
}