數據結構(15.3)線索二叉樹

前言

樹形結構是一種非線性結構,我們對二叉樹進行遍歷,是將二叉樹的結點按照一定的規則排列成一個線性序列,這實際上是對一個非線性化結構進行了線性化操作。前面提到過,線性結構除了第一個結點和最後一個結點以外,每一個結點都有且只有一個前驅和後繼。觀察我們的二叉樹可以發現,我們沒有是辦法直接在樹中找到每個結點的前驅和後繼的,這個信息只能在二叉樹的遍歷中得到。

img_1
與此同時,二叉樹中存在很多空的鏈域,造成了空間上的浪費。(有n個結點,就會有n+1個空鏈域)

爲了解決這些問題,有人想到可以使用空的鏈域來存放結點的前驅和後繼,於是線索二叉樹就出現了。

img_2

現在,二叉樹的子結點指向有兩種可能性:既可能指向其左子樹或者右子樹,也可能指向其前驅或者後繼。當結點指向的是前驅或者後繼時,稱結點爲線索。擁有線索的二叉樹就是線索二叉樹。把空指針轉化爲線索的過程,就叫做線索化。

注意:將二叉樹按照不同的規則來排列可以得到不同的序列,結點的前驅和後繼也會不同,這樣線索的指向也不同了。也就是說,對同一個二叉樹,按照不同的次序進行線索化,可以分別得到先序線索二叉樹、中序線索二叉樹和後序線索二叉樹。本文講的是中序線索的二叉樹。

線索二叉樹的存儲結構

我們知道,二叉樹的結點有三個部分,分別是數據域、左孩子和右孩子。當使用空指針來存放線索時,我們不知道左右孩子指針指向的是子樹還是線索。爲了解決這個問題,線索化二叉樹需要增加兩個標誌域,通過標誌來協助判斷。

//標記
typedef enum {
  	//子樹
    LINK,
   //線索
    THREAD
}TagType;

//線索二叉樹的結點
typedef struct BinTreeNode{
    //數據域
    ElemType data;
    //左孩子/線索
    struct BinTreeNode *leftChild;
    //右孩子/線索
    struct BinTreeNode *rightChild;
    //左標記
    TagType leftTag;
    //右標記
    TagType rightTag;
}BinTreeNode;

線索二叉樹的初始化與創建

線索化二叉樹就是對二叉樹裏的空指針進行修改,使其指向結點前驅或者後繼,而前驅和後繼顯然無法在創建時就獲得。

這意味着要得到線索二叉樹,首先要存在一個二叉樹。因此在初始化和創建上,線索二叉樹與二叉樹是相同的。

創建是指通過輸入一串字符串來生成二叉樹,要有特殊的字符來表示結點不存在。因此初始化時要設置標記的值。

//初始化
void InitBinTree(BinTree *bt, ElemType ref){
    //初始化樹根爲空
    bt->root = NULL;
    //設置結束標記
    bt->refvalue = ref;
}

創建:

爲了方便,先寫一個生成結點的方法:

//生成一個結點
BinTreeNode *GetNewNode(ElemType x){
    BinTreeNode *s = (BinTreeNode *)malloc(sizeof(BinTreeNode));
    assert(s != NULL);
    
    s->data = x;
    s->leftChild = s->rightChild = NULL;
    s->leftTag = s->rightTag = LINK;
    
    return s;
}

然後實現創建方法:


//創建
void CreateBinTree(BinTree *bt, char *str){
    CreateBinTree(bt, bt->root, str);
}
void CreateBinTree(BinTree *bt, BinTreeNode *&t, char *&str){
    if (*str == bt->refvalue) {
        t = NULL;
    }else{
        t = GetNewNode(*str);
        CreateBinTree(bt, t->leftChild, ++str);
        CreateBinTree(bt, t->rightChild, ++str);
    }
}

注:這次的代碼是用c++來寫的,有些地方使用了引用,和直接用指針區別不大:

//創建
void CreateBinTree(BinTree *bt, char *str){
    CreateBinTree1(bt, &(bt->root), str);
}
void CreateBinTree1(BinTree *bt, BinTreeNode **t,char *&str){
    if (*str == bt->refvalue) {
        t = NULL;
    }else{
        *t = GetNewNode(*str);
        CreateBinTree1(bt, &(*t)->leftChild, ++str);
        CreateBinTree1(bt, &(*t)->rightChild, ++str);
    }
}

注注:字符串如果不使用引用,而是用char **str的話,我使用的編譯器會出現一些問題,因此這裏字符串還是使用的引用。

二叉樹的中序線索化

