【数据结构】【王道】【树与二叉树】中序二叉线索树的实现及基本操作(可直接运行)

总目录:https://blog.csdn.net/treesorshining/article/details/125726400

1.概述

在含有n个结点的二叉树中,有n+1个空指针,即每个度为1的结点有一个空指针,每个叶子结点有两个空指针,这就造成了空间的浪费。可以通过一些方法将这些空指针充分利用起来,即将这些空指针改成指向前驱或后继的线索,可以视为在原本的二叉树上用空指针建立一个线索链表,加上了线索的二叉树也可称为线索二叉树。

2.基本操作

2.1 结构体构造

// 线索二叉树结点
typedef struct ThreadNode {
    char data;  // 数据域
    // 左孩子、右孩子
    struct ThreadNode *lchild, *rchild;
    // 左、右线索标志
    // tag==0表示指针指向孩子
    // tag==1表示指针指向线索
    int ltag, rtag;
} ThreadNode, *ThreadTree;

// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;

2.2 初始化

建立一个根节点,同时将左右孩子指针置空,将左右tag设为0。

// 初始化
void InitTree(ThreadNode* &T) {
    // 初始化可将根节点置空,空树
    T = NULL;
    // 插入根节点
    // 分配空间
    T = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 赋值
    T->data = 'A';
    // 初始tag设为0
    T->ltag = 0;
    T->rtag = 0;
    // 初始无左右子树
    T->lchild = NULL;
    T->rchild = NULL;
}

2.3 左插入结点

与一般二叉树插入类似,不过要注意修改tag的值。

// 左插入结点
bool InsertLeftTreeNode(ThreadNode* &T, char x) {
    // 分配空间
    ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 分配空间失败的情况
    if(p == NULL) {
        return false;
    }
    // 数据域赋值
    p->data = x;
    // 将tag值设为0
    p->ltag = 0;
    p->rtag = 0;
    // 初始插入,无左右孩子,置空
    p->lchild = NULL;
    p->rchild = NULL;
    // 作为指定插入结点的左孩子
    T->lchild = p;
    return true;
}

2.4 右插入结点

与一般二叉树插入类似,不过要注意修改tag的值。

// 右插入结点
bool InsertRightTreeNode(ThreadNode* &T, char x) {
    // 分配空间
    ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 分配空间失败的情况
    if(p == NULL) {
        return false;
    }
    // 数据域赋值
    p->data = x;
    // 将tag值设为0
    p->ltag = 0;
    p->rtag = 0;
    // 初始插入,无左右孩子,置空
    p->lchild = NULL;
    p->rchild = NULL;
    // 作为指定插入结点的左孩子
    T->rchild = p;
    return true;
}

2.5 访问结点并建立线索

遍历中访问结点,在遍历的同时建立前驱及后继线索。

// 用于访问结点
void visit(ThreadNode *q) {
    // 左子树为空,建立前驱线索
    if(q->lchild == NULL) {
        // 令其左指针指向前驱
        q->lchild = pre;
        // 将tag值修改为1,表示此处左指针指向的是前驱,即左孩子指针是线索
        q->ltag = 1;
        if(pre != NULL) {
            printf("建立前驱线索:%c-->%c\n", q->data, pre->data);
        } else {
            printf("建立前驱线索:%c-->%s\n", q->data, "NULL");
        }
    }
    // 设立后继线索时前驱不能为空(无前驱无法建立后继)
    // 右孩子指针为空,表示可以建立后继线索
    if(pre != NULL && pre->rchild == NULL) {
        // 建立前驱结点的后继线索
        pre->rchild = q;
        // 将tag值修改为1,表示此处右指针指向的是后继,即右孩子指针是线索
        pre->rtag = 1;
        printf("建立后继线索:%c-->%c\n", pre->data, q->data);
    }
    // 线索建立完成后,前驱指针移动,指向当前指向结点处
    pre = q;
}

2.6 中序遍历二叉树并建立线索

实际上就是进行中序遍历,一边遍历一边线索化,因为想得到一个结点的前驱或者后继,只能在遍历时取得。

// 中序线索化二叉树
// 实际上就是进行中序遍历,一边遍历一边线索化
// 因为想得到一个结点的前驱或者后继,只能在遍历时取得
// 线索化在visit函数中完成
void InThread(ThreadTree T) {
    if(T != NULL) {
        InThread(T->lchild);
        visit(T);
        InThread(T->rchild);
    }
}

2.7 中序线索化二叉树

