数据结构之线索二叉树

整体介绍

线索二叉树是链表表示的树,它是利用了二叉树未被使用的 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);
  }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 基本概念 遍历二叉树是对非线性结构结点的线性化过程,由此得到的遍历序列中,每个结点有且仅有一个前驱和后继(除了序列...
    W1NFRED阅读 3,335评论 0 0
  • 1.顺序存储二叉树的概念 基本说明从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也...
    smallmartial阅读 8,953评论 0 1
  • 一、 概念 二叉树按照先序、中序、后续遍历的方法形成一个线性序列后,每个结点(除第一个和最后一个外),都有且仅有一...
    Qi0907阅读 5,480评论 0 1
  • 定义 以上述结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针,叫做线索。加上...
    小明同学机器人阅读 728评论 0 2
  • 线索二叉树 二叉树转换线索二叉树 步骤: 1)首先写出前序,中序,后序的排列 中序线索二叉树当中,某些结点右指...
    雷猴码阅读 2,607评论 0 0