數(shù)據(jù)結(jié)構(gòu)之線索二叉樹

整體介紹

線索二叉樹是鏈表表示的樹,它是利用了二叉樹未被使用的 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);
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容