树、二叉树、线索二叉树

1. 树的表达方式

集合中的元素关系呈现出一对多的情况

1.1 树的定义

  • 树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件 :
  1. 有且仅有一个特定的称为根(Root)的节点
  2. 其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树, 并称为其根的子树(Subtree)。

  •  树的定义具有递归性,即“树中还有树”。

1.2 树的概念

  • 结点:使⽤树结构存储的每一个数据元素都被称为“结点”。例如图中的A就是一个结点。
  • 根结点:有一个特殊的结点,这个结点没有前驱,我们将这种结点称之为根结点。
  • ⽗结点(双亲结点)、子结点和兄弟结点:对于ABCD四个结点来说,A就是BCD的⽗结点,也称之为双亲结点。 ⽽BCD都是A的子结点,也称之为孩子结点。对于BCD来说,因为他们都有同一个爹,所以它们互相称之为兄弟结点。
  • 叶子结点:如果一个结点没有任何子结点,那么此结点就称之为叶子结点。
  • 结点的度:结点拥有的子树的个数,就称之为结点的度。
  • 树的度:在各个结点当中,度的最⼤值。为树的度。
  • 树的深度或者⾼度:结点的层次从根结点开始定义起,根为第一层,根的孩子为第二层。依次类推。

  •  结点A的度:3 结点B的度:2 结点M的度:0
  • 结点A的孩子:B C D 结点B的孩子:E F
  • 树的度:3 树的深度:4
  • 叶子结点:K L F G M I J
  • 结点A是结点F的祖先
  • 结点F是结点K的叔叔结点

 1.3 树的存储结构

1.3.1 双亲表示法

 双亲表示法采⽤顺序表(也就是数组)存储普通树,其实现的核心思想是:顺序存储各个节点的同时,给各节点附加一个记录其⽗节点位置的变量。

根节点没有⽗节点(⽗节点又称为双亲节点),因此根节点记录⽗节点位置的变量通常置为 -1。

  • 利⽤顺序表存储,表元素由数据和⽗结点构成
  • 特点分析:
  1. 根结点没有双亲,所以位置域设置为-1
  2. 知道一个结点,找他的⽗结点,非常容易,O(1)级
  3. 找孩子节点,必须遍历整个表

 1.3.2 孩子表示法

孩子表示法存储普通树采⽤的是 "顺序表+链表" 的组合结构。

其存储过程是:从树的根节点开始,使⽤顺序表依次存储树中各个节点。需要注意,与双亲表示法不同的是,孩子表示法会给各个节点配备一个链表,⽤于存储各节点的孩子节点位于顺序表中的位置。

如果节点没有孩子节点(叶子节点),则该节点的链表为空链表。

 使⽤孩子表示法存储的树结构,正好和双亲表示法相反,查找孩子结点的效率很⾼,⽽不擅长做查找⽗结点的操作。

我们还可以将双亲表示法和孩子表示法合二为一

 1.3.3 孩子兄弟表示法

在树结构中,同一层的节点互为兄弟节点。例如普通树中,节点 A、B 和 C 互为兄弟节点,⽽节点 D、E 和 F 也 互为兄弟节点。

所谓孩子兄弟表示法,指的是⽤将整棵树⽤二叉链表存储起来,具体实现方案是:从树的根节点开始,依次存储各 个结点的孩子结点和兄弟结点。

在二叉链表中,各个结点包含三部分内容:

 在以孩子兄弟表示法构建的二叉链表中,如果要查找结点 x 的所有孩子,则只要根据该结点的 firstchild 指针找到 它的第一个孩子,然后沿着孩子结点的 nextsibling 指针不断地找它的兄弟结点,就可以找到结点 x 的所有孩子。

2.二叉树简介

2.1二叉树定义

  • 二叉树是每个结点最多有两个子树的树结构。二叉树不允许存在度⼤于2的树。
  • 它有五种最基本的形态:
  1. 二叉树可以是空集
  2. 根可以有空的左子树或者右子树;
  3. 左右子树都是空。只有左子树或者右子树的叫做斜树。

 2.2二叉树的概念和性质