// 中序线索化二叉树
void CreateInThread(ThreadTree T) {
    // pre初始为NULL
    pre = NULL;
    // 若二叉树为空,则不可线索化
    if(T != NULL) {
        // 中序线索化二叉树
        InThread(T);
        // 对于最后一个结点的处理
        // 由于访问完成后,pre和p都指向最后一个结点
        // 按照线索化的要求,如果最后一个结点右孩子指向NULL,则可视为其有后继结点
        // 应将tag设为1
        if(pre->rchild == NULL) {
            pre->rtag = 1;
            printf("建立后继线索:%c-->%s\n", pre->data, "NULL");
        }
    }
}

2.8 找到第一个被中序遍历的结点

找到以p为根的子树中,第一个被中序遍历的结点。

// 找到以p为根的子树中,第一个被中序遍历的结点
// 即后继结点
ThreadNode *FirstNode(ThreadNode *p) {
    // 循环找到最左下结点(不一定是叶子结点)
    // 当ltag==0时,说明其线索二叉树此结点不存在左线索,那么一定有左子树
    while(p->ltag == 0) {
        p = p->lchild;
    }
    return p;
}

2.9 在中序线索二叉树中找到某一结点的后继结点

要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可,如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右。如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点,如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点。

// 在中序线索二叉树中找到结点p的后继结点
// 要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右
// 如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点
// 如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点
// 注意:若没有线索,则一定存在右子树(根据线索二叉树特点)
ThreadNode *NextNode(ThreadNode *p) {
    // 如果右孩子结点无线索
    if(p->rtag == 0) return FirstNode(p->rchild);
    // 如果右孩子结点有线索则直接利用线索找到结点即可
    else return p->rchild;
}

2.10 利用中序线索树完成非递归中序遍历

// 对中序线索二叉树进行中序遍历(利用线索非递归实现)
// 首先找到根节点最左下结点,访问之后,通过NextNode函数找后继结点访问即可
void InOrder(ThreadNode *T) {
    for(ThreadNode *p = FirstNode(T);p != NULL;p = NextNode(p)) {
        visit1(p);
    }
}

3.完整代码

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

// 线索二叉树结点
typedef struct ThreadNode {
    char data;  // 数据域
    // 左孩子、右孩子
    struct ThreadNode *lchild, *rchild;
    // 左、右线索标志
    // tag==0表示指针指向孩子
    // tag==1表示指针指向线索
    int ltag, rtag;
} ThreadNode, *ThreadTree;

// 全局变量pre,指向当前访问结点的前驱
ThreadNode *pre = NULL;

// 初始化
void InitTree(ThreadNode* &T) {
    // 初始化可将根节点置空,空树
    T = NULL;
    // 插入根节点
    // 分配空间
    T = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 赋值
    T->data = 'A';
    // 初始tag设为0
    T->ltag = 0;
    T->rtag = 0;
    // 初始无左右子树
    T->lchild = NULL;
    T->rchild = NULL;
}

// 左插入结点
bool InsertLeftTreeNode(ThreadNode* &T, char x) {
    // 分配空间
    ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 分配空间失败的情况
    if(p == NULL) {
        return false;
    }
    // 数据域赋值
    p->data = x;
    // 将tag值设为0
    p->ltag = 0;
    p->rtag = 0;
    // 初始插入,无左右孩子,置空
    p->lchild = NULL;
    p->rchild = NULL;
    // 作为指定插入结点的左孩子
    T->lchild = p;
    return true;
}

// 右插入结点
bool InsertRightTreeNode(ThreadNode* &T, char x) {
    // 分配空间
    ThreadNode *p = (ThreadNode*)malloc(sizeof(ThreadNode));
    // 分配空间失败的情况
    if(p == NULL) {
        return false;
    }
    // 数据域赋值
    p->data = x;
    // 将tag值设为0
    p->ltag = 0;
    p->rtag = 0;
    // 初始插入,无左右孩子,置空
    p->lchild = NULL;
    p->rchild = NULL;
    // 作为指定插入结点的左孩子
    T->rchild = p;
    return true;
}

// 用于访问结点
void visit(ThreadNode *q) {
    // 左子树为空,建立前驱线索
    if(q->lchild == NULL) {
        // 令其左指针指向前驱
        q->lchild = pre;
        // 将tag值修改为1,表示此处左指针指向的是前驱,即左孩子指针是线索
        q->ltag = 1;
        if(pre != NULL) {
            printf("建立前驱线索:%c-->%c\n", q->data, pre->data);
        } else {
            printf("建立前驱线索:%c-->%s\n", q->data, "NULL");
        }
    }
    // 设立后继线索时前驱不能为空(无前驱无法建立后继)
    // 右孩子指针为空,表示可以建立后继线索
    if(pre != NULL && pre->rchild == NULL) {
        // 建立前驱结点的后继线索
        pre->rchild = q;
        // 将tag值修改为1,表示此处右指针指向的是后继,即右孩子指针是线索
        pre->rtag = 1;
        printf("建立后继线索:%c-->%c\n", pre->data, q->data);
    }
    // 线索建立完成后,前驱指针移动,指向当前指向结点处
    pre = q;
}