來比較一下中序遍歷方法和線索化方法:

中序遍歷:

//中序遍歷-遞歸
void InOrder(BinTreeNode *t){
    if (t != NULL) {
        //左子樹遞歸遍歷
        InOrder(t->leftChild);
      
        //輸出結點信息
        printf("%4c",t->data);
      
        //右子樹遞歸遍歷
        InOrder(t->rightChild);
    }
}

線索化:

void CreateInThread(BinTreeNode *&t, BinTreeNode *&pre){
    if (t != NULL) {
        //左子樹遞歸線索化
        CreateInThread(t->leftChild, pre);
        
        //將空指針變爲線索
        if (t ->leftChild == NULL) {
            //左子樹爲空->左子樹指向前驅結點
            t->leftTag = THREAD;
            t->leftChild = pre;
        }
        if (pre != NULL && pre->rightChild == NULL) {
            //前驅結點的右子樹爲空->前驅結點的右子樹指向該結點(後繼)
            pre->rightTag = THREAD;
            pre->rightChild = t;
        }
        //更新前驅結點
        pre = t;
        
        //右子樹遞歸線索化
        CreateInThread(t->rightChild, pre);
    }
}

可以發現,線索化方法和中序遍歷方法在結構上是一致的,只不過是中間的操作有所區別。實際上,因爲結點的前驅或後繼信息只能在遍歷中得到,中序線索化的實質就是在中序遍歷的過程中,對空指針進行修改。

當我們找到一個子結點爲空的結點時,假如是左結點,那麼就要修改它的標記值,並且讓它指向本結點的前驅。這說明了我們需要一個額外的變量來保存前驅結點。

假如是右結點,我們此時是不知道本結點的後繼結點的。

但是注意:對本結點的前驅結點來說,本結點就是其後繼結點。也就是說我們這時候能得到前驅結點的後繼信息,因此可以判斷前驅結點的右結點是否爲空,假如爲空,則修改它的標記值,然後讓它指向本結點。

這時候本結點訪問完畢了,需要更新一下前驅結點的指向(本結點成爲了下一個結點的前驅結點)。這同樣也說明,對結點的右結點進行修改,是要放到下一個結點中處理的。

img_3
img_4

有人可能注意到,本結點的右結點放到下一個結點來操作,那麼到最後一個結點的右結點是不會被操作到的

img_5

這就需要進行特殊處理。

可以另外寫一個方法,用於調用CreateInThread()方法,同時對最後一個結點進行處理(我使用的是c++,所以兩個方法的名稱是一樣的)

//中序線索化
void CreateInThread(BinTree *bt){
    BinTreeNode *pre = NULL;
    //調用上面的線索化方法
    CreateInThread(bt->root, pre);
    //處理最後一個結點的右結點
    pre->rightChild = NULL;
    pre->rightTag = THREAD;
}

線索二叉樹的遍歷

現在我們有了一棵線索化的二叉樹,可以很容易得到某個結點的前驅和後繼信息,這樣遍歷起來也很容易。先看如何找到結點的前驅和後繼。

img_6

尋找前驅

對於某結點而言,如果其左結點是線索,就找到了它的前驅。那麼如果其左結點是子樹呢?通過觀察可以發現,如果左結點是子樹,那麼該結點的前驅就是遍歷該結點左子樹時最後一個訪問的結點(左子樹中最右下的結點)。

img_7

那麼,如何找到某個子樹的最後一個結點呢?我們知道這個結點在最右下方,那麼就一直往右子樹查找,只需要找到右結點標記值爲線索的結點,就說明走到頭了,該結點就是最後一個結點。

//查找最後一個結點
BinTreeNode *Last(BinTreeNode *&t){
    if(t == NULL){
        return NULL;
    }
    
    BinTreeNode *p = t;
    while (p->rightTag == LINK) {
        p = p->rightChild;
    }
    return p;
}

這樣,對尋找結點前驅來說,只需要判斷其左結點的標記值,如果是線索,則直接返回左結點;否則返回左子樹的最後一個結點。

//尋找前驅
BinTreeNode *Prio(BinTreeNode *t,BinTreeNode *cur){
    if(t == NULL || cur == NULL){
        return NULL;
    }
    if (cur->leftTag == THREAD) {
        return cur->leftChild;
    }else{
        return Last(cur->leftChild);
    }
}

尋找後繼

同理,對於某結點而言,假如其右結點是線索,可以直接得到後繼。假如是子樹,則其後繼是訪問右子樹時訪問到的第一個結點(右子樹中最左下的結點)。

img_8