2.2.1完全二叉树和满二叉树

  • 满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都 在同一层上,这样的二叉树称为满二叉树。

一棵深度为k且有2 -1个结点的二叉树称为满二叉树。( k ≥ 1)

  •  完全二叉树

如果一棵深度为k,有n个结点的二叉树中各结点能够与深度为k的顺序编号的满二叉树从1到n标号的结点相对应的 二叉树称为完全二叉树。

 特点:

  • 所有的叶结点都出现在第k层或k-1层
  • 若任一结点,如果其右子树的最⼤层次为i,则其左子树的最⼤层次为i或i+1

2.2.2二叉树的性质

  • 性质1

在二叉树的第i层上的结点最多为2 ^(i-1)个。(i ≥ 1)

  • 性质2

深度为k的二叉树至多有2^k -1个结点。(i ≥ 1)

  • 性质3

在一棵二叉树中,叶结点的数目比度为2的结点数目多一个。

        总节点数为各类节点之和:n = n0 + n1 + n2

        总节点数为所有子节点数加一:n = n 1+ 2*n2 + 1

故:n0 = n2 + 1

  • 性质4

具有N个结点的完全二叉树的深度为log N+1。(向下取整)

  • 性质5

如果有一棵n个结点的完全二叉树,其结点编号按照层次序(从上到下,从左到右),则除根结点外,满足[i/2 , i, 2i, 2i+1]的规则

2.3二叉树的存储

2.3.1顺序存储

  • 依靠性质5,可以将任意棵二叉树构造成满二叉树结构或完全二叉树结构,依据下标规则,就可以找到⽗结点,子结点

核心算法:
拥有天然的下标索引0 1 2 3 4 5

规定下标索引的运算规则

1.[i/2,i,2i,2i+1]:父节点,本身节点、子节点、子节点

2.3.2链式存储

  • 由于二叉树的每个结点最多只能有两个子树,每个结点定义两个指针域和一个数据域即可。

2.4二叉树存储的核心代码

//树的节点结构
typedef class treeNode{
public:
    Element data;       //保存的元素
    treeNode *left;     //左子节点
    treeNode *right;    //右子节点
}TreeNode;
//二叉树描述信息的结构
typedef class {
public:
    TreeNode* root; //二叉树的根节点
    int count;      //二叉树的节点数量
}BinaryTree;

3二叉树的遍历

3.1遍历思想

  • 遍历 :沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。
  • 对线性结构⽽言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。
  • 二叉树是非线性结构,每个结点有两个后继,则存在如何遍历,即按什么样的搜索路径进行遍历的问题。
  1. 按层次,⽗子关系,知道了⽗,那么就把其所有的子结点都看一遍
  2. 按深度,一条道走到黑,然后再返回走另一条道

 

 3.2广度遍历(层次遍历)

每到一层,面临多个任务,不能同时处理,于是引入队列。

  • 算法思想:
  1. 引入队列,将根结点入队
  2. 从队列中取出队头元素,访问该结点,将该结点的所有孩子节点入队
  3. 再次从队列中取出队头元素,并访问,以此重复
  4. 直到队列为空,说明所有元素都遍历完成
  • 算法实现
void levelOrderBTree(BinaryTree *tree){
    ArrayQueue *queue=new ArrayQueue ;      //申请一个队列
    pTreeNode node;                         //用来存放取出的节点
    enArrayQueue(queue,tree->root);      //根节点入队
    while(deArrayQueue(queue,&node)!=-1){   //取出节点
        //访问节点
        visitTreeNode(node);
        //入队左右节点
        if(node->left)enArrayQueue(queue,node->left);
        if(node->right)enArrayQueue(queue,node->right);
    }
    //遍历结束,释放队列
    releaseArrayQueue(queue);
}

3.3递归