// 中序线索化二叉树
// 实际上就是进行中序遍历,一边遍历一边线索化
// 因为想得到一个结点的前驱或者后继,只能在遍历时取得
// 线索化在visit函数中完成
void InThread(ThreadTree T) {
    if(T != NULL) {
        InThread(T->lchild);
        visit(T);
        InThread(T->rchild);
    }
}

// 中序线索化二叉树
void CreateInThread(ThreadTree T) {
    // pre初始为NULL
    pre = NULL;
    // 若二叉树为空,则不可线索化
    if(T != NULL) {
        // 中序线索化二叉树
        InThread(T);
        // 对于最后一个结点的处理
        // 由于访问完成后,pre和p都指向最后一个结点
        // 按照线索化的要求,如果最后一个结点右孩子指向NULL,则可视为其有后继结点
        // 应将tag设为1
        if(pre->rchild == NULL) {
            pre->rtag = 1;
            printf("建立后继线索:%c-->%s\n", pre->data, "NULL");
        }
    }
}

// 找到以p为根的子树中,第一个被中序遍历的结点
// 即后继结点
ThreadNode *FirstNode(ThreadNode *p) {
    // 循环找到最左下结点(不一定是叶子结点)
    // 当ltag==0时,说明其线索二叉树此结点不存在左线索,那么一定有左子树
    while(p->ltag == 0) {
        p = p->lchild;
    }
    return p;
}

// 在中序线索二叉树中找到结点p的后继结点
// 要找到一个结点的后继,如果右孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找后继结点,即左根右
// 如果该结点有右孩子且右孩子为叶子结点,那么这就是其后继结点
// 如果该节点有右孩子,且为分支结点,那么后继结点就应该是该节点右子树的最左下结点
// 注意:若没有线索,则一定存在右子树(根据线索二叉树特点)
ThreadNode *NextNode(ThreadNode *p) {
    // 如果右孩子结点无线索
    if(p->rtag == 0) return FirstNode(p->rchild);
    // 如果右孩子结点有线索则直接利用线索找到结点即可
    else return p->rchild;
}

// 访问结点
void visit1(ThreadNode *T) {
    printf("%c   ", T->data);
}

// 对中序线索二叉树进行中序遍历(利用线索非递归实现)
// 首先找到根节点最左下结点,访问之后,通过NextNode函数找后继结点访问即可
void InOrder(ThreadNode *T) {
    for(ThreadNode *p = FirstNode(T);p != NULL;p = NextNode(p)) {
        visit1(p);
    }
}

// 找到以p为根的子树中最后一个被中序遍历的结点
// 即前驱结点
ThreadNode *LastNode(ThreadNode *p) {
    // 循环找到最右下结点(不一定为叶子结点)
    // 当rtag==0时,说明其线索二叉树此结点不存在右线索,那么一定有右子树
    while(p->rtag == 0) {
        p = p->rchild;
    }
    return p;
}

// 在中序线索二叉树中找到结点p的前驱结点
// 要找到一个结点的前驱,如果左孩子指针tag==1,直接用线索即可
// 如若不为1,那么就要根据中序遍历的特点来寻找前驱结点,即左根右
// 即应该找到其左子树中遍历的最后一个结点,即左子树最右下结点,那即是其前驱结点
// 注意:若没有线索,则一定存在左子树(根据线索二叉树特点)
ThreadNode *PreNode(ThreadNode *p) {
    // 左子树中最右下结点
    if(p->ltag == 0) {
        return LastNode(p->lchild);
    } else {
        // 如果右孩子结点有线索则直接利用线索找到结点即可
        return p->lchild;
    }
}

int main() {
    ThreadTree T;
    InitTree(T);
    InsertLeftTreeNode(T, 'B');
    InsertRightTreeNode(T, 'C');
    InsertLeftTreeNode(T->lchild, 'D');
    InsertRightTreeNode(T->lchild, 'E');
    InsertLeftTreeNode(T->rchild, 'F');
    InsertRightTreeNode(T->rchild, 'G');
    CreateInThread(T);
    printf("InOrder\n");
    InOrder(T);
}

4.运行结果

在这里插入图片描述

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Silver Star

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值