查找某子樹中訪問到的第一個結點也是一樣,一直往左子樹查找,找到左結點的標記值爲線索的結點,就說明到頭了。

//查找第一個訪問到的結點
BinTreeNode *First(BinTreeNode *&t){
    if(t == NULL){
        return NULL;
    }
    
    BinTreeNode *p = t;
    while (p->leftTag == LINK) {
        p = p->leftChild;
    }
    return p;
}

對尋找後繼結點來說,只需要判斷其右結點的標記值,如果爲線索則直接返回右結點;否則返回右子樹的第一個結點。

//尋找後繼
BinTreeNode *Next(BinTreeNode *&t,BinTreeNode *cur){
    if(t == NULL || cur == NULL){
        return NULL;
    }
    //如果右子樹是線索,直接返回右右子樹
    if (cur->rightTag == THREAD) {
        return cur->rightChild;
    }else{
        return First(cur->rightChild);
    }
}

遍歷

現在,要遍歷二叉樹就很容易了:只需要從第一個結點開始,不斷找到其後繼並且輸出即可。

//中序遍歷
void InOrder(BinTreeNode *t){
    BinTreeNode *p;
    for (p = First(t); p != NULL; p = Next(t, p)) {
        printf("%c ",p->data);
    }
    printf("\n");
}

當然,這麼中序遍歷簡便是對於中序線索二叉樹來說的,如果其他遍歷也想這麼簡便,還需要建立對應次序的線索二叉樹。但是,在先序線索二叉樹上尋找前驅或者在後序線索二叉樹上尋找後繼,都比較複雜。

線索二叉樹的查找

查找某結點

要查找某結點很容易,只需要在遍歷的同時尋找即可

BinTreeNode *Search(BinTreeNode *t,ElemType key){
    BinTreeNode *p = NULL;
    for (p = First(t); p != NULL; p = Next(t, p)) {
        if (p->data == key) {
            return p;
        }
    }
    return p;
}

查找某結點的父結點

查找父結點則有點困難。總的來說,想查找某個結點的父結點,要將以該結點爲根的樹看做一個整體,然後尋找到這棵樹的前驅和後繼結點,其中必然有一個是其父結點。所以還需要特別判斷:假如是前驅,判斷前驅的右子樹是不是本結點;假如是後繼,判斷後繼的左子樹是不是本結點。

img_9

首先觀察擁有線索的結點,以E結點爲例,以E爲根的整棵樹,其前驅結點是B,後繼結點是D,判斷一下前驅結點的右子樹和後繼結點的左子樹是否爲E,即可找到E的父結點。(擁有線索的結點,它的父結點一定是它的線索)。

img_11

假如結點擁有的是左右子樹,很顯然我們需要繞出這棵樹來尋找。以D結點爲例,將以D爲根的子樹看做一個整體,要繞出這棵樹可以選擇往左子樹一直走,繞到結點B,B此時可以看做以D爲根的整棵樹的前驅;也可以往右子樹一直走,繞到結點A,A此時可以看做以D爲根的整棵樹的後繼。A結點和B結點之中必然有一個是D的父結點。

總結以上規律,可以得到方法:

BinTreeNode *Parent(BinTreeNode *t,BinTreeNode *cur){
    if (t == NULL || cur == NULL) {
        return NULL;
    }
    if (t == cur) {
        return NULL;
    }
    
    BinTreeNode *p;
    //有線索->查找前驅
    if (cur->leftTag == THREAD) {
        p = cur->leftChild;
        if (p->rightChild == cur) {
            return p;
        }
    }
    //有線索->查找後繼
    if (cur->rightTag == THREAD) {
        p = cur->rightChild;
        if (p->leftChild == cur) {
            return p;
        }
    }
    
    //有子樹->得到整棵樹的前驅
    p = First(cur->leftChild);
    p = p->leftChild;
    if (p != NULL && p->rightChild == cur) {
        return p;
    }
    //得到整棵樹的後繼
    p = Last(cur->rightChild);
    //不是前驅必然是後繼,直接輸出
    return p->rightChild;
}

全部代碼

ThreadBinTree.hpp


#ifndef ThreadBinTree_hpp
#define ThreadBinTree_hpp

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define ElemType char

//標記
typedef enum {
    LINK,
    THREAD
}TagType;

//線索二叉樹的結點
typedef struct BinTreeNode{
    //數據域
    ElemType data;
    //左孩子/線索
    struct BinTreeNode *leftChild;
    //右孩子/線索
    struct BinTreeNode *rightChild;
    //左標記
    TagType leftTag;
    //右標記
    TagType rightTag;
}BinTreeNode;