递归的概念

  • 递归其实就是某个函数直接或者间接的调⽤了⾃身。这种调⽤方式叫递归调⽤。说⽩了还是一个函数调⽤。
  • 既然是函数调⽤,那么就有一个雷打不动的原则:所有被调⽤的函数都将创建一个副本,各⾃为调⽤者服务,⽽不受 其他函数的影响。

递归的条件
递归函数分为两个条件,边界条件和递归条件。

  • 边界条件:就是递归中⽌条件,避免出现死循环。也叫做递归出⼝。
  • 递归条件:也就是递归体。将一个⼤问题分解为一步步⼩问题。也是递归调⽤的阶段。

递归函数在具备这两个要素以后,才可以在有限次的计算后得出想要的结果。

3.4深度遍历

每个节点处理方式都是一样 大任务 分解成小任务
小任务处理完后,最终回到大任务处结束 递归

3.4.1先序遍历

  • 先访问根结点、然后左子树、最后右子树
//递归写法
static void preOrder(TreeNode *node) {
   if (node) {
      visitTreeNode(node);        //访问根节点
      preOrder(node->left);        //进入左节点
      preOrder(node->right);        //进入右节点
   }
}
void preOrderBTreeRecur(BinaryTree *tree) {
   if (tree) {
      preOrder(tree->root);
   }
}

 中序遍历代码和后序遍历代码与前序代码类似

3.4.2中序遍历

  • 先访问左子树、然后根节点、最后右子树

3.4.3后序遍历

  • 先访问左子树、然后右子树、最后根节点

3.4.4二叉树的先序、中序、后序互推方法

先序 可以确定一棵树的根节点    后序  从后看也能确定根
 中序 一旦确定了根节点后,在中序的序列中,以根为中心,左边的序列一定是这个根的左孩子,另外就是右孩子

 只知道先序和后序不能推出二叉树

3.5二叉树非递归深度遍历算法

需要使用栈

1.先序非递归写法

  • 先压入根节点
  • 弹出元素         访问
  • 将这个元素的右边先压栈,然后左边再压栈

2.中序非递归写法

  • 以根节点开始,整条左边进栈
  • 从栈中弹出节点,访问,然后以这个节点的右孩子为新结点
  • 再次让整条左边进栈,再弹栈

3.后序非递归写法

  • 非递归的后序遍历,需要两个栈,第一个栈作为赋值,第二个栈作为输出
  • 第一个栈压入根节点后,弹到第二个栈,根节点就变成最后输出
  • 后序遍历的倒数第二个应该是头节点的右孩子,所以辅助栈,先左后右
  • 辅助栈弹出元素放入到第二个栈,这个节点先左后右放第一个栈

3.6根据遍历结果重构二叉树

1.  若某二叉树的前遍历访问顺序是序abdgcefh,中序遍历顺序是dgbaechf,则后序遍历的访问顺序是什么。

 2. 已知一棵二叉树的中序序列和后序序列分别是BDCEAFHG 和 DECBHGFA,请画出这棵二叉树。

 

3.7完整代码 

//arrayQueue.h
#ifndef DATA_STRUCTURE_ARRAYQUEUE_H
#define DATA_STRUCTURE_ARRAYQUEUE_H
#include "binaryTree.h"
#define MaxQueue 100
//定义顺序队列的结构
typedef struct {
    pTreeNode data[MaxQueue];         //顺序队列数组
    int front;                      //队头指针,出队用
    int rear;                       //队尾指针,入队用
}ArrayQueue;

//创建顺序队列
ArrayQueue *createArrayQueue();
//删除顺序队列
void releaseArrayQueue(ArrayQueue *queue);
//插入元素
int enArrayQueue(ArrayQueue *queue,pTreeNode e );
//删除元素
int deArrayQueue(ArrayQueue *queue,pTreeNode *e);




#endif //DATA_STRUCTURE_ARRAYQUEUE_H

 

//arrayQueue.cpp

