理解線索二叉樹

原鏈接:理解線索二叉樹|CloudWong

線索二叉樹原理

遍歷二叉樹的其實(shí)就是以一定規(guī)則將二叉樹中的結(jié)點(diǎn)排列成一個(gè)線性序列异袄,得到二叉樹中結(jié)點(diǎn)的先序序列、中序序列或后序序列蝠嘉。這些線性序列中的每一個(gè)元素都有且僅有一個(gè)前驅(qū)結(jié)點(diǎn)后繼結(jié)點(diǎn)瑰妄。

但是當(dāng)我們希望得到二叉樹中某一個(gè)結(jié)點(diǎn)的前驅(qū)或者后繼結(jié)點(diǎn)時(shí)蕉鸳,普通的二叉樹是無法直接得到的送滞,只能通過遍歷一次二叉樹得到侠草。每當(dāng)涉及到求解前驅(qū)或者后繼就需要將二叉樹遍歷一次,非常不方便犁嗅。

于是是否能夠改變?cè)械慕Y(jié)構(gòu)边涕,將結(jié)點(diǎn)的前驅(qū)和后繼的信息存儲(chǔ)進(jìn)來。

二叉樹結(jié)構(gòu)

觀察二叉樹的結(jié)構(gòu)褂微,我們發(fā)現(xiàn)指針域并沒有充分的利用功蜓,有很多“NULL”,也就是存在很多空指針宠蚂。

對(duì)于一個(gè)有n個(gè)結(jié)點(diǎn)的二叉鏈表式撼,每個(gè)節(jié)點(diǎn)都有指向左右孩子的兩個(gè)指針域,一共有2n個(gè)指針域求厕。而n個(gè)結(jié)點(diǎn)的二叉樹又有n-1條分支線數(shù)(除了頭結(jié)點(diǎn)著隆,每一條分支都指向一個(gè)結(jié)點(diǎn)),也就是存在2n-(n-1)=n+1個(gè)空指針域呀癣。這些指針域只是白白的浪費(fèi)空間美浦。因此, 可以用空鏈域來存放結(jié)點(diǎn)的前驅(qū)和后繼。線索二叉樹就是利用n+1個(gè)空鏈域來存放結(jié)點(diǎn)的前驅(qū)和后繼結(jié)點(diǎn)的信息项栏。

線索二叉樹

如圖以中序二叉樹為例浦辨,我們可以把這顆二叉樹中所有空指針域的lchild,改為指向當(dāng)前結(jié)點(diǎn)的前驅(qū)(灰色箭頭)沼沈,把空指針域中的rchild流酬,改為指向結(jié)點(diǎn)的后繼(綠色箭頭)。我們把指向前驅(qū)和后繼的指針叫做線索 列另,加上線索的二叉樹就稱之為線索二叉樹芽腾。

線索二叉樹結(jié)點(diǎn)結(jié)構(gòu)

如果只是在原二叉樹的基礎(chǔ)上利用空結(jié)點(diǎn),那么就存在著這么一個(gè)問題:我們?nèi)绾沃滥骋唤Y(jié)點(diǎn)的lchild是指向他的左孩子還是指向前驅(qū)結(jié)點(diǎn)页衙?rchild是指向右孩子還是后繼結(jié)點(diǎn)摊滔?顯然我們要對(duì)他的指向增設(shè)標(biāo)志來加以區(qū)分。

因此拷姿,我們?cè)诿恳粋€(gè)結(jié)點(diǎn)都增設(shè)兩個(gè)標(biāo)志域LTagRTag,它們只存放0或1的布爾型變量旱函,占用的空間很小响巢。于是結(jié)點(diǎn)的結(jié)構(gòu)如圖所示。

結(jié)點(diǎn)結(jié)構(gòu)

其中:

LTag為0是指向該結(jié)點(diǎn)的左孩子棒妨,為1時(shí)指向該結(jié)點(diǎn)的前驅(qū)

RTag為0是指向該結(jié)點(diǎn)的右孩子踪古,為1時(shí)指向該結(jié)點(diǎn)的后繼