//線索二叉樹
typedef struct BinTree{
    BinTreeNode *root;
    ElemType refvalue;
}BinTree;

//初始化
void InitBinTree(BinTree *bt, ElemType ref);

//申請一個結點
BinTreeNode *GetNewNode(ElemType x);
//創建
void CreateBinTree(BinTree *bt, char *str);
void CreateBinTree(BinTree *bt, BinTreeNode *&t, char *&str);
void CreateBinTree1(BinTree *bt, BinTreeNode **t,char *&str);

//中序線索化
void CreateInThread(BinTree *bt);
void CreateInThread(BinTreeNode *&t, BinTreeNode *&pre);
void CreateInThread1(BinTreeNode *&t, BinTreeNode *&pre);

//求第一個結點
BinTreeNode *First(BinTree *bt);
BinTreeNode *First(BinTreeNode *&t);
//求最後一個結點
BinTreeNode *Last(BinTree *bt);
BinTreeNode *Last(BinTreeNode *&t);
//求後繼結點
BinTreeNode *Next(BinTree *bt,BinTreeNode *cur);
BinTreeNode *Next(BinTreeNode *&t,BinTreeNode *cur);
//求前驅結點
BinTreeNode *Prio(BinTree *bt,BinTreeNode *cur);
BinTreeNode *Prio(BinTreeNode *t,BinTreeNode *cur);

//中序遍歷
void InOrder(BinTree *bt);
void InOrder(BinTreeNode *t);

//查找某結點
BinTreeNode *Search(BinTree *bt,ElemType key);
BinTreeNode *Search(BinTreeNode *t,ElemType key);
//查找某結點的父結點
BinTreeNode *Parent(BinTree *bt, BinTreeNode *cur);
BinTreeNode *Parent(BinTreeNode *t,BinTreeNode *cur);

#endif /* ThreadBinTree_hpp */

ThreadBinTree.cpp

#include "ThreadBinTree.hpp"

//初始化
void InitBinTree(BinTree *bt, ElemType ref){
    //初始化樹根爲空
    bt->root = NULL;
    //設置結束標記
    bt->refvalue = ref;
}

//生成一個結點
BinTreeNode *GetNewNode(ElemType x){
    BinTreeNode *s = (BinTreeNode *)malloc(sizeof(BinTreeNode));
    assert(s != NULL);
    
    s->data = x;
    s->leftChild = s->rightChild = NULL;
    s->leftTag = s->rightTag = LINK;
    
    return s;
}
//創建
void CreateBinTree(BinTree *bt, char *str){
    CreateBinTree1(bt, &(bt->root), str);
}
void CreateBinTree(BinTree *bt, BinTreeNode *&t, char *&str){
    if (*str == bt->refvalue) {
        t = NULL;
    }else{
        t = GetNewNode(*str);
        CreateBinTree(bt, t->leftChild, ++str);
        CreateBinTree(bt, t->rightChild, ++str);
    }
}
void CreateBinTree1(BinTree *bt, BinTreeNode **t,char *&str){
    if (*str == bt->refvalue) {
        t = NULL;
    }else{
        *t = GetNewNode(*str);
        CreateBinTree1(bt, &(*t)->leftChild, ++str);
        CreateBinTree1(bt, &(*t)->rightChild, ++str);
    }
}

//中序線索化
void CreateInThread(BinTree *bt){
    BinTreeNode *pre = NULL;
    CreateInThread(bt->root, pre);
    //處理最後一個結點的右結點
    pre->rightChild = NULL;
    pre->rightTag = THREAD;
}
void CreateInThread(BinTreeNode *&t, BinTreeNode *&pre){
    if (t != NULL) {
        //左子樹遞歸線索化
        CreateInThread(t->leftChild, pre);
        
        //將空指針變爲線索
        if (t ->leftChild == NULL) {
            //左子樹爲空->左子樹指向前驅結點
            t->leftTag = THREAD;
            t->leftChild = pre;
        }
        if (pre != NULL && pre->rightChild == NULL) {
            //前驅結點的右子樹爲空->前驅結點的右子樹指向該結點(後繼)
            pre->rightTag = THREAD;
            pre->rightChild = t;
        }
        //更新前驅結點
        pre = t;
        
        //右子樹遞歸線索化
        CreateInThread(t->rightChild, pre);
    }
}

void CreateInThread1(BinTreeNode *&t, BinTreeNode *&pre){
    if (t) {
        CreateInThread(t->leftChild, pre);
        
        if (!t->leftChild) {
            t->leftTag = THREAD;
            t->leftChild = pre;
        }
        if (!pre || !pre->rightChild) {
            pre->rightTag = THREAD;
            pre->rightChild = t;
        }
        
        pre = t;
        CreateInThread1(t->rightChild, pre);
    }
}