#include "arrayQueue.h"
using namespace std;
//创建顺序队列
ArrayQueue *createArrayQueue(){
    ArrayQueue *queue=new ArrayQueue ;      //申请顺序队列空间
    queue->front=queue->rear=0;             //对顺序队列里的头尾指针初始化
    return queue;                           //返回分配空间的地址
}
//删除顺序队列
void releaseArrayQueue(ArrayQueue *queue){
    if(queue== nullptr){
        cout<<"要删除的顺序队列不存在"<<endl;
        return;
    }
    delete queue;
}
//插入元素
int enArrayQueue(ArrayQueue *queue,pTreeNode e ){
    //插入元素前先判断,队列是否为满
    if((queue->rear+1)%MaxQueue==queue->front){
        cout<<"队列已满"<<endl;
        return -1;
    }
    queue->rear=(queue->rear+1)%MaxQueue;       //头指针先加一
    queue->data[queue->rear]=e;                 //对指向的位置赋值

    return 0;
}
//删除元素
int deArrayQueue(ArrayQueue *queue,pTreeNode *e){
    //删除元素前先判断顺序队列是否为空
    if(queue->front==queue->rear){
        cout<<"队列为空"<<endl;
        return -1;
    }
    queue->front=(queue->front+1)%MaxQueue;     //尾指针++
    *e=queue->data[queue->front];               //将要删除的元素赋值给*e
    return 0;
}


 

//arayStack.h
#ifndef ARRAYSTACK_H
#define ARRAYSTACK_H
/* 顺序栈 满递增栈*/
#include "binaryTree.h"
#define MaxStackSize 20
typedef struct {
    pTreeNode data[MaxStackSize];
    int top;
}ArrayStack;

ArrayStack *createArrayStack();
void releaseArrayStack(ArrayStack *stack);

int pushArrayStack(ArrayStack *stack, pTreeNode e);
int popArrayStack(ArrayStack *stack, pTreeNode *e);
#endif

 

//arrayStack.cpp
#include "arrayStack.h"
using namespace std;
//创建顺序栈
ArrayStack *createArrayStack(){
    ArrayStack *stack=new ArrayStack ;  //申请顺序栈的空间
    stack->top=-1;                      //初始化顺序栈
    return stack;
}
//删除顺序栈
void releaseArrayStack(ArrayStack *stack){
    if(stack){                          //判断stack是否存在
        delete stack;                   //释放分配给stack的空间
    }else{
        cout<<"要被删除的顺序栈不存在"<<endl;
        return;
    }

}
//入栈
int pushArrayStack(ArrayStack *stack,pTreeNode e){
    if(stack->top>=MaxStackSize-1){     //判断是否栈满
        cout<<"栈已满"<<endl;
        return -1;
    }
    stack->data[++stack->top]=e;        //top++,对应位置写入元素
    return 0;
}
//出栈
int popArrayStack(ArrayStack *stack,pTreeNode * e){
    if(stack->top<0){                   //判断栈是否为空
        cout<<"栈以空"<<endl;
        return -1;
    }
    *e=stack->data[stack->top--];       //将top对应元素给e,然后top--
    return 0;
}
//binaryTree.h

#ifndef DATA_STRUCTURE_BINARYTREE_H
#define DATA_STRUCTURE_BINARYTREE_H
#include <iostream>

typedef int Element;
//树的节点结构
typedef class treeNode{
public:
    Element data;       //保存的元素
    treeNode *left;     //左子节点
    treeNode *right;    //右子节点
}TreeNode;
typedef TreeNode* pTreeNode;
//二叉树描述信息的结构
typedef class {
public:
    TreeNode* root; //二叉树的根节点
    int count;      //二叉树的节点数量
}BinaryTree;

BinaryTree* createBinaryTree(TreeNode* root);
void releaseBinaryTree(BinaryTree* tree);

TreeNode* createTreeNode(Element e);
void insertBinaryTree(BinaryTree* tree,TreeNode* parent,TreeNode* left,TreeNode* right);
void visitTreeNode(TreeNode* node);

void preOrderBTreeRecur(BinaryTree *tree);				// 先序遍历tree,使用递归方法
void inOrderBTreeRecur(BinaryTree *tree);				// 中序遍历tree,使用递归方法
void postOrderBTreeRecur(BinaryTree *tree);				// 后序遍历tree,使用递归方法

