數據結構與算法--線索化二叉樹

前言

  前一篇簡單的對二叉樹進行初探,簡單的瞭解了一下二叉樹的一些概念,和二叉樹的 順序存儲鏈式存儲 以及二叉樹的一些簡單操做,和二叉樹的幾種遍歷方式。這一篇,咱們在對二叉樹進行了解,假如這個二叉樹有不少的葉子節點,那麼葉子節點左孩子右孩子的指針空間是否會浪費呢?數組

1. 線索化二叉樹初探

如開篇提到的,假如,一個二叉樹有不少的葉子節點,以下圖 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;
複製代碼

2. 線索化二叉樹實現

下面以中序遍歷爲例,線索二叉樹的線索化和遍歷指針

  • 線索二叉樹的線索化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);

複製代碼
相關文章
相關標籤/搜索