前一篇簡單的對二叉樹進行初探,簡單的瞭解了一下二叉樹的一些概念,和二叉樹的 順序存儲 和 鏈式存儲 以及二叉樹的一些簡單操做,和二叉樹的幾種遍歷方式。這一篇,咱們在對二叉樹進行了解,假如這個二叉樹
有不少的葉子節點
,那麼葉子節點
的左孩子
和右孩子
的指針空間是否會浪費呢?數組
如開篇提到的,假如,一個二叉樹有不少的葉子節點,以下圖 bash
那麼,上圖中,標記 '^'的左孩子
指針域和
右孩子
指針域中則爲
NULL
,那麼對於這些空間來講就是
空間浪費
。
咱們稱這種那麼,當
節點
的左孩子
的指針域爲空的時候,能夠將這個指向遍歷(以中序遍歷爲例)時的前一個節點,稱之爲前驅
;函數當
節點
的右孩子
的指針域爲空的時候,能夠將這個指向遍歷(以中序遍歷爲例)時的後一個節點,稱之爲後繼
,以下圖(以中序遍歷爲例):優化
二叉樹
爲
線索化二叉樹
,
優勢:ui
那麼怎麼區分節點
的左孩子
指針指向的是左子樹
仍是前驅
呢?spa
那麼咱們能夠對二叉樹節點
的結構進行優化,增長兩個標識
,來指示具體指向的是左右子樹
仍是前驅後繼
,以下示意圖:3d
// Link==0表示指向左右孩子指針
// Thread==1表示指向前驅或後繼的線索
typedef enum {Link, Thread} PointerTag;
// 線索二叉樹節點
typedef struct BiThrNode{
//數據
CElemType data;
//左右孩子指針
struct BiThrNode *lchild,*rchild;
//左右標記
PointerTag LTag;
PointerTag RTag;
}BiThrNode,*BiThrTree;
複製代碼
下面以中序遍歷爲例,線索二叉樹的線索化和遍歷指針
線索二叉樹的線索化code
首先,聲明定義一些變量,並定義一個方法,cdn
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSW 0
#define MAXSIZE 100 // 存儲空間初始分配量
typedef int Status; // 函數類型
typedef char CElemType;
CElemType Nill = '#'; // 字符型以空格標識空
#pragma mark -二叉樹的構造
int indexs = 1;
typedef char String[24]; /* 0號單元存放串的長度 */
String str;
// 字符傳構造字符數組
Status StrAssign(String T, char *chars) {
int i;
if (strlen(chars) > MAXSIZE) {
return ERROR;
}else {
T[0] = strlen(chars);
for (i = 1; i <= T[0]; i++) {
T[i] = *(chars+i-1);
}
return OK;
}
}
// 打印值
Status visit(CElemType e) {
printf("%c ", e);
return OK;
}
複製代碼
按照中序遍歷的方式,建立線索化二叉樹
,先建立,再線索化
// 中序二叉樹線索化
// 定義全局變量,始終指向上一個訪問的節點
BiThrTree pre;
void InThreading(BiThrTree p) {
if (p) {
// ✅ 遞歸左子樹線索化
InThreading(p->lchild);
if (!p->lchild) { // ✅ 沒有左孩子,其前驅節點剛放訪問過,賦值給 pre
// 修改左標識
p->LTag = Thread;
// 左孩子的指針指向前驅
p->lchild = pre;
} else { // 有左孩子,修改左標識
// 修改左標識
p->LTag = Link;
}
// ✅ 判斷前驅的右孩子
if (!pre->rchild) { // 沒有右孩子,p 就是 pre 的後繼
// 修改右標識
pre->RTag = Thread;
// 前驅右孩子指針指向後繼(當前節點p)
pre->rchild = p;
} else {
// 修改右標識
pre->RTag = Link;
}
// ✅ 修改pre 指向 p的前驅
pre = p;
// ✅ 遞歸右子樹線索化
InThreading(p->rchild);
}
}
複製代碼
在對二叉樹進行線索化的時候,咱們能夠增長一個頭節點
* 左孩子指向根節點
* 右孩子指向中序遍歷的最後一個節點
* 中序遍歷的第一個節點的左孩子指針(原來爲null)和最後一個節點的右孩子指針(原來爲null)都指向頭結點
複製代碼
好處:能夠從第一個節點起,順着前驅遍歷,也能夠順着後繼遍歷,不用開始就遞歸找左孩子,以下圖:
代碼:
Status InOrderThreading(BiThrTree *Thrt , BiThrTree T){
*Thrt = (BiThrTree)malloc(sizeof(BiThrNode));
if (!*Thrt) {
exit(OVERFLOW);
}
// 創建頭結點
(*Thrt)->LTag = Link;
(*Thrt)->RTag = Thread;
// 右指針回指向,暫時指向本身(還不知道最後一個節點)
(*Thrt)->rchild = (*Thrt);
if (!T) { // 首節點爲空,左孩子指針指向頭結點
(*Thrt)->lchild = *Thrt;
} else {
// ✅ 圖中1
(*Thrt)->lchild = T;
// 修改pre,指向上一個操做的節點
pre = (*Thrt);
//中序遍歷進行中序線索化
InThreading(T);
//✅ 圖中4,最後一個結點rchil 孩子指向頭結點
pre->rchild = *Thrt;
//✅ 最後一個結點線索化
pre->RTag = Thread;
//✅ 圖中2
(*Thrt)->rchild = pre;
}
return OK;
}
複製代碼
// 中序遍歷
Status InOrderTraverse_Thr(BiThrTree T){
BiThrTree p = T->lchild;
// p = T時,說明根節點(首節點)爲空或者遍歷結束
while (p != T) {
// 遍歷,找到開始的葉子節點,即找到左子樹爲空的節點
while (p->LTag == Link) {
p = p->lchild;
}
// 訪問
if(!visit(p->data)) /* 訪問其左子樹爲空的結點 */
return ERROR;
while (p->RTag == Thread && p->rchild!=T) {
p=p->rchild;
visit(p->data); /* 訪問後繼結點 */
}
p = p->rchild;
}
return OK;
}
// 調用
BiThrTree H,T;
//StrAssign(str,"ABDH#K###E##CFI###G#J##");
StrAssign(str,"ABDH##I##EJ###CF##G##");
CreatBiThrTree(&T); /* 按前序產生二叉樹 */
InOrderThreading(&H,T); /* 中序遍歷,並中序線索化二叉樹 */
InOrderTraverse_Thr(H);
複製代碼