// 层级遍历、广度遍历
void levelOrderBTree(BinaryTree *tree);
//遍历非递归写法
void preOrderBTreeNoRecur(BinaryTree *tree);
void inOrderBTreeNoRecur(BinaryTree *tree);
void postOrderBTreeNoRecur(BinaryTree *tree);

#endif //DATA_STRUCTURE_BINARYTREE_H

 

//binarryTree.cpp

#include "arrayQueue.h"
#include "arrayStack.h"
/* 申请二叉树的信息体,若有根节点,那么指向根节点,并更新树的节点个数 */
BinaryTree* createBinaryTree(TreeNode* root){
    BinaryTree* tree=new BinaryTree ;
    if(root){
        tree->root=root;
        tree->count=1;
    }else{
        tree->root= nullptr;
        tree->count=0;
    }
    return tree;
}
/*后序递归遍历释放节点*/
static void destoryTreeNode(BinaryTree *tree,TreeNode* node){
    if(node){
        destoryTreeNode(tree,node->left);
        destoryTreeNode(tree,node->right);
        delete node;
        tree->count--;
    }
}
/* 释放二叉树,通过后序遍历的方式,将节点逐个释放 */
void releaseBinaryTree(BinaryTree* tree){
    destoryTreeNode(tree,tree->root);
    std::cout<<"树还有"<<tree->count<<"个节点";
    delete tree;
}
/*产生新节点,初始化左右指针为null*/
TreeNode* createTreeNode(Element e){
    TreeNode* node=new TreeNode ;
    node->data=e;
    node->left=node->right= nullptr;
    return node;
}
/*向父节点插入左右节点*/
void insertBinaryTree(BinaryTree* tree,TreeNode* parent,TreeNode* left,TreeNode* right){
    if(tree&&parent){
        parent->left=left;
        parent->right=right;
        if(left)tree->count++;
        if(right)tree->count++;
    }
}
/*查看节点内容*/
void visitTreeNode(TreeNode* node){
    std::cout<<node->data<<'\t';
}
static void preOrder(TreeNode* node){
    if(node){
        visitTreeNode(node);
        preOrder(node->left);
        preOrder(node->right);
    }
}
static void inOrder(TreeNode* node){
    if(node){
        preOrder(node->left);
        visitTreeNode(node);
        preOrder(node->right);
    }
}

static void postOrder(TreeNode* node){
    if(node){
        preOrder(node->left);
        preOrder(node->right);
        visitTreeNode(node);
    }
}


/*递归先序遍历*/
void preOrderBTreeRecur(BinaryTree *tree){
    if(tree){
        preOrder(tree->root);
    }
}
/*递归中序遍历*/
void inOrderBTreeRecur(BinaryTree *tree){
    if(tree){
        inOrder(tree->root);
    }
}
/*递归后序遍历*/
void postOrderBTreeRecur(BinaryTree *tree){
    if(tree){
        postOrder(tree->root);
    }
}
// 层级遍历、广度遍历
void levelOrderBTree(BinaryTree *tree){
    ArrayQueue *queue=new ArrayQueue ;      //申请一个队列
    pTreeNode node;                         //用来存放取出的节点
    enArrayQueue(queue,tree->root);      //根节点入队
    while(deArrayQueue(queue,&node)!=-1){   //取出节点
        //访问节点
        visitTreeNode(node);
        //入队左右节点
        if(node->left)enArrayQueue(queue,node->left);
        if(node->right)enArrayQueue(queue,node->right);
    }
    //遍历结束,释放队列
    releaseArrayQueue(queue);
}
/* 非递归的先序遍历:
 * 1. 压入根节点到栈
 * 2. 弹出元素,访问该元素
 * 3. 将该元素的所知道的任务压入栈(有右压右,有左压左)
 * */