//求第一個訪問到的結點
BinTreeNode *First(BinTree *bt){
    return First(bt->root);
}
BinTreeNode *First(BinTreeNode *&t){
    if(t == NULL){
        return NULL;
    }
    
    BinTreeNode *p = t;
    while (p->leftTag == LINK) {
        p = p->leftChild;
    }
    return p;
}
//求最後一個訪問到的結點
BinTreeNode *Last(BinTree *bt){
    return Last(bt->root);
}
BinTreeNode *Last(BinTreeNode *&t){
    if(t == NULL){
        return NULL;
    }
    
    BinTreeNode *p = t;
    while (p->rightTag == LINK) {
        p = p->rightChild;
    }
    return p;
}
//求後繼結點
BinTreeNode *Next(BinTree *bt,BinTreeNode *cur){
    return Next(bt->root, cur);
}
BinTreeNode *Next(BinTreeNode *&t,BinTreeNode *cur){
    if(t == NULL || cur == NULL){
        return NULL;
    }
    //如果右子樹是線索,直接返回右右子樹
    if (cur->rightTag == THREAD) {
        return cur->rightChild;
    }else{
        return First(cur->rightChild);
    }
}
//求前驅結點
BinTreeNode *Prio(BinTree *bt,BinTreeNode *cur){
    return Prio(bt->root, cur);
}
BinTreeNode *Prio(BinTreeNode *t,BinTreeNode *cur){
    if(t == NULL || cur == NULL){
        return NULL;
    }
    if (cur->leftTag == THREAD) {
        return cur->leftChild;
    }else{
        return Last(cur->leftChild);
    }
}

//中序遍歷
void InOrder(BinTree *bt){
    InOrder(bt->root);
}
void InOrder(BinTreeNode *t){
    BinTreeNode *p;
    for (p = First(t); p != NULL; p = Next(t, p)) {
        printf("%c ",p->data);
    }
    printf("\n");
}

//查找某結點
BinTreeNode *Search(BinTree *bt,ElemType key){
    return Search(bt->root, key);
}
BinTreeNode *Search(BinTreeNode *t,ElemType key){
 
    BinTreeNode *p = NULL;
    for (p = First(t); p != NULL; p = Next(t, p)) {
        if (p->data == key) {
            return p;
        }
    }
    return p;
}
//查找某結點的父結點
BinTreeNode *Parent(BinTree *bt, BinTreeNode *cur){
    return Parent(bt->root, cur);
}
BinTreeNode *Parent(BinTreeNode *t,BinTreeNode *cur){
    if (t == NULL || cur == NULL) {
        return NULL;
    }
    if (t == cur) {
        return NULL;
    }
    
    BinTreeNode *p;
    //有線索->查找前驅
    if (cur->leftTag == THREAD) {
        p = cur->leftChild;
        if (p->rightChild == cur) {
            return p;
        }
    }
    //有線索->查找後繼
    if (cur->rightTag == THREAD) {
        p = cur->rightChild;
        if (p->leftChild == cur) {
            return p;
        }
    }
    
    //有子樹->得到整棵樹的前驅
    p = First(cur->leftChild);
    p = p->leftChild;
    if (p != NULL && p->rightChild == cur) {
        return p;
    }
    //得到整棵樹的後繼
    p = Last(cur->rightChild);
    //不是前驅必然是後繼,直接輸出
    return p->rightChild;
}

main.cpp

#include "ThreadBinTree.hpp"

int main(int argc, const char * argv[]) {
    char *str = "ABC##DE##F##G#H##";
    BinTree myTree;
    InitBinTree(&myTree,'#');
    CreateBinTree(&myTree, str);
    CreateInThread(&myTree);
    
    InOrder(&myTree);
    
    BinTreeNode *first = First(&myTree);
    printf("first:%c\n",first->data);
    
    BinTreeNode *last = Last(&myTree);
    printf("last:%c\n",last->data);
    
    BinTreeNode *find = Search(&myTree, 'D');
    printf("find 'D':%c\n",find->data);
    
    BinTreeNode *pre = Prio(&myTree,find);
    printf("D pre:%c\n",pre->data);
    
    BinTreeNode *next = Next(&myTree,find);
    printf("D next:%c\n",next->data);
    
    BinTreeNode *parent = Parent(&myTree, find);
    printf("parent: %c\n",parent->data);
    
    
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章