因此實(shí)際的二叉鏈表圖為

實(shí)際的二叉鏈表圖

線索二叉樹的結(jié)構(gòu)實(shí)現(xiàn)

二叉樹的線索存儲(chǔ)結(jié)構(gòu)定義如下:

typedef char TElemType;                     

typedef enum { Link, Thread } PointerTag;       //Link==0,表示指向左右孩子指針
                                                //Thread==1,表示指向前驅(qū)或后繼的線索
//二叉樹線索結(jié)點(diǎn)存儲(chǔ)結(jié)構(gòu)
typedef struct BiThrNode {
  TElemType data;                       //結(jié)點(diǎn)數(shù)據(jù)
  struct BiThrNode *lchild, *rchild;    //左右孩子指針
  PointerTag LTag;                      
  PointerTag RTag;                      //左右標(biāo)志
}BiThrNode, *BiThrTree;

二叉樹線索化

線索化

對(duì)普通二叉樹以某種次序遍歷使其成為線索二叉樹的過程就叫做線索化含长。因?yàn)榍膀?qū)和后繼結(jié)點(diǎn)只有在二叉樹的遍歷過程中才能得到,所以線索化的具體過程就是在二叉樹的遍歷中修改空指針伏穆。

線索化具體實(shí)現(xiàn)

以中序二叉樹的線索化為例拘泞,線索化的具體實(shí)現(xiàn)就是將中序二叉樹的遍歷進(jìn)行修改,把原本打印函數(shù)的代碼改為指針修改的代碼就可以了枕扫。

我們?cè)O(shè)置一個(gè)pre指針陪腌,永遠(yuǎn)指向遍歷當(dāng)前結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn)。若遍歷的當(dāng)前結(jié)點(diǎn)左指針域?yàn)榭昭糖疲簿褪菬o左孩子诗鸭,則把左孩子的指針指向pre(相對(duì)當(dāng)前結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn))。

右孩子同樣的参滴,當(dāng)pre的右孩子為空强岸,則把pre右孩子的指針指向當(dāng)前結(jié)點(diǎn)(相對(duì)pre結(jié)點(diǎn)為后繼結(jié)點(diǎn))。

最后把當(dāng)前結(jié)點(diǎn)賦給pre砾赔,完成后續(xù)的遞歸遍歷線索化蝌箍。

中序遍歷線索化的遞歸函數(shù)代碼如下:

void InThreading(BiThrTree B,BiThrTree *pre) {
  if(!B) return;

  InThreading(B->lchild,pre);   
//--------------------中間為修改空指針代碼---------------------

  if(!B->lchild){                   //沒有左孩子 
    B->LTag = Thread;               //修改標(biāo)志域?yàn)榍膀?qū)線索
    B->lchild = *pre;               //左孩子指向前驅(qū)結(jié)點(diǎn)
  }

  if(!(*pre)->rchild){              //沒有右孩子
    (*pre)->RTag = Thread;          //修改標(biāo)志域?yàn)楹罄^線索
    (*pre)->rchild = B;             //前驅(qū)右孩子指向當(dāng)前結(jié)點(diǎn)
  }

  *pre = B;                         //保持pre指向p的前驅(qū)
//---------------------------------------------------------
  InThreading(B->rchild,pre);
}

增設(shè)頭結(jié)點(diǎn)

線索化后的二叉樹,就如同操作一個(gè)雙向鏈表暴心。于是我們想到為二叉樹增設(shè)一個(gè)頭結(jié)點(diǎn)妓盲,這樣就和雙向鏈表一樣,即能夠從第一個(gè)結(jié)點(diǎn)正向開始遍歷酷勺,也可以從最后一個(gè)結(jié)點(diǎn)逆向遍歷本橙。

增設(shè)頭結(jié)點(diǎn)

如上圖,在線索二叉鏈表上添加一個(gè)head結(jié)點(diǎn)脆诉,并令其lchild域的指針指向二叉樹的根結(jié)點(diǎn)(A)甚亭,其rchild域的指針指向中序遍歷訪問的最后一個(gè)結(jié)點(diǎn)(G)。同樣地击胜,二叉樹中序序列的第一個(gè)結(jié)點(diǎn)中亏狰,lchild域指針指向頭結(jié)點(diǎn),中序序列的最后一個(gè)結(jié)點(diǎn)rchild域指針也指向頭結(jié)點(diǎn)偶摔。