void preOrderBTreeNoRecur(BinaryTree *tree){
    treeNode* node;
    if(tree){
        ArrayStack* stack=createArrayStack() ;
        pushArrayStack(stack,tree->root);
        while(popArrayStack(stack,&node)!=-1&&node){
            visitTreeNode(node);
            if(node->right)pushArrayStack(stack,node->right);
            if(node->left)pushArrayStack(stack,node->left);
        }
        releaseArrayStack(stack);
    }
}
/* 非递归的中序遍历:
 * 以根节点开始,整条左边进栈
 * 从栈中弹出节点,访问,然后以这个节点的右孩子为新节点
 * 再次安装整条左边进栈,再弹栈
 * */
void inOrderBTreeNoRecur(BinaryTree *tree){
    TreeNode* node;
    if(tree->root){
        ArrayStack* stack=createArrayStack() ;
        node=tree->root;
        while(stack->top>=0||node){
            if(node){
                pushArrayStack(stack,node);
                node=node->left;
            }else{
                popArrayStack(stack,&node);
                visitTreeNode(node);
                node=node->right;
            }
        }
        releaseArrayStack(stack);
    }
}
/* 1. 非递归的后序遍历,需要两个栈,第一个栈作为辅助,最后一个栈作为输出
 * 2. 第一个栈压入根节点后,弹出第二个栈,根节点就变成最后输出
 * 3. 后序遍历的倒数第二个应该是头节点的右孩子,所以辅助栈,先左后右
 * 4. 辅助栈弹出元素放入到第二个栈,这个节点先左后右放第一个栈
 * */
void postOrderBTreeNoRecur(BinaryTree *tree){
    TreeNode* node;
    if(tree){
        ArrayStack* stack1=createArrayStack() ;
        ArrayStack* stack2=createArrayStack() ;
        pushArrayStack(stack1,tree->root);  //初始化辅助栈
        while (popArrayStack(stack1,&node)!=-1){
            pushArrayStack(stack2,node);
            if(node->left)pushArrayStack(stack1,node->left);
            if(node->right)pushArrayStack(stack1,node->right);
        }
        while(popArrayStack(stack2,&node)!=-1){
            visitTreeNode(node);
        }
        releaseArrayStack(stack1);
        releaseArrayStack(stack2);
    }
}

 

#include "binaryTree.h"


BinaryTree *initTree1() {
    TreeNode *nodeA = createTreeNode('A');
    TreeNode *nodeB = createTreeNode('B');
    TreeNode *nodeC = createTreeNode('C');
    TreeNode *nodeD = createTreeNode('D');
    TreeNode *nodeE = createTreeNode('E');
    TreeNode *nodeF = createTreeNode('F');
    TreeNode *nodeG = createTreeNode('G');
    TreeNode *nodeH = createTreeNode('H');
    TreeNode *nodeK = createTreeNode('K');

    BinaryTree *tree = createBinaryTree(nodeA);
    insertBinaryTree(tree, nodeA, nodeB, nodeE);
    insertBinaryTree(tree, nodeB, NULL, nodeC);
    insertBinaryTree(tree, nodeE, NULL, nodeF);
    insertBinaryTree(tree, nodeC, nodeD, NULL);
    insertBinaryTree(tree, nodeF, nodeG, NULL);
    insertBinaryTree(tree, nodeG, nodeH, nodeK);
    return tree;
}

int main() {
    BinaryTree *tree = initTree1();
    printf("树的节点数: %d\n", tree->count);
    printf("先序遍历: ");
    preOrderBTreeRecur(tree);
    printf("\n中序遍历: ");
    inOrderBTreeRecur(tree);
    printf("\n后序遍历: ");
    postOrderBTreeRecur(tree);
    printf("\n层次遍历: ");
    levelOrderBTree(tree);
    printf("\n非递归先序遍历: ");
    preOrderBTreeNoRecur(tree);
    printf("\nInorder: ");
    inOrderBTreeNoRecur(tree);
    printf("\nPostOrder:");
    postOrderBTreeNoRecur(tree);
    printf("\n");
    releaseBinaryTree(tree);
    return 0;
}

