数据结构之线索二叉树

整体介绍

线索二叉树是链表表示的树,它是利用了二叉树未被使用的 n + 1个闲置的指针构成的树;
根据二叉树的三种遍历方式构成了三种不同的线索二叉树;
二叉树的遍历只能从根结点开始依次遍历,而构建了线索二叉树后,就可以从二叉树中任何一个结点进行遍历,这就是线索化最大的意义了;
实际上线索二叉树的应用面是很窄的,但是学习它最重要的意义还是理解它的这种思想;
就是将闲置的空间充分利用起来,这应该是最重要的意义了;

线索二叉树的存储结构

与二叉树结构唯一的不同就是多了是否线索化的标识

typedef struct ThrBiTreeNode {
  ElemType data;
  struct ThrBiTreeNode *lchild, *rchild;
  //这里是和普通的二叉树的区别,用来标识该结点的左右指针是否被线索化
  //1表示线索化了,0表示未线索化
   int ltag, rtag;
}ThrBiTreeNode, *ThrBiTree;

前(先)序线索二叉树

1、创建
这里是使用递归的方式创建的,非递归方式可以通过参考二叉树的非递归现实

void createPreTree(ThrBiTree root) {
  ThrBiTree pre = NULL;
  if (NULL == root) {
    return;
  }
  //通过递归的方式进行前序线索化
  preThread(root, pre);
  //如果递归完成最后一个结点的后继结点因为空
  if (NULL == pre -> rchild) {
    pre -> rtag = 1;
  }
}
void preThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
  //递归出口
  if (NULL == cur) {
    return;
  }
  //如果当前结点没有左孩子结点就将该指针线索化
  if (NULL == cur -> lchild) {
    cur -> lchild = pre;
    cur -> ltag = 1;
  }
  //如果当前结点没有右孩子结点就线索化
  if (NULL != pre && NULL != pre -> rchild) {
    pre -> rchild = cur;
    pre -> rtag = 1;
  }
  //记录当前结点的前驱结点,方便线索化处理
  pre = p;
  //由于当前结点的左孩子已被线索化,故这里需要判断,只线索化未被线索化的结点
  if (p -> ltag = 0) {
    preThread(p -> lchild, pre);
  }
  //右孩子的线索化
  preThread(p -> rchild, pre);
}

2、遍历
由于前序遍历(根左右)和二叉树本身的特点,所以从一棵树的根结点无法找到其前驱,只能找到后继,因此无法通过前驱完成逆向遍历,只能通过后继顺序遍历树;

 //寻找后继结点
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;
  }
  //当前结点最先被遍历,然后查找后继结点依次遍历,为空就结束
  for (ThrBiTreeNode *cur = p;  cur != NULL; cur = findNextNode(cur)) {
    visit(cur);
  }
}

中序线索二叉树

1、创建

void createInTree(ThrBiTree root) {
  ThrBiTreeNode *pre = NULL;
  if (NULL == root) {
    return;
  }
  //通过递归的方式进行中序线索化
  inThread(root, pre);
  //如果递归完成最后一个结点的后继结点因为空
  if (NULL == pre -> rchild) {
    pre -> rtag = 1;
  }
}
void inThread (ThrBiTree &cur, ThrBiTreeNode &pre) {
  //递归出口
  if (NULL == cur) {
    return;
  }
  //左孩子的线索化
  inThread (cur -> lchild, pre);
  //如果当前结点没有左孩子结点就将该指针线索化
  if (NULL == cur -> lchild) {
    cur -> lchild = pre;
    cur -> ltag = 1;
  }
  //如果当前结点没有右孩子结点就线索化
  if (NULL != pre && NULL != pre -> rchild) {
    pre -> rchild = cur;
    pre -> rtag = 1;
  }
  //记录当前结点的前驱结点,方便线索化处理
  pre = p;
  //右孩子的线索化
  inThread(p -> rchild, pre);
}

2、遍历
中序遍历即可以找到前驱结点,也可以找到后继结点,因此可以正向和反向遍历

a、正向遍历

//找到当前结点中序遍历的第一个被遍历的结点
ThrBiTreeNode * findFirstNode(ThrBiTreeNode *cur) {
  //中序遍历(左根右),因此要找到子树中最后一个左孩子结点,即为第一个要遍历的结点
  while(p -> ltag == 0) {
    p = p -> lchild;
  }
  return p;
}
//找到当前结点的后继结点
ThrBiTreeNode * findNextNode(ThrBiTreeNode *cur) {
  //如果当前结点的右指针未被线索化,就调第一个方法寻找
  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、逆向遍历

//找到当前结点中序遍历的最后一个被遍历的结点
ThrBiTreeNode * findLastNode(ThrBiTreeNode *cur) {
  //中序遍历(左根右),因此要找到子树中最后一个右孩子结点结点,即为最后要遍历的结点
  while(p -> rtag == 0) {
    p = p -> rchild;
  }
  return p;
}
//找到当前结点的前继结点
ThrBiTreeNode * findPreNode(ThrBiTreeNode *cur) {
  //如果当前结点的左指针未被线索化,就调第一个方法寻找
  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、创建

void createPostTree(ThrBiTree root) {
  ThrBiTreeNode *pre = NULL;
  if (NULL == root) {
    return;
  }
  //通过递归的方式进行后序线索化
  postThread(root, pre);
  //如果递归完成最后一个结点的后继结点因为空
  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);
  //如果当前结点没有左孩子结点就将该指针线索化
  if (NULL == cur -> lchild) {
    cur -> lchild = pre;
    cur -> ltag = 1;
  }
  //如果当前结点没有右孩子结点就线索化
  if (NULL != pre && NULL != pre -> rchild) {
    pre -> rchild = cur;
    pre -> rtag = 1;
  }
  //记录当前结点的前驱结点,方便线索化处理
  pre = p;
}

2、遍历
由于后序遍历(左右根)和二叉树本身的特点,所以从一棵树的根结点无法找到其后继,只能找到前驱,因此只能通过前驱结点逆序遍历树;

 //寻找前驱结点,要么为左孩子要么为右孩子
ThrBiTreeNode *findPreNode(ThrBiTreeNode *p) {
  //如果左孩子没有被线索化,左孩子被线索化后就是前驱了
  //这个条件就是如果当前结点无法直接找到前驱应该怎么处理
  if (0 == p -> ltag) {
    //如果有右孩子,则右孩子为前驱
    if (0 == p -> rtag && NULL != p -> rchild) {
      return p -> rchild;
    }
     //如果没有右孩子却有左孩子则前驱为左孩子
     if (NULL != p -> lchild) {
      return p -> lchild;
    }
  } else {//被线索化了,直接返回
    return p -> lchild;
  }
 }
//后续逆向遍历
void postOrderReverse(ThrBiTreeNode *p) {
  if (NULL == p) {
    return;
  }
  //当前结点最先被遍历,然后查找前驱结点依次遍历,为空就结束
  for (ThrBiTreeNode *cur = p;  cur != NULL; cur = findNextNode(cur)) {
    visit(cur);
  }
}

你可能感兴趣的:(数据结构之线索二叉树)