于是從頭結(jié)點(diǎn)開始暇唾,我們既可以從第一個(gè)結(jié)點(diǎn)順后繼結(jié)點(diǎn)遍歷,也可以從最后一個(gè)結(jié)點(diǎn)起順前驅(qū)遍歷辰斋。就和雙鏈表一樣策州。

雙鏈表

增設(shè)頭結(jié)點(diǎn)并線索化的代碼實(shí)現(xiàn)

//為線索二叉樹添加頭結(jié)點(diǎn),使之可以雙向操作
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T){
  if(!(*Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);  //開辟結(jié)點(diǎn)
  (*Thrt)->LTag = Link;         
  (*Thrt)->RTag = Thread;               //設(shè)置標(biāo)志域
  (*Thrt)->rchild = (*Thrt);            //右結(jié)點(diǎn)指向本身
  if(!T) {
    (*Thrt)->lchild = (*Thrt);
    return OK;       //若根結(jié)點(diǎn)不存在,則該二叉樹為空,讓該頭結(jié)點(diǎn)指向自身.
  }
  BiThrTree pre;                //設(shè)置前驅(qū)結(jié)點(diǎn)
  //令頭結(jié)點(diǎn)的左指針指向根結(jié)點(diǎn)
  pre = (*Thrt);
  (*Thrt)->lchild = T;
  //開始遞歸輸入線索化
  InThreading(T,&pre);
  //此時(shí)結(jié)束了最后一個(gè)結(jié)點(diǎn)的線索化了,下面的代碼把頭結(jié)點(diǎn)的后繼指向了最后一個(gè)結(jié)點(diǎn).
  //并把最后一個(gè)結(jié)點(diǎn)的后繼也指向頭結(jié)點(diǎn),此時(shí)樹成為了一個(gè)類似雙向鏈表的循環(huán).
  pre->rchild = *Thrt;
  pre->RTag = Thread;
  (*Thrt)->rchild = pre;
  return OK;
}

遍歷線索二叉樹

線索二叉樹的遍歷就可以通過之前建立好的線索宫仗,沿著后繼線索依依訪問下去就行够挂。

//非遞歸遍歷線索二叉樹
Status InOrderTraverse(BiThrTree T) {
  BiThrNode *p = T->lchild;
  while(p!=T){
    while(p->LTag==Link) p = p->lchild;    //走向左子樹的盡頭
    printf("%c",p->data );
    while(p->RTag==Thread && p->rchild!=T) {  //訪問該結(jié)點(diǎn)的后續(xù)結(jié)點(diǎn)
      p = p->rchild; 
      printf("%c",p->data );
    }
    p = p->rchild;
  }
  return OK;
}

完整代碼

#include <stdio.h>
#include <stdlib.h>
//函數(shù)狀態(tài)結(jié)果代碼
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼
typedef int Status;
typedef char TElemType;

typedef enum { Link, Thread } PointerTag;

typedef struct BiThrNode {
  TElemType data;
  struct BiThrNode *lchild, *rchild;
  PointerTag LTag;
  PointerTag RTag;
}BiThrNode, *BiThrTree;

//線索二叉樹初始化
Status CreateBiThrNode(BiThrTree * B) {
  char ch;
  scanf("%c", &ch);
  if(ch=='#') *B = NULL;
  else{
    if(!((*B) = (BiThrNode *)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
    (*B)->data = ch;
    (*B)->LTag = Link;
    (*B)->RTag = Link;
    CreateBiThrNode(&(*B)->lchild);
    CreateBiThrNode(&(*B)->rchild);
  }
  return OK;  
}

//線索二叉樹線索化
void InThreading(BiThrTree B,BiThrTree *pre) {
  if(!B) return;

  InThreading(B->lchild,pre);

  if(!B->lchild){
    B->LTag = Thread;
    B->lchild = *pre;
  }

  if(!(*pre)->rchild){
    (*pre)->RTag = Thread;
    (*pre)->rchild = B;
  }

  *pre = B;
  InThreading(B->rchild,pre);
}

//為線索二叉樹添加頭結(jié)點(diǎn)藕夫,使之可以雙向操作
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T){
  if(!(*Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) exit(OVERFLOW);
  (*Thrt)->LTag = Link;
  (*Thrt)->RTag = Thread;
  (*Thrt)->rchild = (*Thrt);
  if(!T) {
    (*Thrt)->lchild = (*Thrt);
    return OK;       //若根結(jié)點(diǎn)不存在,則該二叉樹為空,讓該頭結(jié)點(diǎn)指向自身.
  }
  BiThrTree pre;
  //令頭結(jié)點(diǎn)的左指針指向根結(jié)點(diǎn)
  pre = (*Thrt);
  (*Thrt)->lchild = T;
  //開始遞歸輸入線索化
  InThreading(T,&pre);
  //此時(shí)結(jié)束了最后一個(gè)結(jié)點(diǎn)的線索化了,下面的代碼把頭結(jié)點(diǎn)的后繼指向了最后一個(gè)結(jié)點(diǎn).
  //并把最后一個(gè)結(jié)點(diǎn)的后繼也指向頭結(jié)點(diǎn),此時(shí)樹成為了一個(gè)類似雙向鏈表的循環(huán).
  pre->rchild = *Thrt;
  pre->RTag = Thread;
  (*Thrt)->rchild = pre;
  return OK;
}

//非遞歸遍歷線索二叉樹
Status InOrderTraverse(BiThrTree T) {
  BiThrNode *p = T->lchild;
  while(p!=T){
    while(p->LTag==Link) p = p->lchild;    //走向左子樹的盡頭
    printf("%c",p->data );
    while(p->RTag==Thread && p->rchild!=T) {  //訪問該結(jié)點(diǎn)的后續(xù)結(jié)點(diǎn)
      p = p->rchild; 
      printf("%c",p->data );
    }
    p = p->rchild;
  }
  return OK;
}

int main() {
  BiThrTree B,T;
  CreateBiThrNode(&B);
  InOrderThreading(&T,B);
  printf("中序遍歷二叉樹的結(jié)果為:");
  InOrderTraverse(T);
  printf("\n");
}

//測(cè)試數(shù)據(jù):abc##de#g##f###

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孽糖,一起剝皮案震驚了整個(gè)濱河市枯冈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌办悟,老刑警劉巖尘奏,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異病蛉,居然都是意外死亡炫加,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門铡恕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來琢感,“玉大人,你說我怎么就攤上這事探熔【哉耄” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵诀艰,是天一觀的道長柬甥。 經(jīng)常有香客問我,道長其垄,這世上最難降的妖魔是什么苛蒲? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮绿满,結(jié)果婚禮上臂外,老公的妹妹穿的比我還像新娘。我一直安慰自己喇颁,他們只是感情好漏健,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著橘霎,像睡著了一般蔫浆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姐叁,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天瓦盛,我揣著相機(jī)與錄音,去河邊找鬼外潜。 笑死原环,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的处窥。 我是一名探鬼主播嘱吗,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碧库!你這毒婦竟也來了柜与?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嵌灰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚣镜,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豆同,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驹溃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片城丧。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豌鹤,靈堂內(nèi)的尸體忽然破棺而出亡哄,到底是詐尸還是另有隱情,我是刑警寧澤布疙,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布蚊惯,位于F島的核電站,受9級(jí)特大地震影響灵临,放射性物質(zhì)發(fā)生泄漏截型。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一儒溉、第九天 我趴在偏房一處隱蔽的房頂上張望宦焦。 院中可真熱鬧,春花似錦顿涣、人聲如沸波闹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舔痪。三九已至,卻和暖如春锌唾,著一層夾襖步出監(jiān)牢的瞬間锄码,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工晌涕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滋捶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓余黎,卻偏偏與公主長得像重窟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惧财,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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