4.线索二叉树

4.1背景介绍

现有一棵结点数目为n的二叉树,采⽤二叉链表的形式存储,对于每个结点均有指向左右孩子的两个指针域。 结点为n的二叉树一共有n-1条有效分支路径。那么,则二叉链表中存在

                2n - ( n-1 ) = n + 1个 空指针域

那么,这些空指针造成了空间浪费。

 当对二叉树进行中序遍历时可以得到二叉树的中序序列。

如图所示二叉树的中序遍历结果为DBEAC,可以得知A的前驱结点为E,后继结点为C

这种关系的获得是建⽴在完成遍历后得到 的,那么可不可以在建⽴二叉树时就记录下前驱后继的关系呢,那么在后续寻找前驱结点和后继结点时将⼤⼤提升效率

4.2线索化

  • 将某结点的空指针域指向该结点的前驱后继,定义规则如下:
  1. 若结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
  2. 若结点的右子树为空,则该结点的右孩子指针指向其后继结点。
  • 这种指向前驱和后继的指针称为线索。将一棵普通二叉树以某种次序遍历,并添加线索的过程称为线索化。

 4.3线索化的改进

  • 可以将一棵二叉树线索化为一棵线索二叉树,那么新的问题产⽣了。我们如何区分一个结点的 lchild指针是指向左孩 子还是前驱结点呢?
  • 为了解决这一问题,现需要添加标志位ltag,rtag。并定义规则如下:
  1. ltag为0时,指向左孩子,为1时指向前驱
  2. rtag为0时,指向右孩子,为1时指向后继

  •  在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以 在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。

4.4线索化的优势

  • 递归遍历需要使⽤系统栈,非递归遍历需要使⽤内存中的空间来帮助遍历,⽽线索化之后就不需 要这些辅助了,直接可以像遍历数组一样遍历。
  • 线索二叉树核心目的在于加快查找结点的前驱和后继的速度。如果不使⽤线索的话,当查找一个 结点的前驱与后继需 要从根节点开始遍历,当然,如果二叉树数据量较⼩时,可能线索化之后作⽤不 ⼤,但是当数据量很⼤时,线索化所 带来的性能提升就会比较明显。

4.5完整代码

//threadedTree.h

#ifndef DATA_STRUCTURE_THREADEDTREE_H
#define DATA_STRUCTURE_THREADEDTREE_H
typedef int Element;
// 线索二叉树的节点结构
typedef struct treeNode {
    Element data;
    struct treeNode *left;
    struct treeNode *right;
    int lTag;					// 0表示left指向左节点,1表示left指向前驱
    int rTag;					// 0表示right指向右节点,1表示right指向后继
}TBTNode;
// 二叉树的描述信息结构
typedef struct {
    TBTNode *root;			// 二叉树的根节点
    int count;				// 二叉树的节点个数
}ThreadedBTree;
//创建线索树表头
ThreadedBTree *createThreadedBTree(TBTNode *root);
//释放线索树
void releaseThreadedBTree(ThreadedBTree *tree);
//创建线索化二叉树节点
TBTNode *createTBTNode(Element e);
//插入节点
void insertThreadedBTree(ThreadedBTree *tree, TBTNode *parent, TBTNode *left, TBTNode *right);
//访问节点
void visitTBTNode(TBTNode *node);

// 中序线索化树,在中序遍历的过程中,建立左右孩子为空指针的值的确定
void inOrderThreadedTree(ThreadedBTree *tree);
// 按照已经线索化后的树,进行中序遍历
void inOrderTBTree(ThreadedBTree *tree);





#endif //DATA_STRUCTURE_THREADEDTREE_H
//threadedTree.cpp

#include "threadedTree.h"
#include<iostream>
using namespace std;
//创建线索树表头
ThreadedBTree *createThreadedBTree(TBTNode *root) {
    ThreadedBTree *tree = new ThreadedBTree ;
    if (root) {
        tree->root = root;
        tree->count = 1;
    } else {
        tree->root = NULL;
        tree->count = 0;
    }
    return tree;
}

// 后序遍历释放节点,但在找后继节点时,需要找非线索化的节点
static void freeTBTNode(ThreadedBTree* tree,TBTNode* node){
    if(node){
        if(node->rTag==0)freeTBTNode(tree,node->right);
        if(node->lTag==0)freeTBTNode(tree,node->left);
        delete node;
        tree->count--;
    }
}
//释放线索树
void releaseThreadedBTree(ThreadedBTree *tree){
    if(tree){
        freeTBTNode(tree,tree->root);
        cout<<"树还有"<<tree->count<<"个节点。"<<endl;
    }
    delete tree;
}
/* 产生新节点,初始化左右指针为NULL */
TBTNode *createTBTNode(Element e){
    TBTNode* node=new TBTNode ;
    node->left=node->right= nullptr;
    node->rTag=node->lTag=0;
    node->data=e;
    return node;
}
//插入节点
void insertThreadedBTree(ThreadedBTree *tree, TBTNode *parent, TBTNode *left, TBTNode *right){
    if(tree&&parent){
        parent->left=left;
        parent->right=right;
        if(left)tree->count++;
        if(right)tree->count++;
    }
}
//访问节点
void visitTBTNode(TBTNode *node){
    if(node)cout<<node->data<<"\t";
}

static TBTNode *pre = NULL;                     //用来保存前一个节点位置的指针
static void inOrderThreading(TBTNode *node){
    if(node){
        inOrderThreading(node->left);
        if(node->left== nullptr){               //更新当前结点的前驱
            node->lTag=1;
            node->left=pre;
        }
        // 当前节点会不会是前面那个节点后继节点
        if(pre&&pre->right== nullptr){
            pre->rTag=1;
            pre->right=node;
        }
        pre=node;
        inOrderThreading(node->right);
    }
}
// 中序线索化树,在中序遍历的过程中,建立左右孩子为空指针的值的确定
void inOrderThreadedTree(ThreadedBTree *tree){
    if(tree){
        inOrderThreading(tree->root);
    }
}
// 按照已经线索化后的树,进行中序遍历
void inOrderTBTree(ThreadedBTree *tree){
    TBTNode *node = tree->root;
    while (node) {
        while (node->lTag == 0) {
            node = node->left;
        }
        visitTBTNode(node);
        // 一直向右开始遍历,只要右边是后继标记,就查看
        while (node->rTag && node->right) {
            node = node->right;
            visitTBTNode(node);
        }
        node = node->right;
    }
}
#include <iostream>
#include "threadedTree.h"

ThreadedBTree *initTree() {
    TBTNode *nodeA = createTBTNode('A');
    TBTNode *nodeB = createTBTNode('B');
    TBTNode *nodeC = createTBTNode('C');
    TBTNode *nodeD = createTBTNode('D');
    TBTNode *nodeE = createTBTNode('E');
    TBTNode *nodeF = createTBTNode('F');
    TBTNode *nodeG = createTBTNode('G');
    TBTNode *nodeH = createTBTNode('H');
    TBTNode *nodeK = createTBTNode('K');

    ThreadedBTree *tree = createThreadedBTree(nodeA);
    insertThreadedBTree(tree, nodeA, nodeB, nodeE);
    insertThreadedBTree(tree, nodeB, NULL, nodeC);
    insertThreadedBTree(tree, nodeE, NULL, nodeF);
    insertThreadedBTree(tree, nodeC, nodeD, NULL);
    insertThreadedBTree(tree, nodeF, nodeG, NULL);
    insertThreadedBTree(tree, nodeG, nodeH, nodeK);
    return tree;
}

int main() {
    ThreadedBTree *tree = initTree();
    // 1. 对原二叉树进行中序线索化,n + 1个空节点,按照左孩子指针指向前驱,右孩子指针指向后继
    inOrderThreadedTree(tree);
    // 2. 根据线索化后的树,进行中序遍历
    inOrderTBTree(tree);
    releaseThreadedBTree(tree);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值